MyBatis系列第五篇:MyBatis緩存

 

1、什麼是MyBatis緩存?

使用MyBatis緩存可以減少java應用程序與數據庫的交互次數,從這個角度看,它的好處和使用MyBatis延遲加載一樣,都可以在一定程度上提高程序的運行效率。比如,查詢出 id=1的java對象,第一次查詢出來之後會自動把這個java對象保存在MyBatis緩存中,當下一次再查詢id=1的對象時,直接從緩存中取出對象接口,無需再次訪問數據庫。但是,如果第二次查詢的是id=2的對象,那麼從緩存中是獲取不到的,此時仍然需要再次訪問數據庫,訪問完之後再把id=2的java對象再保存到緩存中去。

    public static void main(String[] args) {
        //加載MyBatis配置文件
        InputStream inputStream = UserTest3.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        //獲取實現接口的代理對象
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentRepository studentRepository2 = sqlSession2.getMapper(StudentRepository.class);

        System.out.println(studentRepository);
        System.out.println(studentRepository2);

        // org.apache.ibatis.binding.MapperProxy@6df97b55
        // org.apache.ibatis.binding.MapperProxy@3cbbc1e0

        StudentEntity studentEntity = studentRepository.queryStudentLazy(2);
        System.out.println(studentEntity.getClassEntity().getClassName());

        StudentEntity studentEntity2 = studentRepository2.queryStudentLazy(2);
        System.out.println(studentEntity2.getClassEntity().getClassName());
        
        sqlSession.close();
    }

2、MyBatis緩存分類

MyBatis的緩存分爲兩類:一級緩存和二級緩存。

(1)一級緩存:SqlSession級別,默認是開啓的,而且不能關閉。

操作數據庫的時候,需要創建SqlSession對象,在SqlSession對象中有一個HashMap用來存儲緩存數據,由於一級緩存是SqlSession級別的,所以不同的SqlSession對象之間緩存的數據是互不影響的。

一級緩存的作用域是在SqlSession範圍內,當在同一個SqlSession中執行了兩次相同的SQL語句時,第一次執行完之後會把結果保存在該SqlSession對象的緩存中,在第二次執行相同查詢SQL的時候會直接從緩存中取出結果並返回。

需要注意的是,如果SqlSession對象執行了DML操作(insert、update、delete),那麼MyBatis必須將SqlSession對象的緩存清空來保證數據查詢的準確性。

(2)二級緩存:Mapper級別,默認是關閉的,想要使用的話需要手動開啓。它的範圍要大於一級緩存。

使用二級緩存時,多個SqlSession對象使用同一個Mapper的SQL語句操作數據庫時,得到的數據會在同一個二級緩存區進行保存,同樣是使用HashMap來做數據存儲。相比較與一級緩存而言,二級緩存的可見範圍更大,多個SqlSession對象可以共用同一個Mapper的二級緩存,所以說,二級緩存是跨SqlSession的。

二級緩存是多個SqlSession共享的,其作用域就是Mapper的同一個namespace。不同的SqlSession兩次執行相同的namespace下的SQL語句,參數也相等時,則第一次執行成功後會把數據保存到二級緩存中,第二次可以直接從二級緩存中獲取。

 

3、代碼演示:一級緩存

(1)實體類

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity implements Serializable {
    private static final long serialVersionUID = -7497520016303190017L;
    private int id;                     //學號
    private String name;                //姓名
    private int classId;                //班級
    private int status;                 //是否有效(1:有效,-1:無效)
    private String addTime;             //添加時間
    private String updateTime;          //更新時間
    private ClassEntity classEntity;    //該學生屬於哪個班級
}

(2)實體類接口

public interface StudentRepository {
    StudentEntity queryStudent(@Param("id") int id);
}

