MyBatis的事物
事物的概念
在Java語言數(shù)據(jù)庫框架中富岳,數(shù)據(jù)庫的事務(wù)管理都是非常重要的诈茧。
每個業(yè)務(wù)邏輯都是由一系列數(shù)據(jù)庫訪問完成的刨仑,這些訪問可能修改多條數(shù)據(jù)記錄努释,這一系列修改應(yīng)該是一個整體拷泽,絕對不能只修改其中的某幾條數(shù)據(jù)記錄疫鹊。
多個數(shù)據(jù)庫原子訪問應(yīng)該被綁定成一個整體,這就是事物司致。事務(wù)是一步或幾步操作組成的邏輯執(zhí)行單元拆吆,這些基本操作作為一個整體執(zhí)行單元,它們要么全部執(zhí)行脂矫,要么全部取消執(zhí)行枣耀,絕對不能僅僅執(zhí)行一部分。
一個用戶請求對應(yīng)一個業(yè)務(wù)邏輯方法庭再,一個邏輯方法往往具有邏輯上的原子性捞奕,此時應(yīng)使用事物。
例如:一個轉(zhuǎn)賬操作拄轻,對應(yīng)修改兩個賬戶余額颅围,這兩個賬戶的修改要么同時生效,要么同時取消恨搓,同時生效是轉(zhuǎn)賬成功院促,同時取消是轉(zhuǎn)賬失敗斧抱;但不可只修改其中一個賬戶常拓,那將破壞數(shù)據(jù)庫的完整性。
事物的四個特性
- 原子性:事物是應(yīng)用中最小的執(zhí)行單位夺姑,就如原子是自然界最小顆粒而不可以再分一樣墩邀,事物是應(yīng)用中不可再分的最小邏輯執(zhí)行體。
- 一致性:事物的執(zhí)行結(jié)果盏浙,必須使數(shù)據(jù)庫從一種一致性狀態(tài)眉睹,變?yōu)榱硪环N一致性狀態(tài)。當數(shù)據(jù)庫只包含事物成功提交的結(jié)果時废膘,數(shù)據(jù)庫處于一致性狀態(tài)竹海。當系統(tǒng)運行發(fā)生中斷,某個事物尚未完成而被迫中斷丐黄,而該未完成的事物對數(shù)據(jù)庫所做的修改已被寫入數(shù)據(jù)庫斋配,此時,數(shù)據(jù)庫處于不正確的狀態(tài)。一致性是通過原子性來保證的艰争。
- 隔離性:各個事物的執(zhí)行互不干擾坏瞄,任意一個事物的內(nèi)部操作對其他并發(fā)的事物,都是隔離的甩卓。
- 持續(xù)性:持續(xù)性也被稱為持久性鸠匀,指事物一旦提交,對數(shù)據(jù)所做的任何改變都要記錄到用就存儲器中逾柿,通常是保存到物理數(shù)據(jù)庫缀棍。
Transaction接口
對數(shù)據(jù)庫事物而言,應(yīng)具有:創(chuàng)建机错、提交爬范、回滾、關(guān)閉幾個動作弱匪,MyBatis的事物設(shè)計重點是org.apache.ibatis.transaction.Transaction接口青瀑,該接口源碼如下:
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
Transaction接口有兩個實現(xiàn)類:org.apache.ibatis.transaction.jdbc.JdbcTransaction和org.apache.ibatis.transaction.managed.ManagedTransaction。
所以MyBatis的事務(wù)管理有兩種形式:
- 使用JDBC的事物管理機制痢法,利用java.sql.Connection對象完成對事物的提交狱窘、回滾、關(guān)閉等操作财搁。
- 使用MANAGED的事物管理機制蘸炸,MyBatis自身不會去實現(xiàn)事務(wù)管理,而是讓容器如WebLogic尖奔、JBOSS等來實現(xiàn)對事物的管理搭儒。
事物的創(chuàng)建和使用
在使用MyBatis的時候,會在MyBatis的配置文件mybatis-config.xml中定義提茁,此處使用前文(http://www.reibang.com/p/063a5ca8874c)配置信息:
<environment id="mysql">
<!--指定事務(wù)管理的類型淹禾,這里簡單使用Java的JDBC的提交和回滾設(shè)置-->
<transactionManager type="JDBC"></transactionManager>
<!--dataSource 指連接源配置,POOLED是JDBC連接對象的數(shù)據(jù)源連接池的實現(xiàn)-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="****"></property>
</dataSource>
</environment>
<environment>元素定義了連接數(shù)據(jù)庫的信息茴扁,<transactionManager>子元素的type決定了使用什么類型的事物管理機制铃岔。
MyBatis的緩存
緩存的概述
在實際項目開發(fā)中,通常對數(shù)據(jù)庫查詢的性能要求很高峭火,MyBatis提供了查詢緩存來進行數(shù)據(jù)的緩存毁习,以達到提高查詢性能的要求。
MyBatis的查詢緩存分為一級緩存和二級緩存:
- 一級緩存是SqlSession級別的緩存卖丸。
- 二級緩存是mapper級別的緩存纺且,二級緩存是多個SqlSession共享的。
MyBatis通過緩存機制減輕數(shù)據(jù)壓力稍浆,提高數(shù)據(jù)庫性能载碌。
一級緩存
在操作數(shù)據(jù)庫時需要構(gòu)造SqlSession對象猜嘱,在對象中有一個HashMap用戶緩存數(shù)據(jù)。不同的SqlSession之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互相不影響的嫁艇。
一級緩存的作用是SqlSession范圍的朗伶,當同一個SqlSession中執(zhí)行兩次相同的sql語句時,第一次執(zhí)行完畢會將數(shù)據(jù)庫中查詢的數(shù)據(jù)寫到緩存(內(nèi)存)步咪,第二次查詢時會從緩存中獲取數(shù)據(jù)腕让,不再去底層數(shù)據(jù)庫查詢,提高查詢效率歧斟。
若SqlSession執(zhí)行了DML操作(insert、update和delete)偏形,并提交到數(shù)據(jù)庫静袖,MyBatis則會清空SqlSession中的一級緩存,目的是為了保證緩存中存儲的是最新的數(shù)據(jù)俊扭,避免臟讀現(xiàn)象队橙。
當一個SqlSession結(jié)束后,該SqlSession中的一級緩存也就不存在了萨惑。
一級緩存測試
項目代碼使用前文項目(http://www.reibang.com/p/063a5ca8874c)
現(xiàn)在數(shù)據(jù)庫的tb_user表中插入幾條數(shù)據(jù):
然后在項目的UserMapper.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.snow.dcl.mapper.UserMapper">
<!--插入用戶數(shù)據(jù)-->
<insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true">
insert into tb_user(name,sex,age) values (#{name},#{sex},#{age});
</insert>
<!--根據(jù)id查詢用戶-->
<select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User">
select * from tb_user where id=#{id};
</select>
<!--查詢所有用戶-->
<select id="selectAllUser" resultType="com.snow.dcl.domain.User">
select * from tb_user;
</select>
<!--根據(jù)id刪除用戶-->
<delete id="deleteUserById" parameterType="int">
delete from tb_user where id=#{id};
</delete>
</mapper>
在項目的java目錄右鍵解总,創(chuàng)建com.snow.dcl.mapper包,在該包中創(chuàng)建UserMapper.java接口類:
編寫如下程序:
public interface UserMapper {
//根據(jù)id查詢用戶
User selectUserById(Integer id);
//查詢所有用戶
List<User> selectAllUser();
//根據(jù)id刪除用戶
void deleteUserById(Integer id);
}
獲取mybatis-config.xml配置文件姐仅,根據(jù)配置文件創(chuàng)建SqlSessionFactory花枫,獲取SqlSession對象這一系列操作,每次都要使用掏膏,所以將其封裝在一個類文件中劳翰,在項目java目錄右鍵,創(chuàng)建com.snow.dcl.utils包馒疹,在該包下創(chuàng)建FactoryUtil.java類文件:
添加如下程序:
public class FactoryUtil {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
在項目的test目錄下的java目錄下創(chuàng)建OneLevelCacheTest.java測試類文件:
編寫如下程序:
public class OneLevelCacheTest {
public static void main(String[] args) {
OneLevelCacheTest oneLevelCacheTest = new OneLevelCacheTest();
oneLevelCacheTest.cacheOneTest();
}
public void cacheOneTest(){
SqlSession sqlSession = FactoryUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user);
User anotherUser = userMapper.selectUserById(1);
System.out.println(anotherUser);
sqlSession.close();
}
}
執(zhí)行測試程序OneLevelCacheTest1.java佳簸,可以在控制臺看到打印結(jié)果:
可以看到在第一次執(zhí)行查詢id為1的User對象時,執(zhí)行了一條select語句颖变,第二次執(zhí)行查詢id為1的User對象時生均,沒有執(zhí)行select語句,因為此時一級緩存中已經(jīng)緩存了id為1的User對象悼做,MyBatis直接從緩存中將User對象取出來疯特,并沒有再次去數(shù)據(jù)庫中查詢。
DML操作清空緩存
在項目的test目錄下的java目錄創(chuàng)建OneLevelCacheTest2測試類文件肛走,編寫如下代碼:
public class OneLevelCacheTest2 {
public static void main(String[] args) {
OneLevelCacheTest2 oneLevelCacheTest = new OneLevelCacheTest2();
oneLevelCacheTest.cacheOneTest();
}
public void cacheOneTest(){
SqlSession sqlSession = FactoryUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user);
userMapper.deleteUserById(7);
sqlSession.commit();
User anotherUser = userMapper.selectUserById(1);
System.out.println(anotherUser);
sqlSession.close();
}
}
執(zhí)行測試程序OneLevelCacheTest2.java漓雅,可以在控制臺看到打印結(jié)果:
可以看到在第一次執(zhí)行查詢id為1的User對象時,執(zhí)行了一條select語句,接下來執(zhí)行了一個delete操作邻吞,MyBatis為了保證緩存中存儲的是最新的數(shù)據(jù)组题,清空了一級緩存,所以第二次執(zhí)行查詢id為1的User對象時抱冷,又執(zhí)行了select語句崔列。
不同Session對象對一級緩存的影響
在項目的test目錄下的java目錄創(chuàng)建OneLevelCacheTest3測試類文件,編寫如下代碼:
public class OneLevelCacheTest3 {
public static void main(String[] args) {
OneLevelCacheTest3 oneLevelCacheTest = new OneLevelCacheTest3();
oneLevelCacheTest.cacheOneTest();
}
public void cacheOneTest(){
SqlSession sqlSession = FactoryUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user);
sqlSession.close();
sqlSession = FactoryUtil.getSqlSession();
userMapper = sqlSession.getMapper(UserMapper.class);
User anotherUser = userMapper.selectUserById(1);
System.out.println(anotherUser);
sqlSession.close();
}
}
執(zhí)行測試程序OneLevelCacheTest2.java旺遮,可以在控制臺看到打印結(jié)果:
可以看到在第一次執(zhí)行查詢id為1的User對象時赵讯,執(zhí)行了一條select語句,接下來調(diào)用了sqlSession.close()關(guān)閉了一級緩存耿眉,第二次執(zhí)行查詢id為1的User對象時边翼,一級緩存是一個新的對象,緩存中沒有緩存任何數(shù)據(jù)鸣剪,所以再次執(zhí)行了select語句组底。
二級緩存
使用二級緩存時,多個SqlSession使用同一個Mapper的sql語句去操作數(shù)據(jù)庫筐骇,得到的數(shù)據(jù)會存在二級緩存區(qū)域债鸡,它同樣是使用HashMap進行數(shù)據(jù)存儲。相比一級緩存SqlSession铛纬,二級緩存的范圍更大厌均,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的饺鹃。
二級緩存是多個SqlSession共享的莫秆,其作用域是mapper的同一個namespace。不同的SqlSession兩次執(zhí)行相同namespace下的sql語句悔详,且向sql中傳遞的參數(shù)也相同镊屎,即最終執(zhí)行相同的sql語句,則第一次執(zhí)行完畢會將數(shù)據(jù)庫中查詢的數(shù)據(jù)寫入緩存茄螃,第二次查詢時會從緩存中獲取數(shù)據(jù)缝驳,不再去底層數(shù)據(jù)庫查詢,提高效率归苍。
二級緩存測試
在mubatis-config.xml配置文件中開啟二級緩存夏伊,完整配置文件如下:
<configuration>
<!-- 指定Mybatis所用日志的具體實現(xiàn) -->
<settings>
<!--開啟二級緩存-->
<setting name="cacheEnabled" value="true"/>
<!--開啟日志-->
<setting name="logImpl" value="Log4J"/>
</settings>
<!--環(huán)境配置,連接的數(shù)據(jù)庫-->
<environments default="mysql">
<environment id="mysql">
<!--指定事務(wù)管理的類型吻氧,這里簡單使用Java的JDBC的提交和回滾設(shè)置-->
<transactionManager type="JDBC"></transactionManager>
<!--dataSource 指連接源配置溺忧,POOLED是JDBC連接對象的數(shù)據(jù)源連接池的實現(xiàn)-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"></property>
<property name="username" value="root"></property>
<property name="password" value="Password@123"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--告訴Mybatis持久化類的映射文件路徑-->
<mapper resource="mapping/UserMapper.xml"></mapper>
</mappers>
</configuration>
cacheEnabled默認為false咏连,設(shè)置為true表示開啟二級緩存。
在UserMapper.sml文件配置緩存相關(guān)參數(shù)鲁森,完整配置文件如下:
<?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.snow.dcl.mapper.UserMapper">
<!--開啟當前mapper的namespace下的二級緩存-->
<cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/>
<!--插入用戶數(shù)據(jù)-->
<insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true">
insert into tb_user(name,sex,age) values (#{name},#{sex},#{age});
</insert>
<!--根據(jù)id查詢用戶-->
<select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User">
select * from tb_user where id=#{id};
</select>
<!--查詢所有用戶-->
<select id="selectAllUser" resultType="com.snow.dcl.domain.User">
select * from tb_user;
</select>
<!--根據(jù)id刪除用戶-->
<delete id="deleteUserById" parameterType="int">
delete from tb_user where id=#{id};
</delete>
</mapper>
參數(shù)解釋:
- eviction:回收策略祟滴,默認為LRU,此外還有FIFO歌溉、SOFT垄懂、WEAK。
LRU:最近最少使用策略痛垛,移除最長時間不被使用的對象草慧。
FIFO:先進先出策略,按對象進入緩存的順序來移除匙头。
SOFT:軟引用策略冠蒋,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。
WEAK:弱引用策略乾胶,更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。 - flushInterval:刷新間隔時間朽寞,任意正整數(shù)识窿,單位毫秒,默認情況下是沒有刷新間隔脑融,緩存僅在調(diào)用語句時刷新喻频。
- size:緩存數(shù)目,任意正整數(shù)肘迎,要記住緩存的對象數(shù)目與運行環(huán)境的可用內(nèi)存資源數(shù)目甥温,默認1024。
- readOnly:只讀妓布,屬性為true或false姻蚓。true緩存會給所有調(diào)用者返回緩存對象的相同實例,對象不能修改匣沼,性能高狰挡。false緩存會返回緩存對象的拷貝(通過序列化),性能低释涛,但是安全加叁。默認為false。
使用二級緩存時唇撬,與查詢結(jié)果映射的Java對象必須實現(xiàn)java.io.Serializable接口的序列化和反序列化操作它匕,若存在父類,其成員都要實現(xiàn)序列化接口窖认。實現(xiàn)該接口的原因是為了對緩存數(shù)據(jù)進行序列化和反序列化操作豫柬,因為二級緩存數(shù)據(jù)存儲介質(zhì)多種多樣告希,不一定在內(nèi)存,可能是硬盤或者遠程服務(wù)器轮傍。
在項目的test目錄下的java目錄下創(chuàng)建TwoLevelCacheTest1.java測試類文件暂雹,編寫如下代碼:
public class TwoLevelCacheTest1 {
public static void main(String[] args) {
TwoLevelCacheTest1 twoLevelCacheTest = new TwoLevelCacheTest1();
twoLevelCacheTest.cacheTwoTest();
}
public void cacheTwoTest(){
SqlSession sqlSession = FactoryUtil.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectUserById(1);
System.out.println(user);
sqlSession.close();
sqlSession = FactoryUtil.getSqlSession();
userMapper = sqlSession.getMapper(UserMapper.class);
User anotherUser = userMapper.selectUserById(1);
System.out.println(anotherUser);
sqlSession.close();
}
}
執(zhí)行測試程序TwoLevelCacheTest.java,可以在控制臺看到打印結(jié)果:
可以看到在第一次執(zhí)行查詢id為1的User對象時创夜,執(zhí)行了一條select語句杭跪,接下來調(diào)用了sqlSession.close()關(guān)閉了一級緩存,第二次執(zhí)行查詢id為1的User對象時驰吓,一級緩存沒有任何對象涧尿,但因為啟用了二級緩存,第一次查詢的數(shù)據(jù)會緩存在二級緩存中檬贰,所以顯示命中二級緩存數(shù)據(jù)姑廉,不需要在執(zhí)行select語句。
在UserMapper.xml文件的select語句中設(shè)置useCache='false'翁涤,可以禁用當前select語句的二級緩存桥言,即每次都會查詢數(shù)據(jù)庫,默認為true葵礼,配置內(nèi)容如下:
<?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.snow.dcl.mapper.UserMapper">
<!--開啟當前mapper的namespace下的二級緩存-->
<cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/>
<!--插入用戶數(shù)據(jù)-->
<insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true">
insert into tb_user(name,sex,age) values (#{name},#{sex},#{age});
</insert>
<!--根據(jù)id查詢用戶-->
<select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User" useCache="false">
select * from tb_user where id=#{id};
</select>
<!--查詢所有用戶-->
<select id="selectAllUser" resultType="com.snow.dcl.domain.User">
select * from tb_user;
</select>
<!--根據(jù)id刪除用戶-->
<delete id="deleteUserById" parameterType="int">
delete from tb_user where id=#{id};
</delete>
</mapper>