1. 程式人生 > 其它 >Mybatis-mapper-xml-基礎

Mybatis-mapper-xml-基礎

今天學習http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html。關於mapper.xml的sql語句的使用。

專案路徑:https://github.com/chenxing12/l4mybatis

首先,準備環境。

1.建立project

在parent專案上右鍵,new model->maven->mybatis-mapper.

填充pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>l4mybatis</artifactId>
        <groupId>com.test</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mytatis-mapper</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>


</project>

在resources下新增log4j.properties:

log4j.rootLogger=DEBUG, stdout, logfile


log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=log/test.log
log4j.appender.logfile.MaxFileSize=128MB
log4j.appender.logfile.MaxBackupIndex=3
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c.%M(%L) - %m%n

在resources下新增mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="db.properties"/>

    <typeAliases>
        <package name="com.test.mapper.model"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com.test.mapper.mapper/PersonMapper.xml"/>
    </mappers>
</configuration>

在resources下新增db.properties:

#jdbc.driver=com.mysql.jdbc.Driver
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&useSSL=false
jdbc.username=root
jdbc.password=123456

在資料庫mybatis中建立一個person表:

/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 50605
Source Host           : localhost:3306
Source Database       : mybatis

Target Server Type    : MYSQL
Target Server Version : 50605
File Encoding         : 65001

Date: 2016-07-06 22:22:34
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for person
-- ----------------------------
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of person
-- ----------------------------
INSERT INTO `person` VALUES ('1', 'Ryan');

在resources下建立com.test.mapper.mapper/PersonMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.test.mapper.dao.PersonMapper">
    <select id="selectPerson" parameterType="int" resultType="hashmap">
        select * from person where id = #{id}
    </select>

</mapper>

在java下新建com.test.mapper.dao.PersonMapper.java:

package com.test.mapper.dao;

import java.util.HashMap;

/**
 * Created by miaorf on 2016/7/6.
 */
public interface PersonMapper {

    HashMap selectPerson(int id);
}

在java下新增:com.test.mapper.model.Person:

package com.test.mapper.model;

import java.io.Serializable;

/**
 * Created by miaorf on 2016/7/6.
 */
public class Person implements Serializable {

