MyBatis的事物管理和緩存

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&amp;characterEncoding=UTF-8"></property>
  8         <property name="username" value="root"></property>
  9         <property name="password" value="****"></property>
 10     </dataSource>
 11 </environment>

<environment>元素定義了鏈接數據庫的信息,<transactionManager>子元素的type決定了使用什麼類型的事物管理機制。
MyBatis的緩存
緩存的概述
在實際項目開發中,一般對數據庫查詢的性能要求很高,MyBatis提供了查詢緩存來進行數據的緩存,以達到提升查詢性能的要求。
MyBatis的查詢緩存分爲一級緩存和二級緩存:
1.一級緩存是SqlSession級別的緩存。
2.二級緩存是mapper級別的緩存,二級緩存是多個SqlSession共享的。

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表中插入幾條數據:
MyBatis01
而後在項目的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>

在項目的java目錄右鍵,建立com.snow.dcl.mapper包,在該包中建立UserMapper.java接口類:
MyBatis02
編寫以下程序:
  1 public interface UserMapper {
  2     //根據id查詢用戶
  3     User selectUserById(Integer id);
  4     //查詢全部用戶
  5     List<User> selectAllUser();
  6     //根據id刪除用戶
  7     void deleteUserById(Integer id);
  8 }

獲取mybatis-config.xml配置文件,根據配置文件建立SqlSessionFactory,獲取SqlSession對象這一系列操做,每次都要使用,因此將其封裝在一個類文件中,在項目java目錄右鍵,建立com.snow.dcl.utils包,在該包下建立FactoryUtil.java類文件:
MyBatis03
添加以下程序:
  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測試類文件:
MyBatis04
編寫以下程序:
數據庫

  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,能夠在控制檯看到打印結果:
MyBatis05
能夠看到在第一次執行查詢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,能夠在控制檯看到打印結果:
MyBatis06
能夠看到在第一次執行查詢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,能夠在控制檯看到打印結果:
MyBatis07
能夠看到在第一次執行查詢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&amp;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>

cacheEnabled默認爲false,設置爲true表示開啓二級緩存。
在UserMapper.sml文件配置緩存相關參數,完整配置文件以下:
  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.eviction:回收策略,默認爲LRU,此外還有FIFO、SOFT、WEAK。
LRU:最近最少使用策略,移除最長時間不被使用的對象。
FIFO:先進先出策略,按對象進入緩存的順序來移除。
SOFT:軟引用策略,移除基於垃圾回收器狀態和軟引用規則的對象。
WEAK:弱引用策略,更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
2.flushInterval:刷新間隔時間,任意正整數,單位毫秒,默認狀況下是沒有刷新間隔,緩存僅在調用語句時刷新。
3.size:緩存數目,任意正整數,要記住緩存的對象數目與運行環境的可用內存資源數目,默認1024。
4.readOnly:只讀,屬性爲true或false。true緩存會給全部調用者返回緩存對象的相同實例,對象不能修改,性能高。false緩存會返回緩存對象的拷貝(經過序列化),性能低,可是安全。默認爲false。
注意:
使用二級緩存時,與查詢結果映射的Java對象必須實現java.io.Serializable接口的序列化和反序列化操做,若存在父類,其成員都要實現序列化接口。實現該接口的緣由是爲了對緩存數據進行序列化和反序列化操做,由於二級緩存數據存儲介質多種多樣,不必定在內存,多是硬盤或者遠程服務器。

在項目的test目錄下的java目錄下建立TwoLevelCacheTest1.java測試類文件,編寫以下代碼:
  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,能夠在控制檯看到打印結果:
MyBatis08
能夠看到在第一次執行查詢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>
相關文章
相關標籤/搜索