(3)實體類接口的Mapper.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.wind.repository.StudentRepository">

    <resultMap id="studentMap" type="com.wind.entity.StudentEntity">
        <result column="Id" property="id"/>
        <result column="Name" property="name"/>
        <result column="ClassId" property="classId"/>
        <result column="Status" property="status"/>
        <result column="AddTime" property="addTime"/>
        <result column="UpdateTime" property="updateTime"/>
    </resultMap>

    <sql id="sql_select">
        select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student
    </sql>

    <select id="queryStudent" parameterType="int" resultMap="studentMap">
        <include refid="sql_select"/>
        where id = #{id} and status = 1
    </select>
</mapper>

(4)一級緩存測試:sqlSession對象 是 同一個,然後前後兩次執行相同的SQL

public class UserTest4 {
    public static void main(String[] args) {
        //加載MyBatis配置文件
        InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        //獲取實現接口的代理對象
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        StudentEntity entity2 = studentRepository.queryStudent(1);
        System.out.println(entity2);

        sqlSession.close();
    }
}

結論:從上述測試結果可以發現,SQL語句只執行了一次,也就是說只和數據庫交互了一次。

(5)一級緩存測試:sqlSession對象 不是 同一個,然後前後兩次執行相同的SQL

public class UserTest4 {
    public static void main(String[] args) {
        //加載MyBatis配置文件
        InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //獲取實現接口的代理對象
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        //上面那個sqlSession關閉,也即關閉了那個一級緩存,再重新打開一個
        sqlSession.close();
        sqlSession = sqlSessionFactory.openSession();
        studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity2 = studentRepository.queryStudent(1);
        System.out.println(entity2);

        sqlSession.close();
    }
}

結論:從上述測試結果可以發現,先創建一個sqlSession對象,執行一次SQL,此時和數據庫交互一次,並把結果返回。然後把這個sqlSession對象關閉,再重新創建一個新的sqlSession對象,此時再去執行相同的SQL時,會發現第二次查詢時仍然需要再次和數據庫交互一次。所以,前後共交互兩次。

4、代碼演示:二級緩存

4.1 MyBatis自帶的二級緩存

(1)在MyBatis配置文件中開啓二級緩存: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>

    <settings>
        <!--開啓在控制檯打印SQL日誌-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--開啓延遲加載-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--開啓二級緩存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <typeAliases>
        <package name="com.wind.entity"/>
    </typeAliases>

    <!--配置mybatis運行環境-->
    <environments default="development">
        <environment id="development">
            <!--配置JDBC事務管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置POOLED類型的JDBC數據源連接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/RUNOOB?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="admin0001112"/>
            </dataSource>
        </environment>
    </environments>

    <!--註冊mapper文件-->
    <mappers>
        <mapper resource="com/entity/mapper/UserMapper.xml"/>
        <mapper resource="com/entity/mapper/UserRepository.xml"/>
        <mapper resource="com/entity/mapper/StudentRepository.xml"/>
        <mapper resource="com/entity/mapper/ClassRepository.xml"/>
    </mappers>

</configuration>

(2)在Mapper.xml文件中添加二級緩存的配置:<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.wind.repository.StudentRepository">

    <cache/>

    <resultMap id="studentMap" type="com.wind.entity.StudentEntity">
        <result column="Id" property="id"/>
        <result column="Name" property="name"/>
        <result column="ClassId" property="classId"/>
        <result column="Status" property="status"/>
        <result column="AddTime" property="addTime"/>
        <result column="UpdateTime" property="updateTime"/>
    </resultMap>

    <sql id="sql_select">
        select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student
    </sql>

    <select id="queryStudent" parameterType="int" resultMap="studentMap">
        <include refid="sql_select"/>
        where id = #{id} and status = 1
    </select>

</mapper>

 (3)實體類實現序列化接口

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity implements Serializable {
    private static final long serialVersionUID = -7497520016303190017L;
    private int id;                     //學號
    private String name;                //姓名
    private int classId;                //班級
    private int status;                 //是否有效(1:有效,-1:無效)
    private String addTime;               //添加時間
    private String updateTime;            //更新時間
    private ClassEntity classEntity;    //該學生屬於哪個班級
}

(4)二級緩存測試