    private Integer id;
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + ''' +
                '}';
    }
}

測試環境:

在test下建立com.test.mapper.dao.PersonMapperTest:

package com.test.mapper.dao;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.util.HashMap;

import static org.junit.Assert.*;

/**
 * Created by miaorf on 2016/7/6.
 */
public class PersonMapperTest {
    private SqlSession sqlSession;
    private static SqlSessionFactory sqlSessionFactory;

    @BeforeClass
    public static void init() throws IOException {
        String config = "mybatis-config.xml";
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(config));
    }

    @Before
    public void setUp() throws Exception {
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void selectPerson() throws Exception {
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        HashMap map = mapper.selectPerson(1);
        System.out.println(map);
    }

}

執行:

2016-07-06 22:23:31,962 DEBUG [org.apache.ibatis.logging.LogFactory] - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
2016-07-06 22:23:32,128 DEBUG [org.apache.ibatis.io.VFS] - Class not found: org.jboss.vfs.VFS
2016-07-06 22:23:32,129 DEBUG [org.apache.ibatis.io.JBoss6VFS] - JBoss 6 VFS API is not available in this environment.
2016-07-06 22:23:32,131 DEBUG [org.apache.ibatis.io.VFS] - Class not found: org.jboss.vfs.VirtualFile
2016-07-06 22:23:32,132 DEBUG [org.apache.ibatis.io.VFS] - VFS implementation org.apache.ibatis.io.JBoss6VFS is not valid in this environment.
2016-07-06 22:23:32,134 DEBUG [org.apache.ibatis.io.VFS] - Using VFS adapter org.apache.ibatis.io.DefaultVFS
2016-07-06 22:23:32,135 DEBUG [org.apache.ibatis.io.DefaultVFS] - Find JAR URL: file:/D:/workspace/mybatis/l4mybatis/mytatis-mapper/target/classes/com/test/mapper/model
2016-07-06 22:23:32,135 DEBUG [org.apache.ibatis.io.DefaultVFS] - Not a JAR: file:/D:/workspace/mybatis/l4mybatis/mytatis-mapper/target/classes/com/test/mapper/model
2016-07-06 22:23:32,213 DEBUG [org.apache.ibatis.io.DefaultVFS] - Reader entry: Person.class
2016-07-06 22:23:32,214 DEBUG [org.apache.ibatis.io.DefaultVFS] - Listing file:/D:/workspace/mybatis/l4mybatis/mytatis-mapper/target/classes/com/test/mapper/model
2016-07-06 22:23:32,214 DEBUG [org.apache.ibatis.io.DefaultVFS] - Find JAR URL: file:/D:/workspace/mybatis/l4mybatis/mytatis-mapper/target/classes/com/test/mapper/model/Person.class
2016-07-06 22:23:32,215 DEBUG [org.apache.ibatis.io.DefaultVFS] - Not a JAR: file:/D:/workspace/mybatis/l4mybatis/mytatis-mapper/target/classes/com/test/mapper/model/Person.class
2016-07-06 22:23:32,217 DEBUG [org.apache.ibatis.io.DefaultVFS] - Reader entry: ����   1 6
2016-07-06 22:23:32,220 DEBUG [org.apache.ibatis.io.ResolverUtil] - Checking to see if class com.test.mapper.model.Person matches criteria [is assignable to Object]
2016-07-06 22:23:32,306 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2016-07-06 22:23:32,307 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2016-07-06 22:23:32,309 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2016-07-06 22:23:32,310 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - PooledDataSource forcefully closed/removed all connections.
2016-07-06 22:23:32,511 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Opening JDBC Connection
2016-07-06 22:23:32,842 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 733672688.
2016-07-06 22:23:32,842 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2bbaf4f0]
2016-07-06 22:23:32,847 DEBUG [com.test.mapper.dao.PersonMapper.selectPerson] - ==>  Preparing: select * from person where id = ? 
2016-07-06 22:23:32,911 DEBUG [com.test.mapper.dao.PersonMapper.selectPerson] - ==> Parameters: 1(Integer)
2016-07-06 22:23:32,946 DEBUG [com.test.mapper.dao.PersonMapper.selectPerson] - <==      Total: 1
{name=Ryan, id=1}

2.select

查詢語句。負責拼接查詢語句並將查詢結果映射出來。上例中:

<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>
  • 這個語句的id為selectPerson,這個就是對應mapper介面中的方法的名字。
  • parameterType是輸入引數型別為int。
  • resultType表示查詢結果對映為HashMap
  • #{id}是佔位符,相當於JDBC中採用PreparedStatement時sql語句中的問號,表示引數名為id的引數值會替換這個位置。

注意到mapper.xml的namespace就是指向所對應的mapper 介面:

<mapper namespace="com.test.mapper.dao.PersonMapper">

在mapper介面中的方法要和mapper.xml中的id所一一對應。因此,這個查詢的節點對應的mapper介面的方法為:

public interface PersonMapper {

    HashMap selectPerson(int id);
}

事實上,select節點的可選引數有以下幾種:

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10000"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">

文件對各個引數含義給出瞭解釋:

屬性

描述

id

在名稱空間中唯一的識別符號,可以被用來引用這條語句。

parameterType

將會傳入這條語句的引數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的引數,預設值為 unset。

parameterMap

這是引用外部 parameterMap 的已經被廢棄的方法。使用內聯引數對映和 parameterType 屬性。

resultType

從這條語句中返回的期望型別的類的完全限定名或別名。注意如果是集合情形,那應該是集合可以包含的型別,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。

resultMap

外部 resultMap 的命名引用。結果集的對映是 MyBatis 最強大的特性,對其有一個很好的理解的話,許多複雜對映的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同時使用。

flushCache

將其設定為 true,任何時候只要語句被呼叫,都會導致本地快取和二級快取都會被清空,預設值:false。

useCache

將其設定為 true,將會導致本條語句的結果被二級快取,預設值:對 select 元素為 true。

timeout

這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為 unset(依賴驅動)。

fetchSize

這是嘗試影響驅動程式每次批量返回的結果行數和這個設定值相等。預設值為 unset(依賴驅動)。

statementType

STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。

resultSetType

FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一個,預設值為 unset (依賴驅動)。

databaseId

如果配置了 databaseIdProvider,MyBatis 會載入所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。

resultOrdered

這個設定僅針對巢狀結果 select 語句適用:如果為 true,就是假設包含了巢狀結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。這就使得在獲取巢狀的結果集的時候不至於導致記憶體不夠用。預設值:false。

resultSets

這個設定僅對多結果集的情況適用,它將列出語句執行後返回的結果集並每個結果集給一個名稱,名稱是逗號分隔的。

3.Insert

首先,準備資料庫:

CREATE TABLE `author` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(40) NOT NULL DEFAULT '',
  `password` varchar(128) NOT NULL DEFAULT '',
  `email` varchar(40) NOT NULL DEFAULT '',
  `bio` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

然後,編寫對應實體:com.test.mapper.model.Author

package com.test.mapper.model;

import java.io.Serializable;

/**
 * Created by miaorf on 2016/7/7.
 */
public class Author implements Serializable {

    private Integer id;
    private String username;
    private String password;
    private String email;
    private String bio;

    public Author() {
    }

    public Author(String username, String password, String email, String bio) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.bio = bio;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getBio() {
        return bio;
    }

    public void setBio(String bio) {
        this.bio = bio;
    }
}

編寫mapper介面:com.test.mapper.dao.AuthorMapper

package com.test.mapper.dao;

import com.test.mapper.model.Author;

import java.util.HashMap;
import java.util.List;

/**
 * Created by miaorf on 2016/7/6.
 */
public interface AuthorMapper {

    int insertAuthor(Author author);

    int insertAuthors(List<Author> list);
}

編寫mapper.xml:com.test.mapper.mapper/AuthorMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.test.mapper.dao.AuthorMapper">
    <insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO author(username,password,email,bio)
        VALUES (#{username},#{password},#{email},#{bio})
    </insert>

    <insert id="insertAuthors" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO author(username,password,email,bio) VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.username},#{item.password},#{item.email},#{item.bio})
        </foreach>
    </insert>











</mapper>

編寫測試用例:com.test.mapper.dao.AuthorMapperTest

package com.test.mapper.dao;

import com.test.mapper.model.Author;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;

/**
 * Created by miaorf on 2016/7/7.
 */
public class AuthorMapperTest {
    private SqlSession sqlSession;
    private static SqlSessionFactory sqlSessionFactory;

    @BeforeClass
    public static void init() throws IOException {
        String config = "mybatis-config.xml";
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(config));
    }

    @Before
    public void setUp() throws Exception {
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void insertAuthorTest() throws  Exception{
        AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
        Author ryan = new Author("Ryan", "123456", "[email protected]", "this is a blog");
        int result = mapper.insertAuthor(ryan);
        sqlSession.commit();

        assertNotNull(ryan.getId());
    }

    @Test
    public void insertAuthorsTest() throws  Exception{
        AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
        List<Author> list = new ArrayList<Author>();
        for (int i = 0; i < 3; i++) {
            list.add(new Author("Ryan"+i, "123456", "[email protected]", "this is a blog"));
        }

        int result = mapper.insertAuthors(list);
        sqlSession.commit();

        assertNotNull(list.get(2).getId());
    }

}

以上就是一個簡單的插入了,資料庫為支援主鍵自增的,比如Mysql。可以返回主鍵到對應實體中。下面來分析每個引數:

3.1 AuthorMapperTest

前文講過mybatis的SqlSessionFactory 的最佳範圍是應用範圍。有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。所以,這個必須是要可以暴露出啦的bean。每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的範圍是請求或方法範圍。絕對不能將 SqlSession 例項的引用放在一個類的靜態域,甚至一個類的例項變數也不行。

因此,將SqlSessionFactory設定成靜態成員變數,在類初始化的時候就載入。而SqlSession則要在每次請求資料庫的時候生成,這裡放到Before裡是為了複用在多個Test中,如果是在一個dao中,就不可以將SqlSession放到成員變量了,應該為區域性變數。

3.2 insert 返回值

通過獲取mapper介面,然後呼叫方法來實現sql的執行。insert方法的返回值是整形,對應mysql中執行成功的結果數。插入一條成功則返回1,插入n條成功,則返回n,插入失敗,返回0. 當然,失敗通常會丟擲異常。這個在更新的時候有用,如果不需要更新,則返回0,更新成功n條返回n。

3.3 AuthorMapper.xml

使用完全限定名來指定對應的mappr介面:

<mapper namespace="com.test.mapper.dao.AuthorMapper">

用insert節點來編寫插入語句:

 <insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO author(username,password,email,bio)
        VALUES (#{username},#{password},#{email},#{bio})
  </insert>
  • id表示mapper介面的方法名,要一致
  • useGeneratedKeys="true"表示資料庫支援主鍵自增
  • keyProperty="id"表示自增欄位為id,這個屬性和useGeneratedKeys配合使用
  • 發現這裡沒有指定parameterType,因為mybatis可以通過 TypeHandler 推斷出具體傳入語句的引數
  • 也沒有指定resultType,MyBatis 通常可以推算出來,但是為了更加確定寫上也不會有什麼問題

到這裡是理想插入,但如果其中一個欄位是null,而我們設定資料庫欄位不允許為null,則會丟擲異常。也就是說,在執行這條語句前一定要檢查各項引數是否滿足要求。

 @Test(expected = PersistenceException.class)
    public void insertAuthorExceptionTest() throws  Exception{
        AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
        Author ryan = new Author("Ryan", null, "[email protected]", "this is a blog");
        int result = mapper.insertAuthor(ryan);
        sqlSession.commit();

        assertNotNull(ryan.getId());
    }

4.update

更新前需要先有一個實體,這裡先來查詢一個出來:

4.1新增selectById

在com.test.mapper.mapper/AuthorMapper.xml中新增一個select節點:

<select id="selectAuthorById" resultType="author">
        SELECT * FROM author WHERE id = #{id}
</select>

和insert不同的是,這裡必須制定resultType或者resultMap,否則無法對映查詢結果。

在com.test.mapper.dao.AuthorMapper中新增對應的方法:

Author selectAuthorById(int id);

新增測試用例:

@Test
    public void selectAuthorByIdTest() throws Exception{
        AuthorMapper mapper = sqlSession.getMapper(AuthorMapper.class);
        Author author = mapper.selectAuthorById(3);
        assertNotNull(author);
    }

4.2 新增update節點

AuthorMapper.xml:

<update id="updateAuthor" >
        update Author set
            username = #{username},
            password = #{password},
            email = #{email},
            bio = #{bio}
        where id = #{id}
</update>

AuthorMapper.java:

int updateAuthor(Author author);

AuthorMapperTest.java:

@Test
    public void TestUpdateAuthorEffective() throws Exception{
        Author author = mapper.selectAuthorById(3);
        author.setBio("I have changed the bio");
        int i = mapper.updateAuthor(author);
        sqlSession.commit();
        assertTrue(i>0);
    }
    @Test
    public void TestUpdateAuthorInEffective() throws Exception{
        Author author = mapper.selectAuthorById(3);
        author.setId(1024);
        int i = mapper.updateAuthor(author);
        sqlSession.commit();
        assertTrue(i==0);
    }

update和insert一樣,mybatis自動判斷輸入和輸出。

 5.delete

新增delete節點:

<delete id="deleteAuthor">
        delete from Author where id = #{id}
</delete>

 新增delete方法:

int deleteAuthor(int id);

新增delete測試:

@Test
    public void TestDeleteAuthorEffective() throws Exception{
        int i = mapper.deleteAuthor(3);
        assertTrue(i==1);
    }
    @Test
    public void TestDeleteAuthorInEffective() throws Exception{
        int i = mapper.deleteAuthor(3000);
        assertTrue(i==0);
    }

delete節點也不用指定輸入和輸出,當然也可以指定。

6.insert,update,delete引數表

屬性

描述

id

名稱空間中的唯一識別符號,可被用來代表這條語句。

parameterType

將要傳入語句的引數的完全限定類名或別名。這個屬性是可選的,因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的引數,預設值為 unset。

parameterMap

這是引用外部 parameterMap 的已經被廢棄的方法。使用內聯引數對映和 parameterType 屬性。

flushCache

將其設定為 true,任何時候只要語句被呼叫,都會導致本地快取和二級快取都會被清空,預設值:true(對應插入、更新和刪除語句)。

timeout

這個設定是在丟擲異常之前,驅動程式等待資料庫返回請求結果的秒數。預設值為 unset(依賴驅動)。

statementType

STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,預設值:PREPARED。

useGeneratedKeys

(僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由資料庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關係資料庫管理系統的自動遞增欄位),預設值:false。

keyProperty

(僅對 insert 和 update 有用)唯一標記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設定它的鍵值,預設:unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。

keyColumn

(僅對 insert 和 update 有用)通過生成的鍵值設定表中的列名,這個設定僅在某些資料庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設定。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。

databaseId

如果配置了 databaseIdProvider,MyBatis 會載入所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。

 7.SQL程式碼段

為了提高程式碼重複利用率,mybatis提供了將部分sql片段提煉出來的, 作為公共程式碼使用。

<sql id="userColumns">
        ${alias}.id,${alias}.username,${alias}.password,${alias}.email,${alias}.bio
</sql>
<select id="selectAuthor" resultType="map">
        SELECT 
          <include refid="userColumns"><property name="alias" value="t1"/></include>
        FROM author t1
</select>
List<Author> selectAuthor();
@Test
    public void TestSelectAuthor() throws Exception{
        List<Author> authors = mapper.selectAuthor();
        assertTrue(authors.size()>0);
    }

 8.Parameters(引數)

前文在insert的時候提到過,輸入引數不指定,可以通過物件判斷出來。然而遇到複雜的輸入引數的時候就需要指定.將要傳入語句的引數的完全限定類名或別名.

修改insert節點,增加parameterType:

<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id" parameterType="author">
        INSERT INTO author(username,password,email,bio)
        VALUES (#{username},#{password},#{email},#{bio})
</insert>

parameterType指定為author。這個的全稱是:com.test.mapper.model.Author,之所以簡寫是因為在mybatis-config.xml中配置了名稱簡化:

<typeAliases>
        <package name="com.test.mapper.model"/>
 </typeAliases>

該配置指定model下的類都可以簡寫為類名首字母小寫。

username,password等屬性可以通過查詢輸入物件Author的getter屬性獲得。

注意:

  輸入引數是簡單型別int,short,long,char,byte,boolean,float,double可以直接寫基本型別。如果是類,則要寫全稱,比java.lang.String.

輸出引數

輸出引數用resultType表示,用來和查詢出來的結果集對映。下面是一個簡單的查詢:

<select id="selectAuthorById" resultType="author">
        SELECT * FROM author WHERE id = #{id}
</select>

查詢出來的欄位和resultType中author對映。

如果列名和bean的屬性不一致如何對映?需要使用查詢的別名:

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

還有一種做法是將結果集單獨拿出來,使用resultMap而不是resultType:

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

對應的,查詢語句的返回型別為resultMap:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

其實,很容易看出來,resultType和resultMap的作用都是將結果集對映成java bean,因此二者只能使用一個,不然無法確定究竟轉換成哪個bean。而當我們涉及聯合查詢的時候,查詢的結果集並沒有建立的bean可以對映,這時候resultMap就可以為結果集提供對映方案。下面學習resultMap的各種引數

高階結果對映

1. id&result

在上述查詢中,看到了id和result節點。相信很容易就猜出來。id就是表的主鍵欄位對映,result則表示主鍵以外的對映。標註id對效能快取等有幫助。

屬性

描述

property

對映到列結果的欄位或屬性。如果匹配的是存在的,和給定名稱相同 的 JavaBeans 的屬性,那麼就會使用。否則 MyBatis 將會尋找給定名稱 property 的欄位。這兩種情形你可以使用通常點式的複雜屬性導航。比如,你 可以這樣對映一些東西: “username” ,或者對映到一些複雜的東西: “address.street.number” 。

column

從資料庫中得到的列名,或者是列名的重新命名標籤。這也是通常和會 傳遞給 resultSet.getString(columnName)方法引數中相同的字串。

javaType

一個 Java 類的完全限定名,或一個類型別名(參考上面內建類型別名 的列表) 。如果你對映到一個 JavaBean,MyBatis 通常可以斷定型別。 然而,如果你對映到的是 HashMap,那麼你應該明確地指定 javaType 來保證所需的行為。

jdbcType

在這個表格之後的所支援的 JDBC 型別列表中的型別。JDBC 型別是僅 僅需要對插入,更新和刪除操作可能為空的列進行處理。這是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 程式設計,你需要指定 這個型別-但僅僅對可能為空的值。

typeHandler

我們在前面討論過預設的型別處理器。使用這個屬性,你可以覆蓋默 認的型別處理器。這個屬性值是類的完全限定名或者是一個型別處理 器的實現,或者是類型別名。

2.構造方法

上述方法中,id可以對映主鍵,result可以對映其他列,貌似已經完全了。也確實完全了,但對於屬性注入,構造方法顯然是個很好的辦法。Mybatis提供了構造方法的對映:

首先,給Author增加一個構造方法:

public Author(Integer id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
}

然後,在com.test.mapper.mapper/AuthorMapper.xml中新增select:

<resultMap id="authorByConstructor" type="author">
        <constructor>
            <idArg column="id" javaType="int"/>
            <arg column="username" javaType="String"/>
            <arg column="password" javaType="String"/>
        </constructor>
        <result column="email" property="email"/>
    </resultMap>
    <select id="selectAuthor2Construct" resultMap="authorByConstructor">
        SELECT * FROM author
    </select>

新增對應的介面:com.test.mapper.dao.AuthorMapper:

List<Author> selectAuthor2Construct();

測試:

@Test
    public void testSelectAuthor2Construct() throws Exception{
        List<Author> authors = mapper.selectAuthor2Construct();
        assertTrue(authors.get(0).getUsername()!=null);
    }

3. 關聯查詢

做查詢之前,先修改幾個配置。mapper.xml是在mybatis-config.xml中指定,那麼我們每增加一個mapper都要增加一個配置,很麻煩。為了簡化配置。需要將mapper介面和mapper.xml放到同一個檔案下,並且介面和xml檔案命名一致。使用mybatis的自動掃描:

.這樣,當我們新增介面的時候,直接建立介面和對應xml檔案就可以了:

<mappers>
        <!--<mapper resource="com.test.mapper.dao/AuthorMapper.xml"/>-->
        <package name="com.test.mapper.dao"/>
</mappers>

3.1prepare

增加一個表blog :

CREATE TABLE `blog` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `author_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; 

建立實體類com.test.mapper.model.Blog:

package com.test.mapper.model;

/**
 * Created by miaorf on 2016/7/20.
 */
public class Blog {
    private Integer id;
    private String name;
    private Author author;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Author getAuthor() {
        return author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", name='" + name + ''' +
                ", author=" + author +
                '}';
    }
}

