MyBatis的事物
事物的概念
在Java語言數據庫框架中,數據庫的事務管理都是很是重要的。
每一個業務邏輯都是由一系列數據庫訪問完成的,這些訪問可能修改多條數據記錄,這一系列修改應該是一個總體,絕對不能只修改其中的某幾條數據記錄。
多個數據庫原子訪問應該被綁定成一個總體,這就是事物。事務是一步或幾步操做組成的邏輯執行單元,這些基本操做做爲一個總體執行單元,它們要麼所有執行,要麼所有取消執行,絕對不能僅僅執行一部分。
一個用戶請求對應一個業務邏輯方法,一個邏輯方法每每具備邏輯上的原子性,此時應使用事物。
例如:一個轉帳操做,對應修改兩個帳戶餘額,這兩個帳戶的修改要麼同時生效,要麼同時取消,同時生效是轉帳成功,同時取消是轉帳失敗;但不可只修改其中一個帳戶,那將破壞數據庫的完整性。
事物的四個特性
1.原子性:事物是應用中最小的執行單位,就如原子是天然界最小顆粒而不能夠再分同樣,事物是應用中不可再分的最小邏輯執行體。
2.一致性:事物的執行結果,必須使數據庫從一種一致性狀態,變爲另外一種一致性狀態。當數據庫只包含事物成功提交的結果時,數據庫處於一致性狀態。當系統運行發生中斷,某個事物還沒有完成而被迫中斷,而該未完成的事物對數據庫所作的修改已被寫入數據庫,此時,數據庫處於不正確的狀態。一致性是經過原子性來保證的。
3.隔離性:各個事物的執行互不干擾,任意一個事物的內部操做對其餘併發的事物,都是隔離的。
4.持續性:持續性也被稱爲持久性,指事物一旦提交,對數據所作的任何改變都要記錄到用就存儲器中,一般是保存到物理數據庫。
Transaction接口
對數據庫事物而言,應具備:建立、提交、回滾、關閉幾個動做,MyBatis的事物設計重點是org.apache.ibatis.transaction.Transaction接口,該接口源碼以下:
java
1 public interface Transaction { 2 Connection getConnection() throws SQLException; 3 4 void commit() throws SQLException; 5 6 void rollback() throws SQLException; 7 8 void close() throws SQLException; 9 10 Integer getTimeout() throws SQLException; 11 } 12
Transaction接口有兩個實現類:org.apache.ibatis.transaction.jdbc.JdbcTransaction和org.apache.ibatis.transaction.managed.ManagedTransaction。
因此MyBatis的事務管理有兩種形式:
1.使用JDBC的事物管理機制,利用java.sql.Connection對象完成對事物的提交、回滾、關閉等操做。
2.使用MANAGED的事物管理機制,MyBatis自身不會去實現事務管理,而是讓容器如WebLogic、JBOSS等來實現對事物的管理。
## 事物的建立和使用
在使用MyBatis的時候,會在MyBatis的配置文件mybatis-config.xml中定義,此處使用前文(https://www.jianshu.com/p/063a5ca8874c)配置信息:
mysql
1 <environment id="mysql"> 2 <!--指定事務管理的類型,這裏簡單使用Java的JDBC的提交和回滾設置--> 3 <transactionManager type="JDBC"></transactionManager> 4 <!--dataSource 指鏈接源配置,POOLED是JDBC鏈接對象的數據源鏈接池的實現--> 5 <dataSource type="POOLED"> 6 <property name="driver" value="com.mysql.jdbc.Driver"></property> 7 <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"></property> 8 <property name="username" value="root"></property> 9 <property name="password" value="****"></property> 10 </dataSource> 11 </environment>
MyBatis經過緩存機制減輕數據壓力,提升數據庫性能。
一級緩存
在操做數據庫時須要構造SqlSession對象,在對象中有一個HashMap用戶緩存數據。不一樣的SqlSession之間的緩存數據區域(HashMap)是互相不影響的。
一級緩存的做用是SqlSession範圍的,當同一個SqlSession中執行兩次相同的sql語句時,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次查詢時會從緩存中獲取數據,再也不去底層數據庫查詢,提升查詢效率。
注意:
若SqlSession執行了DML操做(insert、update和delete),並提交到數據庫,MyBatis則會清空SqlSession中的一級緩存,目的是爲了保證緩存中存儲的是最新的數據,避免髒讀現象。
當一個SqlSession結束後,該SqlSession中的一級緩存也就不存在了。
MyBatis默認開啓一級緩存,不須要進行任何配置。
一級緩存測試
項目代碼使用前文項目(https://www.jianshu.com/p/063a5ca8874c)
如今數據庫的tb_user表中插入幾條數據:
而後在項目的UserMapper.xml文件中添加查詢和刪除程序,完整程序以下:
sql
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.snow.dcl.mapper.UserMapper"> 5 <!--插入用戶數據--> 6 <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true"> 7 insert into tb_user(name,sex,age) values (#{name},#{sex},#{age}); 8 </insert> 9 <!--根據id查詢用戶--> 10 <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User"> 11 select * from tb_user where id=#{id}; 12 </select> 13 <!--查詢全部用戶--> 14 <select id="selectAllUser" resultType="com.snow.dcl.domain.User"> 15 select * from tb_user; 16 </select> 17 <!--根據id刪除用戶--> 18 <delete id="deleteUserById" parameterType="int"> 19 delete from tb_user where id=#{id}; 20 </delete> 21 </mapper>
1 public interface UserMapper { 2 //根據id查詢用戶 3 User selectUserById(Integer id); 4 //查詢全部用戶 5 List<User> selectAllUser(); 6 //根據id刪除用戶 7 void deleteUserById(Integer id); 8 }
1 public class FactoryUtil { 2 private static SqlSessionFactory sqlSessionFactory = null; 3 4 static { 5 try { 6 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); 7 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 8 } catch (IOException e) { 9 e.printStackTrace(); 10 } 11 } 12 13 public static SqlSession getSqlSession(){ 14 return sqlSessionFactory.openSession(); 15 } 16 } 17
在項目的test目錄下的java目錄下建立OneLevelCacheTest.java測試類文件:
編寫以下程序:
數據庫
1 public class OneLevelCacheTest { 2 public static void main(String[] args) { 3 OneLevelCacheTest oneLevelCacheTest = new OneLevelCacheTest(); 4 oneLevelCacheTest.cacheOneTest(); 5 } 6 7 public void cacheOneTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 User anotherUser = userMapper.selectUserById(1); 13 System.out.println(anotherUser); 14 sqlSession.close(); 15 } 16 } 17
執行測試程序OneLevelCacheTest1.java,能夠在控制檯看到打印結果:
能夠看到在第一次執行查詢id爲1的User對象時,執行了一條select語句,第二次執行查詢id爲1的User對象時,沒有執行select語句,由於此時一級緩存中已經緩存了id爲1的User對象,MyBatis直接從緩存中將User對象取出來,並無再次去數據庫中查詢。
DML操做清空緩存
在項目的test目錄下的java目錄建立OneLevelCacheTest2測試類文件,編寫以下代碼:
apache
1 public class OneLevelCacheTest2 { 2 public static void main(String[] args) { 3 OneLevelCacheTest2 oneLevelCacheTest = new OneLevelCacheTest2(); 4 oneLevelCacheTest.cacheOneTest(); 5 } 6 7 public void cacheOneTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 userMapper.deleteUserById(7); 13 sqlSession.commit(); 14 User anotherUser = userMapper.selectUserById(1); 15 System.out.println(anotherUser); 16 sqlSession.close(); 17 } 18 } 19
執行測試程序OneLevelCacheTest2.java,能夠在控制檯看到打印結果:
能夠看到在第一次執行查詢id爲1的User對象時,執行了一條select語句,接下來執行了一個delete操做,MyBatis爲了保證緩存中存儲的是最新的數據,清空了一級緩存,因此第二次執行查詢id爲1的User對象時,又執行了select語句。
不一樣Session對象對一級緩存的影響
在項目的test目錄下的java目錄建立OneLevelCacheTest3測試類文件,編寫以下代碼:
緩存
1 public class OneLevelCacheTest3 { 2 public static void main(String[] args) { 3 OneLevelCacheTest3 oneLevelCacheTest = new OneLevelCacheTest3(); 4 oneLevelCacheTest.cacheOneTest(); 5 } 6 7 public void cacheOneTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 sqlSession.close(); 13 sqlSession = FactoryUtil.getSqlSession(); 14 userMapper = sqlSession.getMapper(UserMapper.class); 15 User anotherUser = userMapper.selectUserById(1); 16 System.out.println(anotherUser); 17 sqlSession.close(); 18 } 19 } 20
執行測試程序OneLevelCacheTest2.java,能夠在控制檯看到打印結果:
能夠看到在第一次執行查詢id爲1的User對象時,執行了一條select語句,接下來調用了sqlSession.close()關閉了一級緩存,第二次執行查詢id爲1的User對象時,一級緩存是一個新的對象,緩存中沒有緩存任何數據,因此再次執行了select語句。
二級緩存
使用二級緩存時,多個SqlSession使用同一個Mapper的sql語句去操做數據庫,獲得的數據會存在二級緩存區域,它一樣是使用HashMap進行數據存儲。相比一級緩存SqlSession,二級緩存的範圍更大,多個SqlSession能夠共用二級緩存,二級緩存是跨SqlSession的。
二級緩存是多個SqlSession共享的,其做用域是mapper的同一個namespace。不一樣的SqlSession兩次執行相同namespace下的sql語句,且向sql中傳遞的參數也相同,即最終執行相同的sql語句,則第一次執行完畢會將數據庫中查詢的數據寫入緩存,第二次查詢時會從緩存中獲取數據,再也不去底層數據庫查詢,提升效率。
MyBatis默認沒有開啓二級緩存,須要在setting全局參數中進行配置,開啓二級緩存。
二級緩存測試
在mubatis-config.xml配置文件中開啓二級緩存,完整配置文件以下:
安全
1 <configuration> 2 <!-- 指定Mybatis所用日誌的具體實現 --> 3 <settings> 4 <!--開啓二級緩存--> 5 <setting name="cacheEnabled" value="true"/> 6 <!--開啓日誌--> 7 <setting name="logImpl" value="Log4J"/> 8 </settings> 9 <!--環境配置,鏈接的數據庫--> 10 <environments default="mysql"> 11 <environment id="mysql"> 12 <!--指定事務管理的類型,這裏簡單使用Java的JDBC的提交和回滾設置--> 13 <transactionManager type="JDBC"></transactionManager> 14 <!--dataSource 指鏈接源配置,POOLED是JDBC鏈接對象的數據源鏈接池的實現--> 15 <dataSource type="POOLED"> 16 <property name="driver" value="com.mysql.jdbc.Driver"></property> 17 <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8"></property> 18 <property name="username" value="root"></property> 19 <property name="password" value="Password@123"></property> 20 </dataSource> 21 </environment> 22 </environments> 23 <mappers> 24 <!--告訴Mybatis持久化類的映射文件路徑--> 25 <mapper resource="mapping/UserMapper.xml"></mapper> 26 </mappers> 27 </configuration>
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.snow.dcl.mapper.UserMapper"> 5 <!--開啓當前mapper的namespace下的二級緩存--> 6 <cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/> 7 <!--插入用戶數據--> 8 <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true"> 9 insert into tb_user(name,sex,age) values (#{name},#{sex},#{age}); 10 </insert> 11 <!--根據id查詢用戶--> 12 <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User"> 13 select * from tb_user where id=#{id}; 14 </select> 15 <!--查詢全部用戶--> 16 <select id="selectAllUser" resultType="com.snow.dcl.domain.User"> 17 select * from tb_user; 18 </select> 19 <!--根據id刪除用戶--> 20 <delete id="deleteUserById" parameterType="int"> 21 delete from tb_user where id=#{id}; 22 </delete> 23 </mapper>
1 public class TwoLevelCacheTest1 { 2 public static void main(String[] args) { 3 TwoLevelCacheTest1 twoLevelCacheTest = new TwoLevelCacheTest1(); 4 twoLevelCacheTest.cacheTwoTest(); 5 } 6 7 public void cacheTwoTest(){ 8 SqlSession sqlSession = FactoryUtil.getSqlSession(); 9 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 10 User user = userMapper.selectUserById(1); 11 System.out.println(user); 12 sqlSession.close(); 13 sqlSession = FactoryUtil.getSqlSession(); 14 userMapper = sqlSession.getMapper(UserMapper.class); 15 User anotherUser = userMapper.selectUserById(1); 16 System.out.println(anotherUser); 17 sqlSession.close(); 18 } 19 } 20
執行測試程序TwoLevelCacheTest.java,能夠在控制檯看到打印結果:
能夠看到在第一次執行查詢id爲1的User對象時,執行了一條select語句,接下來調用了sqlSession.close()關閉了一級緩存,第二次執行查詢id爲1的User對象時,一級緩存沒有任何對象,但由於啓用了二級緩存,第一次查詢的數據會緩存在二級緩存中,因此顯示命中二級緩存數據,不須要在執行select語句。
注意:
在UserMapper.xml文件的select語句中設置useCache='false',能夠禁用當前select語句的二級緩存,即每次都會查詢數據庫,默認爲true,配置內容以下:
服務器
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4 <mapper namespace="com.snow.dcl.mapper.UserMapper"> 5 <!--開啓當前mapper的namespace下的二級緩存--> 6 <cache eviction="LRU" flushInterval="20000" size="512" readOnly="true"/> 7 <!--插入用戶數據--> 8 <insert id="saveUser" parameterType="com.snow.dcl.domain.User" useGeneratedKeys="true"> 9 insert into tb_user(name,sex,age) values (#{name},#{sex},#{age}); 10 </insert> 11 <!--根據id查詢用戶--> 12 <select id="selectUserById" parameterType="int" resultType="com.snow.dcl.domain.User" useCache="false"> 13 select * from tb_user where id=#{id}; 14 </select> 15 <!--查詢全部用戶--> 16 <select id="selectAllUser" resultType="com.snow.dcl.domain.User"> 17 select * from tb_user; 18 </select> 19 <!--根據id刪除用戶--> 20 <delete id="deleteUserById" parameterType="int"> 21 delete from tb_user where id=#{id}; 22 </delete> 23 </mapper>