public class UserTest4 {
    public static void main(String[] args) {
        //加載MyBatis配置文件
        InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //獲取實現接口的代理對象
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        //上面那個sqlSession關閉,也即關閉了那個一級緩存,再重新打開一個
        sqlSession.close();
        sqlSession = sqlSessionFactory.openSession();
        studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity2 = studentRepository.queryStudent(1);
        System.out.println(entity2);

        StudentEntity entity3 = studentRepository.queryStudent(1);
        System.out.println(entity3);

        sqlSession.close();
    }
}

結論:從上述測試結果可以發現,先創建一個sqlSession對象,執行一次SQL,此時和數據庫交互一次,並把結果返回。然後把這個sqlSession對象關閉,再重新創建一個新的sqlSession對象,此時再去執行相同的SQL時,會發現由於開啓了二級緩存設置,第二次查詢與第三次查詢時不再需要再次和數據庫交互了,會命中二級緩存直接從二級緩存中取出數據即可。從日誌中也可以看出,【Cache Hit Ratio [com.wind.repository.StudentRepository]: 0.5 】,緩存命中了,命中率0.5。所以,前後共交互一次。

4.2 第三方插件的二級緩存:ehcache組件

(1)pom.xml文件中引入相關依賴

       <!--第三方緩存依賴-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.6.11</version>
        </dependency>

(2)添加ehcache插件自帶的 ehcache.xml 配置文件

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
     <diskStore/>
     <defaultCache
             maxElementsInMemory="1000"
             maxElementsOnDisk="10000000"
             eternal="false"
             overflowToDisk="false"
             timeToIdleSeconds="120"
             timeToLiveSeconds="120"
             diskExpiryThreadIntervalSeconds="120"
             memoryStoreEvictionPolicy="LRU">
     </defaultCache>
 </ehcache>

(3)Mapper.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.wind.repository.StudentRepository">

    <cache type="org.mybatis.caches.ehcache.EhcacheCache">
        <!-- 緩存創建之後,最後⼀一次訪問緩存的時間⾄至緩存失效的時間間隔 -->
        <property name="timeToIdleSeconds" value="3600"/>
        <!-- 緩存⾃自創建時間起⾄至失效的時間間隔 -->
        <property name="timeToLiveSeconds" value="3600"/>
        <!-- 緩存回收策略略,LRU表示移除近期使⽤用最少的對象 -->
        <property name="memoryStoreEvictionPolicy" value="LRU"/>
    </cache>

    <resultMap id="studentMap" type="com.wind.entity.StudentEntity">
        <result column="Id" property="id"/>
        <result column="Name" property="name"/>
        <result column="ClassId" property="classId"/>
        <result column="Status" property="status"/>
        <result column="AddTime" property="addTime"/>
        <result column="UpdateTime" property="updateTime"/>
    </resultMap>

    <sql id="sql_select">
        select Id, Name, ClassId, Status, AddTime, UpdateTime from RUN_Student
    </sql>

    <select id="queryStudent" parameterType="int" resultMap="studentMap">
        <include refid="sql_select"/>
        where id = #{id} and status = 1
    </select>
</mapper>

(4)實體類不再需要實現序列化接口

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity {
    private int id;                     //學號
    private String name;                //姓名
    private int classId;                //班級
    private int status;                 //是否有效(1:有效,-1:無效)
    private String addTime;               //添加時間
    private String updateTime;            //更新時間
    private ClassEntity classEntity;    //該學生屬於哪個班級
}

(5)二級緩存測試

public class UserTest4 {
    public static void main(String[] args) {
        //加載MyBatis配置文件
        InputStream inputStream = UserTest4.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //獲取實現接口的代理對象
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        //上面那個sqlSession關閉,也即關閉了那個一級緩存,再重新打開一個
        sqlSession.close();
        sqlSession = sqlSessionFactory.openSession();
        studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity2 = studentRepository.queryStudent(1);
        System.out.println(entity2);

        StudentEntity entity3 = studentRepository.queryStudent(1);
        System.out.println(entity3);

        sqlSession.close();
    }
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章