建立介面:com/test/mapper/dao/BlogMapper.xml

package com.test.mapper.dao;

import com.test.mapper.model.Blog;

import java.util.List;

/**
 * Created by miaorf on 2016/7/20.
 */
public interface BlogMapper {

    List<Blog> selectBlog(Integer id);
}

 建立xml:com/test/mapper/dao/BlogMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.test.mapper.dao.BlogMapper">
    <resultMap id="blogResult" type="blog">
        <association property="author" column="author_id" javaType="author" select="com.test.mapper.dao.AuthorMapper.selectAuthorById"/>
    </resultMap>
    <select id="selectBlog" resultMap="blogResult">
        select * from blog where id = #{id}
    </select>

</mapper>

3.2含義

首先,修改了mybatis配置檔案中mapper掃描配置,因此可以直接在掃描包下新增介面和xml檔案。

其次,關於mybatis的名稱空間namespace的用法,這個是唯一的,可以代表這個xml檔案本身。因此,當我想要引用Author的查詢的時候,我可以直接使用AuthorMapper.xml的名稱空間點select的id來唯一確定select片段。即:

select="com.test.mapper.dao.AuthorMapper.selectAuthorById"

然後,關聯查詢,blog的author_id欄位和author的id欄位關聯。因此,將Author作為Blog的一個屬性,先查詢blog,然後根據author_id欄位去查詢author。也就是說,查詢了兩次。這裡也是我困惑的地方,為什麼mybatis要這樣處理,命名可以一次查詢取得資料非要兩次查詢。(待求證)

