MyBatis Series Part 5: MyBatis cache

 

1. What is MyBatis caching?

Using MyBatis cache can reduce the number of interaction between java application and database. From this point of view, its benefits are the same as using MyBatis to delay loading, which can improve the running efficiency of the program to a certain extent. For example, if you query a java object with id=1, the java object will be automatically saved in MyBatis cache after the first query. When you query an object with id=1 next time, you can directly retrieve the object interface from the cache without accessing the database again. However, if the object with id=2 is queried for the second time, it cannot be obtained from the cache. At this time, you still need to access the database again. After accessing, you can save the java object with id=2 to the cache.

    public static void main(String[] args) {
        //Load MyBatis profile
        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();

        //Get the proxy object that implements the interface
        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 cache classification

MyBatis has two types of cache: first level cache and second level cache.

(1) Level 1 cache: SqlSession level. It is on by default and cannot be closed.

When operating the database, you need to create a SqlSession object. There is a HashMap in the SqlSession object to store the cached data. Because the first level cache is at the SqlSession level, the cached data between different SqlSession objects will not affect each other.

The scope of the first level cache is in the scope of SqlSession. When the same SQL statement is executed twice in the same SqlSession, the result will be saved in the cache of the SqlSession object after the first execution. When the same query SQL is executed the second time, the result will be directly taken from the cache and returned.

It should be noted that if SqlSession object performs DML operations (insert, update, delete), MyBatis must clear the cache of SqlSession object to ensure the accuracy of data query.

(2) Level 2 Cache: Mapper level. It is off by default. If you want to use it, you need to turn it on manually. Its scope is larger than the first level cache.

When using the second level cache, when multiple SqlSession objects use the same Mapper's SQL statement to operate the database, the data obtained will be saved in the same second level cache area, and the same way is to use HashMap for data storage. Compared with the first level cache, the second level cache has a larger visible range. Multiple SqlSession objects can share the same Mapper's second level cache. Therefore, the second level cache is cross SqlSession.

The second level cache is shared by multiple sqlsessions, and its scope is the same namespace of Mapper. When different sqlsessions execute SQL statements under the same namespace twice and the parameters are equal, the data will be saved to the second level cache after the first successful execution, and the second can be obtained directly from the second level cache.

 

3. Code demonstration: first level cache

(1) Entity class

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity implements Serializable {
    private static final long serialVersionUID = -7497520016303190017L;
    private int id;                     //Student number
    private String name;                //full name
    private int classId;                //class
    private int status;                 //Is it valid (1: valid, - 1: invalid)
    private String addTime;             //Add time
    private String updateTime;          //Update time
    private ClassEntity classEntity;    //Which class does the student belong to
}

(2) Entity class interface

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

(3) Of entity class interface Mapper.xml file

<?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) Level 1 cache test: sqlSession object is the same, and then execute the same SQL twice before and after

public class UserTest4 {
    public static void main(String[] args) {
        //Load MyBatis profile
        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();

        //Get the proxy object that implements the interface
        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();
    }
}

Conclusion: from the above test results, we can see that the SQL statement is executed only once, that is to say, it only interacts with the database once.

(5) First level cache test: sqlSession object is not the same, and then execute the same SQL twice before and after

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

        //Get the proxy object that implements the interface
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        //The sqlSession above is closed, that is, the level of cache is closed, and then another one is reopened
        sqlSession.close();
        sqlSession = sqlSessionFactory.openSession();
        studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity2 = studentRepository.queryStudent(1);
        System.out.println(entity2);

        sqlSession.close();
    }
}

Conclusion: from the above test results, it can be found that first, create an sqlSession object, execute SQL once, interact with the database once, and return the result. Then close the sqlSession object and recreate a new sqlSession object. When executing the same SQL, you will find that you still need to interact with the database once again during the second query. Therefore, there are two interactions before and after.

4. Code demonstration: L2 cache

4.1 secondary cache of mybatis

(1) Enable secondary cache in MyBatis configuration file: 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>
        <!--Turn on printing on the console SQL journal-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--Open delay loading-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--Enable L2 cache-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

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

    <!--to configure mybatis Operating environment-->
    <environments default="development">
        <environment id="development">
            <!--to configure JDBC transaction management-->
            <transactionManager type="JDBC"></transactionManager>
            <!--to configure POOLED Of type JDBC Data source connection pool-->
            <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>

    <!--register mapper file-->
    <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) At Mapper.xml Add the configuration of secondary cache in the file: < 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) entity class implements serialization interface

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity implements Serializable {
    private static final long serialVersionUID = -7497520016303190017L;
    private int id;                     //Student number
    private String name;                //full name
    private int classId;                //class
    private int status;                 //Is it valid (1: valid, - 1: invalid)
    private String addTime;               //Add time
    private String updateTime;            //Update time
    private ClassEntity classEntity;    //Which class does the student belong to
}

(4) L2 cache test

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

        //Get the proxy object that implements the interface
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        //The sqlSession above is closed, that is, the level of cache is closed, and then another one is reopened
        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();
    }
}

Conclusion: from the above test results, it can be found that first, create an sqlSession object, execute SQL once, interact with the database once, and return the result. Then close the sqlSession object and recreate a new sqlSession object. When executing the same SQL, you will find that the second query and the third query do not need to interact with the database again because the second level cache settings are enabled, and the second level cache will be hit to extract data directly from the second level cache. It can also be seen from the log that [cache hit ratio][ com.wind.repository . studentrepository]: 0.5], cache hit, hit rate 0.5. Therefore, they interact once before and after.

4.2 second level cache of third-party plug-ins: ehcache component

(1) pom.xml Introduce dependency in file

       <!--Third party cache dependency-->
        <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) Add the ehcache plug-in's own ehcache.xml configuration file

<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 Configure L2 cache in

<?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">
        <!-- After cache creation, the time from the last access to cache to cache expiration -->
        <property name="timeToIdleSeconds" value="3600"/>
        <!-- Cache time interval from creation time to expiration -->
        <property name="timeToLiveSeconds" value="3600"/>
        <!-- Cache recovery policy, LRU Represents the removal of objects that have been used the least recently -->
        <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) Entity classes no longer need to implement serialization interfaces

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class StudentEntity {
    private int id;                     //Student number
    private String name;                //full name
    private int classId;                //class
    private int status;                 //Is it valid (1: valid, - 1: invalid)
    private String addTime;               //Add time
    private String updateTime;            //Update time
    private ClassEntity classEntity;    //Which class does the student belong to
}

(5) L2 cache test

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

        //Get the proxy object that implements the interface
        StudentRepository studentRepository = sqlSession.getMapper(StudentRepository.class);
        StudentEntity entity = studentRepository.queryStudent(1);
        System.out.println(entity);

        //The sqlSession above is closed, that is, the level of cache is closed, and then another one is reopened
        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();
    }
}

 

 

 

Tags: Mybatis xml SQL Database

Posted on Sun, 21 Jun 2020 02:58:54 -0400 by springo