今天學習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。 |
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", "qweqwe@qq.com", "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", "qweqwe@qq.com", "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, "qweqwe@qq.com", "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。 |
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