注意:association節點中

  • column欄位是author_id,是當做引數傳遞給查詢Author的查詢語句的,如果查詢語句的引數有多個則:column= ” {prop1=col1,prop2=col2} ” 這種語法來傳遞給巢狀查詢語 句。這會引起 prop1 和 prop2 以引數物件形式來設定給目標巢狀查詢語句。
  • property則表示在Blog類中對應的屬性。

我們有兩個查詢語句:一個來載入部落格,另外一個來載入作者,而且部落格的結果對映描 述了“selectAuthor”語句應該被用來載入它的 author 屬性。 其他所有的屬性將會被自動載入,假設它們的列和屬性名相匹配。 這種方式很簡單, 但是對於大型資料集合和列表將不會表現很好。 問題就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:

  • 你執行了一個單獨的 SQL 語句來獲取結果列表(就是“+1”)。
  • 對返回的每條記錄,你執行了一個查詢語句來為每個載入細節(就是“N”)。

這個問題會導致成百上千的 SQL 語句被執行。這通常不是期望的。 MyBatis 能延遲載入這樣的查詢就是一個好處,因此你可以分散這些語句同時執行的消 耗。然而,如果你載入一個列表,之後迅速迭代來訪問巢狀的資料,你會呼叫所有的延遲加 載,這樣的行為可能是很糟糕的。 所以還有另外一種方法

