最近學(xué)習(xí)MyBatis這個(gè)輕量型持久層框架幔崖,感覺入門很簡(jiǎn)單,但是深層次細(xì)節(jié)配置很多。本篇筆記從 配置文件->例子入門->MyBatis傳參和取參->查詢結(jié)果返回類型->關(guān)聯(lián)數(shù)據(jù)查詢->關(guān)聯(lián)數(shù)據(jù)查詢策略(是否啟用懶加載)->動(dòng)態(tài)SQL->一級(jí)緩存和二級(jí)緩存來進(jìn)行一次MyBatis探尋穆端。
一、概念簡(jiǎn)介
MyBatis 是一款優(yōu)秀的持久層框架传惠,它支持定制化 SQL迄沫、存儲(chǔ)過程以及高級(jí)映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集卦方。MyBatis 可以使用簡(jiǎn)單的 XML 或注解來配置和映射原生信息羊瘩,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對(duì)象)映射成數(shù)據(jù)庫(kù)中的記錄。
下載地址:https://github.com/mybatis/mybatis-3
MyBatis 文檔:http://www.mybatis.org/mybatis-3/zh/index.html
mybatis-spring 文檔:http://www.mybatis.org/spring/zh/index.html
開始總結(jié)前先導(dǎo)入Maven依賴
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
二盼砍、總配置文件
在classpath目錄下面建立一個(gè)MyBatis配置文件困后,配置文件可以分為兩個(gè)部分,第一部分?jǐn)?shù)據(jù)庫(kù)環(huán)境配置衬廷,第二部分加載映射文件摇予。
文件名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 文件 -->
<properties resource="jdbc.properties"></properties>
<environments default="debug">
<environment id="debug">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://10.22.70.2:3306/database_chao?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="xxx" />
<property name="password" value="xxx" />
</dataSource>
</environment>
<environment id="release">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</dataSource>
</environment>
</environments>
<mappers>
</mappers>
</configuration>
MyBatis 的配置文件包含了會(huì)深深影響 MyBatis 行為的設(shè)置(settings)和屬性(properties)信息吗跋。文檔的頂層結(jié)構(gòu)如下:
---configuration 配置
-------properties 屬性
-------settings 設(shè)置
-------typeAliases 類型別名
-------typeHandlers 類型處理器
-------plugins 插件
-------environments 環(huán)境
----------environment 環(huán)境變量
-------------transactionManager 事務(wù)管理器
-------------dataSource 數(shù)據(jù)源
-------------databaseIdProvider 數(shù)據(jù)庫(kù)廠商標(biāo)識(shí)
-------mappers 映射器
有關(guān) properties侧戴、settings、typeAliases跌宛、typeHandlers和plugins的配置及相關(guān)功能請(qǐng)參考MyBatis的官方文檔 http://www.mybatis.org/mybatis-3/zh/configuration.html
三酗宋、例子入門
1. 簡(jiǎn)單例子入門
① 建立POJO類
public class Grade {
private int gid;
private String gname;
}
② 創(chuàng)建POJO對(duì)象和Mysql數(shù)據(jù)的表之間的映射配置
[在POJO同在包下]創(chuàng)建名字為 GradeMapper.xml
的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="primary">
<select id="selectGrade" resultType="primary.Grade">
select * from grade where gid=#{id}
</select>
</mapper>
namespace
可以自己隨便定,不要重復(fù)
<select>
標(biāo)簽里面的 id
也可以自己隨便定疆拘,不要重復(fù)
③ 將映射配置加載到總配置文件中
在mybatis-config.xml
文件中的<mappers>
標(biāo)簽下加入如下代碼
<mappers>
<mapper resource="primary/GradeMapper.xml" />
</mappers>
④ 代碼測(cè)試
@Test
public void testSelectById() throws Exception {
//加載 配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//初始化session工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//1. 執(zhí)行SQL語句標(biāo)簽里面的 id為 映射配置里面的 namespace + 標(biāo)簽的id
//2. 參數(shù)為映射文件里面執(zhí)行sql語句需要的參數(shù), 這里是id
Object selectOne = sqlSession.selectOne("primary.selectGrade", 1);
//3. MyBatis將查詢數(shù)封裝成要求的結(jié)果返回類型后再返回
System.out.println(selectOne);
sqlSession.close();
inputStream.close();
}
2. 正規(guī)寫法 實(shí)現(xiàn)簡(jiǎn)單增刪改查功能
開發(fā)跟數(shù)據(jù)交互都是通過 Dao層蜕猫,MyBatis為我們Dao層開發(fā)做了很好的兼容,我們直接寫接口哎迄,然后用Mapper映射文件充當(dāng)實(shí)現(xiàn)類回右。
① 先寫接口
public interface GradeDao {
public int insertGrade(Grade grade);
public int updateGrade(Grade grade);
public int deleteGrade(int id);
public List<Grade> selectAll();
}
② 創(chuàng)建POJO對(duì)象和Mysql數(shù)據(jù)的表之間的映射配置
為了方便查看和加載配置文件,我們一般Mapper文件和對(duì)應(yīng)的Dao接口放在同一個(gè)包下漱挚。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<mapper namespace="com.ogemray.dao.GradeDao">
<insert id="insertGrade">
insert into grade (gname) value (#{gname})
</insert>
<update id="updateGrade">
update grade set gname=#{gname} where gid=#{gid}
</update>
<delete id="deleteGrade">
delete from grade where gid=#{id}
</delete>
<select id="selectAll" resultType="com.ogemray.entity.Grade">
select * from grade
</select>
</mapper>
配置文件有些要注意的地方
mapper 的 namespace 要是 對(duì)應(yīng) Dao接口的全名
SQL語句標(biāo)簽里面的 id 要是 Dao里面對(duì)應(yīng)方法的名字
③ 測(cè)試代碼
public class UpgradeTest {
private InputStream inputStream;
private SqlSession sqlSession;
@Before
public void beforeAction() throws Exception {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sessionFactory.openSession();
}
@After
public void afterAction() throws Exception {
sqlSession.commit();
sqlSession.close();
inputStream.close();
}
@Test
public void testSimpleInsert() {
Grade grade = new Grade("理科四班");
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
int count = mapper.insertGrade(grade);
System.out.println(count);
}
@Test
public void testSimpleUpdate() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
Grade grade = mapper.selectOneById(5);
int count = grade.setGname("理科二班");
mapper.updateGrade(grade);
}
@Test
public void testSimpleDelete() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
int count = mapper.deleteGrade(5);
}
@Test
public void testSimpleSelectAll() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
List<Grade> list = mapper.selectAll();
for (Grade grade : list) { System.out.println(grade); }
}
}
3. 得到新插入對(duì)象的id值
對(duì)于上面的 增 刪 更新后面返回的都是影響行數(shù)翔烁,很多時(shí)候數(shù)據(jù)庫(kù)主鍵自增長(zhǎng),但是我們要知道新插入對(duì)象對(duì)應(yīng)數(shù)據(jù)庫(kù)里面的id值旨涝,這個(gè)時(shí)候需要用到下面這兩個(gè)屬性蹬屹。
屬性 | 描述 |
---|---|
useGeneratedKeys | (僅對(duì) insert 和 update 有用)這會(huì)令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫(kù)內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)的自動(dòng)遞增字段),默認(rèn)值:false白华。 |
keyProperty | (僅對(duì) insert 和 update 有用)唯一標(biāo)記一個(gè)屬性慨默,MyBatis 會(huì)通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設(shè)置它的鍵值,默認(rèn):unset弧腥。如果希望得到多個(gè)生成的列厦取,也可以是逗號(hào)分隔的屬性名稱列表。 |
Mapper文件里面的標(biāo)簽做如下更改
<insert id="insertGrade"
parameterType="com.ogemray.entity.Grade"
useGeneratedKeys="true"
keyProperty="gid">
insert into grade (gname) value (#{gname})
</insert>
測(cè)試代碼
@Test
public void testSimpleInsert() {
Grade grade = new Grade("理科五班");
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
int count = mapper.insertGrade(grade);
System.out.println("影響行數(shù): " + count + " 更新后對(duì)象: " + grade);
}
控制臺(tái)輸出
影響行數(shù): 1 更新后對(duì)象: Grade [gid=8, gname=理科五班]
四鸟赫、參數(shù)傳遞 (多參傳遞)
1. 使用 arg 取參 arg0蒜胖、arg1 ...
Dao接口
public interface GradeDao {
public List<Grade> selectPartGradeByUseArg(int startId, int endId);
}
Mapper文件
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectPartGradeByUseArg" resultType="com.ogemray.entity.Grade">
select * from grade where gid >= #{arg0} and gid <= #{arg1}
</select>
</mapper>
測(cè)試代碼
@Test
public void testMultiParam1() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
List<Grade> list = mapper.selectPartGradeByUseArg(1, 2);
for (Grade grade : list) { System.out.println(grade); }
}
2. 使用 param 取參 param1消别、param2 ...
Dao接口
public interface GradeDao {
public List<Grade> selectPartGradeByUseParam(int startId, int endId);
}
Mapper文件
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectPartGradeByUseParam" resultType="com.ogemray.entity.Grade">
select * from grade where gid >= #{param1} and gid <= #{param2}
</select>
</mapper>
測(cè)試代碼
@Test
public void testMultiParam2() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
List<Grade> list = mapper.selectPartGradeByUseParam(1, 2);
for (Grade grade : list) { System.out.println(grade); }
}
3. 使用 @Param 注解傳參
這種方式比較常用,標(biāo)明參數(shù)名字台谢,代碼閱讀性強(qiáng)
Dao接口
public interface GradeDao {
public List<Grade> selectPartGradeUseAnno(@Param("startId") int startId,@Param("endId") int endId);
}
Mapper文件
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectPartGradeUseAnno" resultType="com.ogemray.entity.Grade">
select * from grade where gid >= #{startId} and gid <= #{endId}
</select>
</mapper>
測(cè)試代碼
@Test
public void testMultiParam3() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
List<Grade> list = mapper.selectPartGradeUseAnno(1, 2);
for (Grade grade : list) { System.out.println(grade); }
}
4. 使用 Map 傳參
Dao接口
public interface GradeDao {
public List<Grade> selectPartGradeUseMap(Map<String, Integer> map);
}
Mapper文件
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectPartGradeUseMap" resultType="com.ogemray.entity.Grade">
select * from grade where gid >= #{startId} and gid <= #{endId}
</select>
</mapper>
測(cè)試代碼
@Test
public void testMultiParam4() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
HashMap<String,Integer> map = new HashMap<String, Integer>();
map.put("startId", 1);
map.put("endId", 2);
List<Grade> list = mapper.selectPartGradeUseMap(map);
for (Grade grade : list) { System.out.println(grade); }
}
5. 使用集合傳參
Dao接口
public interface GradeDao {
public List<Grade> selectPartGradeUseCollection(List<Integer> gids);
}
Mapper文件
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectPartGradeUseCollection" resultType="com.ogemray.entity.Grade">
<!-- select * from grade where gid >= #{collection[0]} and gid <= #{collection[1]} -->
select * from grade where gid >= #{list[0]} and gid <= #{list[1]}
</select>
</mapper>
測(cè)試代碼
@Test
public void testMultiParam5() {
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
List<Integer> gids = new ArrayList<Integer>();
gids.add(1);
gids.add(2);
List<Grade> list = mapper.selectPartGradeUseCollection(gids);
for (Grade grade : list) { System.out.println(grade); }
}
最后說下映射文件中獲取參數(shù)的符號(hào)#{}和${}的區(qū)別
#{}
對(duì)應(yīng)的是 PreparedStatementd 對(duì)象來執(zhí)行 sql 語句
${}
對(duì)應(yīng)的是 Statement 對(duì)象來執(zhí)行 sql 語句
public interface GradeDao {
public List<Grade> selectPartGradeUse(@Param("tableName") String tableName,@Param("startId") int startId, @Param("endId") int endId);
}
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectPartGradeUse" resultType="com.ogemray.entity.Grade">
select * from ${tableName} where gid >= ${startId} and gid <= #{endId}
</select>
</mapper>
最后執(zhí)行的 SQL語句可以看出區(qū)別寻狂,也可以看出兩者的使用場(chǎng)景
select * from grade where gid >= 1 and gid <= ?
五、結(jié)果類型 resultType 和 resultMap
屬性 | 描述 |
---|---|
resultType | 從這條語句中返回的期望類型的類的完全限定名或別名朋沮。注意如果是集合情形蛇券,那應(yīng)該是集合可以包含的類型,而不能是集合本身樊拓。使用 resultType 或 resultMap纠亚,但不能同時(shí)使用。 |
resultMap | 外部 resultMap 的命名引用筋夏。結(jié)果集的映射是 MyBatis 最強(qiáng)大的特性蒂胞,對(duì)其有一個(gè)很好的理解的話,許多復(fù)雜映射的情形都能迎刃而解条篷。使用 resultMap 或 resultType骗随,但不能同時(shí)使用。 |
1. resultType 指定返回封裝好的Java對(duì)象
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectOneById_returnGrade" resultType="com.ogemray.entity.Grade">
select * from grade where gid = #{id}
</select>
</mapper>
//測(cè)試控制臺(tái)輸出
Grade [gid=1, gname=文科一班]
2. resultType 指定返回封裝Java對(duì)象的集合
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectAll_retuenList" resultType="com.ogemray.entity.Grade">
select * from grade
</select>
</mapper>
//測(cè)試控制臺(tái)輸出
[Grade [gid=1, gname=文科一班], Grade [gid=2, gname=文科二班]]
3. resultType 指定返回 Map 對(duì)象赴叹,key查詢出來的 column name value對(duì)應(yīng)值
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectOneById_returnMap" resultType="map">
select * from grade where gid = #{id}
</select>
</mapper>
//測(cè)試控制臺(tái)輸出
{gid=1, gname=文科一班}
4. resultType 指定返回 Map 對(duì)象鸿染,key值隨便指定,value為封裝好的java對(duì)象
//接口類, 需要用 @MapKey注解指定用哪個(gè)column字段作為key
public interface GradeDao {
@MapKey("gid")
public Map<Integer, Grade> selectAll_retuenMap();
}
//映射文件配置
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectAll_retuenMap" resultType="map">
select * from grade
</select>
</mapper>
//測(cè)試控制臺(tái)輸出
{1={gid=1, gname=文科一班}, 2={gid=2, gname=文科二班}}
5. resultMap 指定返回類型
當(dāng)在類里面屬性名和數(shù)據(jù)庫(kù)里面的column名字對(duì)不上的時(shí)候乞巧,可以用resultMap來指定字段匹配涨椒。
resultMap 子元素
-
id
– 一個(gè) ID 結(jié)果,標(biāo)記出作為 ID 的結(jié)果可以幫助提高整體性能 -
result
– 注入到字段或 JavaBean 屬性的普通結(jié)果 -
association
– 一個(gè)復(fù)雜類型的關(guān)聯(lián)绽媒,許多結(jié)果將包裝成這種類型- 嵌套結(jié)果映射 – 關(guān)聯(lián)可以指定為一個(gè)
resultMap
元素蚕冬,或者引用一個(gè)
- 嵌套結(jié)果映射 – 關(guān)聯(lián)可以指定為一個(gè)
-
collection
– 一個(gè)復(fù)雜類型的集合- 嵌套結(jié)果映射 – 集合可以指定為一個(gè)
resultMap
元素,或者引用一個(gè)
- 嵌套結(jié)果映射 – 集合可以指定為一個(gè)
resultMap 屬性
屬性 | 描述 |
---|---|
id |
當(dāng)前命名空間中的一個(gè)唯一標(biāo)識(shí)些椒,用于標(biāo)識(shí)一個(gè)result map |
type |
類的完全限定名, 或者一個(gè)類型別名 |
autoMapping |
如果設(shè)置這個(gè)屬性播瞳,MyBatis將會(huì)為這個(gè)ResultMap開啟或者關(guān)閉自動(dòng)映射。這個(gè)屬性會(huì)覆蓋全局的屬性 autoMappingBehavior免糕。默認(rèn)值為:unset。 |
例如下面實(shí)體類和實(shí)體類在數(shù)據(jù)庫(kù)映射字段對(duì)比
//映射配置文件中
<mapper namespace="com.ogemray.dao.StudentDao">
<select id="selectAll" resultMap="studentMap">
select * from student
</select>
<resultMap type="com.ogemray.entity.Student" id="studentMap">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="birthday" column="birthday"/>
</resultMap>
</mapper>
如上面代碼忧侧,通過 <resultMap>
標(biāo)簽來指定實(shí)體類里面的屬性和映射數(shù)據(jù)表里面字段一一對(duì)應(yīng)關(guān)系石窑,兩者一樣的可以省略指定。然后通過 <select>
標(biāo)簽里面的 resultMap
屬性來引用提前設(shè)置好的 <resultMap>
標(biāo)簽蚓炬。
六松逊、resultMap 實(shí)現(xiàn)關(guān)聯(lián)查詢
MyBatis框架也可以實(shí)現(xiàn)像Hibernate那樣的關(guān)聯(lián)查詢,只不過是需要自己手動(dòng)去配置肯夏。
在開始之前经宏,先介紹下涉及到關(guān)聯(lián)查詢的兩個(gè)標(biāo)簽 <association>
和 <collection>
里面的屬性字段
屬性 | 描述 |
---|---|
property | 映射到列結(jié)果的字段或?qū)傩?/td> |
javaType | 一個(gè) Java 類的完全限定名犀暑,或一個(gè)類型別名 |
column | 數(shù)據(jù)庫(kù)的列名,注意: 要處理復(fù)合主鍵烁兰,你可以指定多個(gè)列名通過 column="{prop1=col1,prop2=col2}" 這種語法來傳遞給嵌套查詢語句耐亏。這會(huì)引起 prop1 和 prop2 以參數(shù)對(duì)象形式來設(shè)置給目標(biāo)嵌套查詢語句。 |
select | 另外一個(gè)映射語句的 ID沪斟,可以加載這個(gè)屬性映射需要的復(fù)雜類型广辰。獲取的在列屬性中指定的列的值將被傳遞給目標(biāo) select 語句作為參數(shù)。 select 注 意 : 要處理復(fù)合主鍵主之,你可以指定多個(gè)列名通過 column= " {prop1=col1,prop2=col2} " 這種語法來傳遞給嵌套查詢語句择吊,這會(huì)引起 prop1 和 prop2 以參數(shù)對(duì)象形式來設(shè)置給目標(biāo)嵌套查詢語句。 |
下面通過Student類和Grade類來建立關(guān)系進(jìn)行關(guān)聯(lián)查詢示例
1. 通過連級(jí)方式實(shí)現(xiàn)關(guān)聯(lián)查詢
<mapper namespace="com.ogemray.dao.StudentDao">
<select id="primaryCorrelationQueryById" resultMap="studentMap2">
select
s.sid sid,
s.sname sname,
s.birthday birthday,
g.gid gid,
g.gname gname
from
student s, grade g
where
s.sid = #{id} and s.sid = g.gid
</select>
<resultMap type="com.ogemray.entity.Student" id="studentMap2">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="birthday" column="birthday"/>
<!-- 通過級(jí)聯(lián)方法, 實(shí)現(xiàn)關(guān)聯(lián)查詢 -->
<result property="grade.gid" column="gid"/>
<result property="grade.gname" column="gname"/>
</resultMap>
</mapper>
2. 使用 <association>
標(biāo)簽實(shí)現(xiàn)關(guān)聯(lián)查詢
<mapper namespace="com.ogemray.dao.StudentDao">
<select id="correlationQueryById" resultMap="studentMap3">
select
s.sid sid,
s.sname sname,
s.birthday birthday,
g.gid gid,
g.gname gname
from
student s, grade g
where
s.sid = #{id} and s.sid = g.gid
</select>
<resultMap type="com.ogemray.entity.Student" id="studentMap3">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="birthday" column="birthday"/>
<!-- 通過子標(biāo)簽 association, 實(shí)現(xiàn)關(guān)聯(lián)查詢 -->
<association property="grade" javaType="com.ogemray.entity.Grade">
<id property="gid" column="grade_id"/>
<result property="gname" column="gname"/>
</association>
</resultMap>
</mapper>
3. 使用 <association>
標(biāo)簽實(shí)現(xiàn)分步關(guān)聯(lián)查詢
GradeMapper.xml 中
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="selectOneById" resultType="com.ogemray.entity.Grade">
select * from grade where gid = #{id}
</select>
</mapper>
StudentMapper.xml 中
<mapper namespace="com.ogemray.dao.StudentDao">
<select id="substepCorrelationQueryById" resultMap="studentMap4">
select * from student where sid = #{id}
</select>
<resultMap type="com.ogemray.entity.Student" id="studentMap4">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="birthday" column="birthday"/>
<association property="grade"
select="com.ogemray.dao.GradeDao.selectOneById"
column="grade_Id">
</association>
</resultMap>
</mapper>
//分別執(zhí)行SQL語句
DEBUG ==> Preparing: select * from student where sid = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG ====> Preparing: select * from grade where gid = ?
DEBUG ====> Parameters: 1(Integer)
4. 使用 <collection>
標(biāo)簽實(shí)現(xiàn) 集合關(guān)聯(lián)查詢
<collection> 里屬性 |
描述 |
---|---|
property |
集合對(duì)應(yīng)屬性名 |
ofType |
集合包含元素類型 |
GradeMapper.xml 中
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="correlationQueryById" resultMap="gradeMap1">
select
g.gid gid,
g.gname gname,
s.sid sid,
s.sname sname,
s.birthday birthday
from
grade g, student s
where
g.gid = #{id} and g.gid = s.grade_id
</select>
<resultMap type="com.ogemray.entity.Grade" id="gradeMap1">
<id property="gid" column="gid"/>
<result property="gname" column="gname"/>
<collection property="students" ofType="com.ogemray.entity.Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="birthday" column="birthday"/>
</collection>
</resultMap>
</mapper>
5. 使用 <collection>
標(biāo)簽實(shí)現(xiàn) 分步 集合關(guān)聯(lián)查詢
StudentMapper.xml 中
<mapper namespace="com.ogemray.dao.StudentDao">
<select id="selectStudentsByGradeId" resultMap="resultMap1">
select * from student where grade_id = #{gid}
</select>
<resultMap type="com.ogemray.entity.Student" id="resultMap1">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="birthday" column="birthday"/>
</resultMap>
</mapper>
GradeMapper.xml 中
<mapper namespace="com.ogemray.dao.GradeDao">
<select id="substepCorrelationQueryById" resultMap="gradeMap2">
select * from grade where gid = #{id}
</select>
<resultMap type="com.ogemray.entity.Grade" id="gradeMap2">
<id property="gid" column="gid"/>
<result property="gname" column="gname"/>
<collection property="students"
select="com.ogemray.dao.StudentDao.selectStudentsByGradeId"
column="gid">
<id property="id" column="sid" />
<result property="name" column="sname" />
<result property="birthday" column="birthday" />
</collection>
</resultMap>
</mapper>
//分別執(zhí)行SQL語句
DEBUG ==> Preparing: select * from grade where gid = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG ====> Preparing: select * from student where grade_id = ?
DEBUG ====> Parameters: 1(Integer)
七槽奕、在分步查詢的基礎(chǔ)上配置懶加載
懶加載的概念几睛,了解過Hibernate的肯定都不陌生,Hibernate中涉及到關(guān)聯(lián)查詢的時(shí)候粤攒,懶加載是默認(rèn)就開啟著的枉长,懶加載就是在關(guān)聯(lián)查詢中,真正需要用到關(guān)聯(lián)對(duì)象的時(shí)候琼讽,才發(fā)起sql語句從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)必峰,從而實(shí)現(xiàn)提升數(shù)據(jù)庫(kù)性能的目的。
Mybatis作為一個(gè)優(yōu)秀的ORM框架當(dāng)然也支持懶加載钻蹬,和Hibernate不同是吼蚁,它默認(rèn)情況下是禁止了懶加載的,要使用懶加載需要手動(dòng)的開啟问欠,開啟的方法就是配置兩個(gè)全局變量:lazyLoadingEnabled設(shè)置為true肝匆,aggressiveLazyLoading設(shè)置為false
設(shè)置參數(shù) | 描述 | 有效值 | 默認(rèn)值 |
---|---|---|---|
lazyLoadingEnabled | 延遲加載的全局開關(guān)。當(dāng)開啟時(shí)顺献,所有關(guān)聯(lián)對(duì)象都會(huì)延遲加載旗国。 特定關(guān)聯(lián)關(guān)系中可通過設(shè)置fetchType屬性來覆蓋該項(xiàng)的開關(guān)狀態(tài) | true/false | false |
aggressiveLazyLoading | 當(dāng)開啟時(shí),任何方法的調(diào)用都會(huì)加載該對(duì)象的所有屬性注整。否則能曾,每個(gè)屬性會(huì)按需加載 | true/false | false (true in ≤3.4.1) |
<?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>
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
...
</configuration>
拿上面第六部分的 3 和 5分別用 <association>
標(biāo)簽和 <collection>
標(biāo)簽實(shí)現(xiàn)的分步查詢來做實(shí)驗(yàn)
1. 在 <association>
標(biāo)簽中實(shí)現(xiàn)懶加載
StudentMapper.xml 中
<mapper namespace="com.ogemray.dao.StudentDao">
...
<resultMap type="com.ogemray.entity.Student" id="studentMap4">
...
<association property="grade"
select="com.ogemray.dao.GradeDao.selectOneById"
column="grade_Id">
</association>
</resultMap>
</mapper>
執(zhí)行測(cè)試代碼
StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student = mapper.substepCorrelationQueryById(1);
System.out.println(student.getName());
System.out.println(student.getGrade().toString());
控制臺(tái)輸出
DEBUG ==> Preparing: select * from student where sid = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Mike
DEBUG ==> Preparing: select * from grade where gid = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Grade [gid=1, gname=文科一班]
2. 在 <collection>
標(biāo)簽中實(shí)現(xiàn)懶加載
GradeMapper.xml 中
<mapper namespace="com.ogemray.dao.GradeDao">
...
<resultMap type="com.ogemray.entity.Grade" id="gradeMap2">
...
<collection property="students"
select="com.ogemray.dao.StudentDao.selectStudentsByGradeId"
column="gid">
...
</collection>
</resultMap>
</mapper>
執(zhí)行測(cè)試代碼
GradeDao mapper = sqlSession.getMapper(GradeDao.class);
Grade grade = mapper.substepCorrelationQueryById(1);
System.out.println(grade.getGname());
System.out.println(grade.getStudents().size());
控制臺(tái)輸出
DEBUG ==> Preparing: select * from grade where gid = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
文科一班
DEBUG ==> Preparing: select * from student where grade_id = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 2
2
3. 設(shè)置 fetchType
屬性來配置單個(gè)關(guān)聯(lián)查詢的加載策略
在 <association>
標(biāo)簽和 <collection>
標(biāo)簽都都有 fetchType
這么個(gè)屬性,他是用來指定該關(guān)聯(lián)在查詢時(shí)是否啟用懶加載肿轨,本身是可選值寿冕,但是設(shè)定了之后將會(huì)取代全局的 lazyLoadingEnabled
設(shè)置。
有效值為 lazy
和 eager
八椒袍、MyBatis 動(dòng)態(tài) SQL
MyBatis 的強(qiáng)大特性之一便是它的動(dòng)態(tài) SQL驼唱。如果你有使用 JDBC 或其它類似框架的經(jīng)驗(yàn),你就能體會(huì)到根據(jù)不同條件拼接 SQL 語句的痛苦驹暑。例如拼接時(shí)要確保不能忘記添加必要的空格玫恳,還要注意去掉列表最后一個(gè)列名的逗號(hào)辨赐。利用動(dòng)態(tài) SQL 這一特性可以徹底擺脫這種痛苦。
動(dòng)態(tài) SQL 元素和 JSTL 或基于類似 XML 的文本處理器相似京办。在 MyBatis 之前的版本中掀序,有很多元素需要花時(shí)間了解。MyBatis 3 大大精簡(jiǎn)了元素種類臂港,現(xiàn)在只需學(xué)習(xí)原來一半的元素便可森枪。MyBatis 采用功能強(qiáng)大的基于 OGNL 的表達(dá)式來淘汰其它大部分元素。
1. if
public interface StudentDao {
public List<Student> selectStudentsByName(@Param("name") String name);
}
<mapper namespace="com.ogemray.dao.StudentDao">
<select id="selectStudentsByName" resultMap="resultMap1">
select * from student where 1=1
<if test="name != null">
and sname like #{name}
</if>
</select>
</mapper>
需要注意以下兩點(diǎn)
① 在接口中加@Param注解审孽,以防止以下異常 ReflectionException: There is no getter for property named...
② if標(biāo)簽里的test屬性里是用的OGNL表達(dá)式县袱,這是apache下的一個(gè)標(biāo)簽,用法類似jstl佑力,但有些小差別式散,具體的內(nèi)容可以在ognl官網(wǎng)上查詢,這里強(qiáng)調(diào)一點(diǎn)打颤,有些符號(hào)在xml文件里寫的時(shí)候暴拄,屬于特殊符號(hào),不能直接使用编饺,我們可以在w3cschool里查http://www.w3school.com.cn/tags/html_ref_entities.html
2. choose, when, otherwise
<select id="selectStudentsByName" resultMap="resultMap1">
select * from student where 1=1
<choose>
<when test="name != null">
and sname like #{name}
</when>
<otherwise>
and sid = 1
</otherwise>
</choose>
</select>
相當(dāng)于平時(shí)用到的 if (condition) { } else { }
判斷語句
3. where
<select id="selectStudentsByName" resultMap="resultMap1">
select * from student
<where>
<if test="name != null">
sname like #{name}
</if>
</where>
</select>
容易實(shí)現(xiàn)動(dòng)態(tài)添加 where 語句
但是遇到 where 標(biāo)簽里面有多個(gè)條件語句該怎么辦呢? 下面來看看 tirm 標(biāo)簽
4. trim
<select id="selectStudentsByName" resultMap="resultMap1">
select * from student
<where>
<trim prefixOverrides="and">
<if test="name != null">
and sname like #{name}
</if>
<if test="name != null">
and sid = 1
</if>
</trim>
</where>
</select>
最終生成的 SQL語句
select * from student WHERE sname like ? and sid = 1
trim 標(biāo)簽可以動(dòng)態(tài)對(duì)一段語句的首尾進(jìn)行操作乖篷,下面來看下 trim標(biāo)簽里面的其他屬性:
prefix:加前綴
prefixOverrides:匹配的前綴去掉
suffix:加后綴
suffixOverrides:匹配的后綴去掉
5. set
public interface StudentDao {
public int updateStudent(@Param("student") Student student);
}
<update id="updateStudent">
update student
<set>
<if test="student.name != null">sname = #{student.name}</if>
</set>
<where>
<if test="student.id != 0">sid = #{student.id}</if>
</where>
</update>
利用 set 標(biāo)簽可以實(shí)現(xiàn)動(dòng)態(tài)更新
6. foreach
平時(shí)開發(fā)中我們會(huì)遇到下面這樣的查詢語句
select * from student where sid in (1, 2, 3)
這個(gè)時(shí)候可以用 foreach 標(biāo)簽做到這樣的效果
public interface StudentDao {
public List<Student> selectPartStudent(@Param("sids") Integer[] sids);
}
<select id="selectPartStudent" resultMap="resultMap1">
select * from student where sid in
<foreach collection="sids"
item="id"
separator=","
open="("
close=")">
#{id}
</foreach>
</select>
最終生成的 SQL 語句
select * from student where sid in ( ? , ? , ? )
對(duì)上面 foreach 標(biāo)簽里面的幾個(gè)屬性做下解釋
collection="ids":接口上傳過來的數(shù)值或list集合或者map集合都可以
item="id":設(shè)定遍歷集合或數(shù)組里的每一個(gè)值的迭代變量
separator=",": 因?yàn)橐獦?gòu)造出 (1,2,3)這種樣子的字符串,設(shè)定中間的分隔符
open="(": 因?yàn)橐獦?gòu)造出 (1,2,3)這種樣子的字符串透且,設(shè)定前綴的符號(hào)(
close=")":因?yàn)橐獦?gòu)造出 (1,2,3)這種樣子的字符串撕蔼,設(shè)計(jì)結(jié)尾的后綴)
index:還有這個(gè)屬性,數(shù)組或list集合的時(shí)候秽誊,設(shè)置索引變量鲸沮,如果是Map集合就是map的key的迭代變量,這里的例子用不著這個(gè)锅论。
7. bind
這個(gè)標(biāo)簽作用就是將OGNL標(biāo)簽里的值讼溺,進(jìn)行二次加工,在綁定到另一個(gè)變量里最易,供其他標(biāo)簽使用怒坯。
例如在用到模糊查詢時(shí)
方案一
public interface StudentDao {
public List<Student> selectStudentsByName(@Param("name") String name);
}
<select id="selectStudentsByName" resultMap="resultMap1">
select * from student where sname like #{name}
</select>
這時(shí)在調(diào)用的時(shí)候需要這么來寫
mapper.selectStudentsByName("%m%");
方案二
用 bind 標(biāo)簽來實(shí)現(xiàn)就簡(jiǎn)單多了
<select id="selectStudentsByName" resultMap="resultMap1">
<bind name="_name" value="'%' + name + '%'"></bind>
select * from student where sname like #{_name}
</select>
這個(gè)時(shí)候調(diào)用就方便多了,模糊查詢的效果同上面第一種方案一樣
mapper.selectStudentsByName("m");
九耘纱、MyBatis一級(jí)緩存
MyBatis 包含一個(gè)非常強(qiáng)大的查詢緩存特性敬肚,它可以非常方便地配置和定制。MyBatis 3 中的緩存實(shí)現(xiàn)的很多改進(jìn)都已經(jīng)實(shí)現(xiàn)了束析,使得它更加強(qiáng)大而且易于配置。
Mybatis 和 Hibernate一樣憎亚,也有一級(jí)和二級(jí)緩存员寇,同樣默認(rèn)開啟的只有一級(jí)緩存弄慰,二級(jí)緩存也需要手動(dòng)配置開啟,我們先看看一級(jí)緩存蝶锋。
一級(jí)緩存又被稱為 session 級(jí)別的緩存陆爽,mybatis一直默認(rèn)是開啟的,每個(gè)與數(shù)據(jù)庫(kù)的連接會(huì)話都有各自自己的緩存扳缕,這些一級(jí)緩存之間是不能通信的慌闭,是相互獨(dú)立的緩存空間!
總結(jié): 一級(jí)緩存其實(shí)就是一個(gè) Map躯舔,一個(gè) session 對(duì)應(yīng)一個(gè) Map驴剔,所以說兩個(gè)不同的 session 之間的一級(jí)緩存不共享,Map 里的 key 就是主鍵 id粥庄。所以查詢對(duì)象的時(shí)候丧失,先查緩存,緩存的 Map 對(duì)象中找不到時(shí)才會(huì)發(fā)送SQL語句惜互,查詢出來后會(huì)放到對(duì)應(yīng)的緩存 Map 里布讹。通過調(diào)用 session.clearCache()
可以用來清空緩存,同時(shí)增刪改也會(huì)刷新緩存训堆。
十描验、MyBatis二級(jí)緩存
Mybatis默認(rèn)情況下二級(jí)緩存是關(guān)閉的,需要手工的配置開啟坑鱼,在開啟之前膘流,我們先說說二級(jí)緩存的基本知識(shí)點(diǎn):
- 二級(jí)緩存又稱為全局緩存,它是基于 namespace 級(jí)別的緩存姑躲,一個(gè)名稱空間對(duì)應(yīng)一個(gè)二級(jí)緩存睡扬,也就是說一般情況下同一個(gè)映射文件中的查詢都共享一個(gè)共同的二級(jí)緩存空間。
- 一級(jí)緩存的生命周期隨著一次會(huì)話 session 的關(guān)閉而清空黍析,開啟二級(jí)緩存的情況下卖怜,一級(jí)緩存里的數(shù)據(jù),在清空或者提交之前會(huì)轉(zhuǎn)存到二級(jí)緩存的空間中繼續(xù)存在阐枣。
- 當(dāng)一次會(huì)話 sqlsession 的緩存里如果存放著兩個(gè)不同類型的對(duì)象马靠,比如 Grade 和 Student 對(duì)象,當(dāng)一級(jí)緩存清空之前蔼两,開起二級(jí)緩存的情況下甩鳄,它們兩個(gè)對(duì)象會(huì)分別存入各自的名稱空間的二級(jí)緩存空間中。直白的說就是一級(jí)緩存中兩個(gè)對(duì)象是放在同一Map對(duì)象(緩存就是Map對(duì)象)额划,在二級(jí)緩存中兩個(gè)對(duì)象是分別放在兩個(gè)獨(dú)立的Map對(duì)象里的(各自的緩存空間里)妙啃。
開啟二級(jí)緩存步驟
① 設(shè)置全局變量 cacheEnabled 設(shè)置為 true
設(shè)置參數(shù) | 描述 | 有效值 | 默認(rèn)值 |
---|---|---|---|
cacheEnabled | 全局地開啟或關(guān)閉配置文件中的所有映射器已經(jīng)配置的任何緩存 | true/false | true |
<?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>
<settings>
...
<!-- 開啟二級(jí)緩存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
...
</configuration>
② 在映射文件中添加一個(gè)標(biāo)簽<cache/>
<?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.ogemray.dao.StudentDao">
<cache />
...
</mapper>
<cache/>這個(gè)簡(jiǎn)單語句的效果如下:
- 映射語句文件中的所有 select 語句將會(huì)被緩存。
- 映射語句文件中的所有 insert,update 和 delete 語句會(huì)刷新緩存揖赴。
- 緩存會(huì)使用 Least Recently Used(LRU馆匿,最近最少使用的)算法來收回。
- 根據(jù)時(shí)間表(比如 no Flush Interval燥滑,沒有刷新間隔)渐北,緩存不會(huì)以任何時(shí)間順序來刷新。
- 緩存會(huì)存儲(chǔ)列表集合或?qū)ο?無論查詢方法返回什么)的 1024 個(gè)引用铭拧。
- 緩存會(huì)被視為是 read/write(可讀/可寫)的緩存赃蛛,意味著對(duì)象檢索不是共享的,而且可以安全地被調(diào)用者修改搀菩,而不干擾其他調(diào)用者或線程所做的潛在修改呕臂。
<cache/>這個(gè)標(biāo)簽中還有很多與緩存有關(guān)的屬性:
eviction:可用的收回策略有:
- LRU – 最近最少使用的,移除最長(zhǎng)時(shí)間不被使用的對(duì)象秕磷。
- FIFO – 先進(jìn)先出诵闭,按對(duì)象進(jìn)入緩存的順序來移除它們。
- SOFT – 軟引用澎嚣,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對(duì)象疏尿。
- WEAK – 弱引用,更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對(duì)象易桃。
默認(rèn)的是 LRU褥琐。
flushInterval:刷新間隔,可以被設(shè)置為任意的正整數(shù)晤郑,而且它們代表一個(gè)合理的毫秒形式的時(shí)間段敌呈。默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔造寝,緩存僅僅調(diào)用語句時(shí)刷新磕洪。
size:引用數(shù)目,可以被設(shè)置為任意正整數(shù)诫龙,要記住你緩存的對(duì)象數(shù)目和你運(yùn)行環(huán)境的可用內(nèi)存資源數(shù)目析显。默認(rèn)值是 1024。
readOnly:(只讀)屬性可以被設(shè)置為 true 或 false签赃。只讀的緩存會(huì)給所有調(diào)用者返回緩存對(duì)象的相同實(shí)例谷异,因此這些對(duì)象不能被修改,這提供了很重要的性能優(yōu)勢(shì)锦聊〈踵冢可讀寫的緩存會(huì)返回緩存對(duì)象的拷貝(通過序列化) 。這會(huì)慢一些孔庭,但是安全尺上,因此默認(rèn)是 false。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個(gè)更高級(jí)的配置創(chuàng)建了一個(gè) FIFO 緩存,并每隔 60 秒刷新尖昏,存數(shù)結(jié)果對(duì)象或列表的 512 個(gè)引用仰税,而且返回的對(duì)象被認(rèn)為是只讀的构资,因此在不同線程中的調(diào)用者之間修改它們會(huì)導(dǎo)致沖突抽诉。
③ Mybatis的二級(jí)緩存使用的序列化接口,所以吐绵,我們要使用二級(jí)緩存迹淌,我們的JavaBean就必須實(shí)現(xiàn)序列化接口
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
...
}
代碼測(cè)試
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
Student student1 = mapper1.selectOneById(1);
System.out.println(student1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
Student student2 = mapper2.selectOneById(1);
System.out.println(student2);
sqlSession2.close();
DEBUG ==> Preparing: select * from student where sid = ?
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Student [id=1, name=Mike, birthday=Mon Nov 05 00:00:00 CST 2018, grade=null]
DEBUG Cache Hit Ratio [com.ogemray.dao.StudentDao]: 0.5
Student [id=1, name=Mike, birthday=Mon Nov 05 00:00:00 CST 2018, grade=null]
從上面可以看出,用兩個(gè)不同的 session 查詢同個(gè)對(duì)象只發(fā)送一次 SQL語句己单。注意唉窃,在用另一個(gè) session 查詢前先將上個(gè) session 關(guān)閉,這樣才會(huì)將一級(jí)緩存里面的數(shù)據(jù)放到二級(jí)緩存里面纹笼。
注意
① 設(shè)置 useCache=false
可以禁用當(dāng)前 select 語句的二級(jí)緩存纹份,即每次查詢都會(huì)發(fā)出 sql 去查詢,默認(rèn)情況是 true廷痘,即該sql使用二級(jí)緩存蔓涧。
<select id="selectOneById" resultMap="resultMap1" useCache="false">
select * from student where sid = #{id}
</select>
② 清空緩存 flushCache
屬性
- flushCache默認(rèn)為false,表示任何時(shí)候語句被調(diào)用笋额,都不會(huì)去清空本地緩存和二級(jí)緩存元暴。
- useCache默認(rèn)為true,表示會(huì)將本條語句的結(jié)果進(jìn)行二級(jí)緩存兄猩。
- 在insert茉盏、update、delete語句時(shí): flushCache默認(rèn)為true枢冤,表示任何時(shí)候語句被調(diào)用鸠姨,都會(huì)導(dǎo)致本地緩存和二級(jí)緩存被清空。 useCache屬性在該情況下沒有淹真。例如如果 update 的時(shí)候如果 flushCache="false"讶迁,則當(dāng)你更新后,查詢的數(shù)據(jù)數(shù)據(jù)還是老的數(shù)據(jù)趟咆。
如果沒有去配置flushCache添瓷、useCache踏幻,那么默認(rèn)是啟用緩存的
<select id="selectOneById"
resultMap="resultMap1"
useCache="true"
flushCache="false">
select * from student where sid = #{id}
</select>