3.3 關聯查詢結果

上述關聯查詢主要是為了延遲載入,做快取用的,如果你不呼叫blog.getAuthor()來獲取author,那麼mybatis就不會去查詢Author。也就是說,mybatis把部落格和作者的查詢當做兩步來執行。實現資訊分段載入,在某些場合是有用的。然而在部落格這裡,顯然不太合適,因為我們看到部落格的同時都會看到作者,那麼必然會導致查詢資料庫兩次。下面,來測試另一種方式,像sql關聯查詢一樣,一次查出結果。

<select id="selectBlogWithAuthor" resultMap="blogResultWithAuthor">
        SELECT
          b.id        as blog_id,
          b.name      as blog_title,
          b.author_id as blog_author_id,
          a.id        as author_id,
          a.username  as author_username,
          a.password  as author_password,
          a.email     as author_email,
          a.bio       as author_bio
        FROM blog b LEFT OUTER JOIN author a ON b.author_id=a.id
        WHERE b.id = #{id}
    </select>
    <resultMap id="blogResultWithAuthor" type="blog">
        <id property="id" column="blog_id"/>
        <result property="name" column="blog_title"/>
        <association property="author" column="blog_author_id" javaType="author" resultMap="authorResult"/>
    </resultMap>
    <resultMap id="authorResult" type="author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
    </resultMap>

和3.1裡的查詢一樣,都是查詢出blog和Author。但這個只查詢資料庫一次,也就是說實現了我們的關聯查詢。這幾行程式碼乍一看有點複雜,仔細分析一下就很明瞭了。

1> 首先看到的是select標籤,這個表示查詢。其中id表示對應的介面的方法名;resultMap的值是一個resultMap節點的id,這個表示select查詢結果的對映          方式。

2> select中間就是我熟悉的關聯查詢語句,這裡不做贅述

3> 然後就是resultMap所指向的節點blogResultWithAuthor。

  • resultMap節點的id就是唯一標識這個節點的,type表示這個resultMap最終對映成的實體類Blog。
  • id是主鍵對映,這個和mybatis快取有關。
  • result:
  • association:
  • authorResult同理。

可以看到控制檯列印的日誌:

2016-07-22 23:01:00,148 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Opening JDBC Connection
2016-07-22 23:01:11,017 DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 1893960929.
2016-07-22 23:01:19,281 DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@70e38ce1]
2016-07-22 23:01:27,018 DEBUG [com.test.mapper.dao.BlogMapper.selectBlogWithAuthor] - ==>  Preparing: SELECT b.id as blog_id, b.name as blog_title, b.author_id as blog_author_id, a.id as author_id, a.username as author_username, a.password as author_password, a.email as author_email, a.bio as author_bio FROM blog b LEFT OUTER JOIN author a ON b.author_id=a.id WHERE b.id = ? 
2016-07-22 23:01:29,697 DEBUG [com.test.mapper.dao.BlogMapper.selectBlogWithAuthor] - ==> Parameters: 1(Integer)
2016-07-22 23:01:30,091 DEBUG [com.test.mapper.dao.BlogMapper.selectBlogWithAuthor] - <==      Total: 1