Mybatis Cache Novel

Keywords: Mybatis Cache

Catalog

Article Directory

Preface

1.Level 1 Cache

1.1.What is a Level 1 Cache

1.2.Test 1-Same SqlSession

1.3. Test 2-Different SqlSession

1.4.Test 3 - Refresh Cache

1.5.summary

2.Secondary Cache

2.1.What is a secondary cache

2.2.Configure Secondary Cache

2.2.1.Global Switch

2.2.2.Separate off

2.2.3.Code Configuration

2.2.4.Detailed cache tag configuration

summary

Preface

The importance of caching is self-evident. Using caching, we can avoid frequent interaction with the database, especially when more queries are made and the cache hit rate is higher, the performance improvements made by using caching are more evident.
mybatis also provides support for caching, which is divided into a first-level cache and a second-level cache. But by default, only the first-level cache is turned on (for the same SqlSession). The code is in ( Idea creates a simple MyBatis project ) Make changes based on

1.Level 1 Cache

1.1.What is a Level 1 Cache

The same SqlSession object executes the SQL statement only once (if the cache does not expire) when the parameters and SQL are identical.
Because after the first query using SelSession, MyBatis puts it in the cache, and when querying later, if there is no declaration that refresh is required and the cache does not time out, SqlSession takes out the currently cached data and does not send the SQL to the database again.

1.2.Test 1-Same SqlSession

StudentMapperTest.java Add the following code
@Test
    public void oneSqlSession() {
        SqlSession sqlSession = null;
        try {
            sqlSession = sqlSessionFactory.openSession();

            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            // Execute the first query
            System.out.println("=============Start with the same Sqlsession First Query============");
            List<Student> students = studentMapper.selectAll();
            for (int i = 0; i < students.size(); i++) {
                System.out.println(students.get(i));
            }
            System.out.println("=============Start with the same Sqlsession Second Query============");
            // Second query with the same sqlSession
            List<Student> stus = studentMapper.selectAll();
            Assert.assertEquals(students, stus);
            for (int i = 0; i < stus.size(); i++) {
                System.out.println("stus:" + stus.get(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

Execute the above code, query twice, use the same SqlSession, and get the following results

In Logs and Outputs:
The first query sent a SQL statement and returned the result.
The second query did not send a SQL statement, and the results were obtained directly from memory.
The two result inputs are identical, and the assertion that the two objects are identical is also passed.

1.3. Test 2-Different SqlSession

StudentMapperTest.java Add the following code

 @Test
    public void differSqlSession() {
        SqlSession sqlSession1 = null;
        SqlSession sqlSession2 = null;
        try {
            sqlSession1 = sqlSessionFactory.openSession();

            StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
            // Execute the first query
            System.out.println("=============Start different Sqlsession First Query============");
            List<Student> students = studentMapper.selectAll();
            for (int i = 0; i < students.size(); i++) {
                System.out.println(students.get(i));
            }
            System.out.println("=============Start different Sqlsession Second Query============");
            // Create a new sqlSession2 for a second query
            sqlSession2 = sqlSessionFactory.openSession();
            StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
            List<Student> stus = studentMapper2.selectAll();
            // Unequal
            Assert.assertNotEquals(students, stus);
            for (int i = 0; i < stus.size(); i++) {
                System.out.println("stus:" + stus.get(i));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession1 != null) {
                sqlSession1.close();
            }
            if (sqlSession2 != null) {
                sqlSession2.close();
            }
        }
    }

Execute the above code and query twice, using SqlSession1 and SqlSession2 respectively, to get the following results

You can see from the log that both queries retrieved data from the database. Although the results are the same, the two are different objects.

1.4.Test 3 - Refresh Cache

Refreshing the cache is emptying all caches for this SqlSession, not just a key.

StudentMapper.java adds the selectByPrimaryKey() method

package com.fang.mybatis.mapper;

import com.fang.mybatis.entity.Student;

import java.util.List;

public interface StudentMapper {

    /**
     *
     * @return
     */
    List<Student> selectAll();

    Student selectByPrimaryKey(int id);
}

StudentMapper.xml adds the following configuration, and if flushCache="true" is not configured, the result is the second non-SQL statement.

<select id="selectByPrimaryKey" flushCache="true" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from student
        where student_id=#{student_id, jdbcType=INTEGER}
</select>

StudentMapperTest.java Add the following code

@Test
    public void sameSqlSessionNoCache() {
        SqlSession sqlSession = null;
        try {
            sqlSession = sqlSessionFactory.openSession();

            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            // Execute the first query
            Student student = studentMapper.selectByPrimaryKey(1);
            System.out.println("=============Start with the same Sqlsession Second Query============");
            // Second query with the same sqlSession
            Student stu = studentMapper.selectByPrimaryKey(1);
            Assert.assertEquals(student, stu);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }

Executing the above code yields the following results, sending the SQL statement the first time and the second time, while asserting that the two objects are identical.

1.5.summary

1. In the same SqlSession, Mybatis generates cached key values from the executed methods and parameters, stores the key values and results in a Map, and retrieves data directly from the Map if the following key values are the same;
2. Caches between different SqlSession s are isolated from each other;
3. With a SqlSession, you can configure it to empty the cache before querying;
4. Any UPDATE, INSERT, DELETE statement will empty the cache.

2.Secondary Cache

2.1.What is a secondary cache

A secondary cache exists in the SqlSessionFactory lifecycle.

2.2.Configure Secondary Cache

In mybatis, the secondary cache has global switches and switches. Global switches are turned on by default and are generally configured to make it easier for the team to know that the secondary cache has been used.

2.2.1.Global Switch

Global switch, which is configured in mybatis-config.xml as follows, is true by default, that is, the total switch is turned on by default.

<settings>
  <!--Turn on or off any caches that have been configured by all mappers in the configuration file globally. -->
  <setting name="cacheEnabled" value="true"/>
</settings>

2.2.2.Separate off

Separation means that the secondary cache is turned on or off in *Mapper.xml and is not turned on by default. If you want to use the secondary cache cache tag, you must configure it.

To use, configure as follows:

<!--At present Mapper.xml File Open Secondary Cache-->
<cache/>

Or customize cache tag parameters

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

eviction: Cleanup policy is FIFO cache, FIFO first-in-first-out principle, default Cleanup policy is LRU
flushInterval: The property can be set to any positive integer and the value set should be a reasonable amount of time in milliseconds
size: maximum number of references that can store result objects or lists
readOnly: A read-only property that can be set to true or false.

2.2.3.Code Configuration

1.StudentMapper.xml is configured as follows:

<cache readOnly="false"/>

The complete code for StudentMapper.xml is as follows:

<?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.fang.mybatis.mapper.StudentMapper">
    <cache readOnly="false"/>
    <resultMap id="BaseResultMap" type="com.fang.mybatis.entity.Student">
        <id column="student_id" jdbcType="INTEGER" property="studentId" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="phone" jdbcType="VARCHAR" property="phone" />
        <result column="email" jdbcType="VARCHAR" property="email" />
        <result column="sex" jdbcType="TINYINT" property="sex" />
        <result column="locked" jdbcType="TINYINT" property="locked" />
        <result column="gmt_created" jdbcType="TIMESTAMP" property="gmtCreated" />
        <result column="gmt_modified" jdbcType="TIMESTAMP" property="gmtModified" />
    </resultMap>

    <sql id="Base_Column_List">
      student_id, name, phone, email, sex, locked, gmt_created, gmt_modified
    </sql>

    <select id="selectAll" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from student
    </select>

    <select id="selectByPrimaryKey"  parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from student
        where student_id=#{student_id, jdbcType=INTEGER}
    </select>

</mapper>

2. Entity class serialization

Student Implements Serialization Interface

package com.fang.mybatis.entity;

import java.io.Serializable;
import java.util.Date;

public class Student implements Serializable {
    private static final long serialVersionUID = -4852658907724408209L;

    private Integer studentId;

    private String name;

    private String phone;

    private String email;

    private Byte sex;

    private Byte locked;

    private Date gmtCreated;

    private Date gmtModified;
    /**
     * The following sections are setter s and getter s, omitted
     */
    public Integer getStudentId() {
        return studentId;
    }

    public void setStudentId(Integer studentId) {
        this.studentId = studentId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        //this.name = (name == null) ? null : name.trim();
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
        //this.phone = (phone == null) ? null : phone.trim();
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
        //this.email = (email == null) ? null : email.trim();
    }

    public Byte getSex() {
        return sex;
    }

    public void setSex(Byte sex) {
        this.sex = sex;
    }

    public Byte getLocked() {
        return locked;
    }

    public void setLocked(Byte locked) {
        this.locked = locked;
    }

    public Date getGmtCreated() {
        return gmtCreated;
    }

    public void setGmtCreated(Date gmtCreated) {
        this.gmtCreated = gmtCreated;
    }

    public Date getGmtModified() {
        return gmtModified;
    }

    public void setGmtModified(Date gmtModified) {
        this.gmtModified = gmtModified;
    }

    @Override
    public String toString() {
        return "Student{" +
                "studentId=" + studentId +
                ", name='" + name + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                ", sex=" + sex +
                ", locked=" + locked +
                ", gmtCreated=" + gmtCreated +
                ", gmtModified=" + gmtModified +
                '}';
    }
}

3. StudentMapperTest.java Add the following code

 @Test
    public void secendLevelCacheTest() {

        // Get SqlSession object
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //  Get Mapper Object
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        // Query object id=2 using Mapper interface's corresponding method
        Student student = studentMapper.selectByPrimaryKey(2);
        // Update the name of the object
        student.setName("Tea with milk");
        // Use the same SqlSession query id=2 again
        Student student1 = studentMapper.selectByPrimaryKey(2);
        /* Assert.assertEquals("Milk tea ", student1.getName()";
        // If the same SqlSession uses the cache, it will get the same objects
        Assert.assertEquals(student, student1);*/

        sqlSession.close();

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
        Student student2 = studentMapper1.selectByPrimaryKey(2);
        Student student3 = studentMapper1.selectByPrimaryKey(2);
        Assert.assertEquals("Tea with milk", student2.getName());
        Assert.assertNotEquals(student3, student2);

        sqlSession1.close();
    }

Execute the above code to get the following results

 

 

Phase 1:

1. In the first SqlSession, the student object is queried and a SQL statement is sent.
2.student changed the name property; (second level cache is looked up first, then first level cache, then database; even in a sqlSession, second level cache is looked up first)
3.SqlSession queries the student1 object again and does not send a SQL statement at this time. It prints "Cache Hit Ratio" in the log to represent the use of the secondary cache, but does not hit it. Then it looks up the first level cache and finds it in the first level cache.
4. Because it is a first-level cache, the two objects are the same at this time.
5. sqlSession.close() is called, where the data is serialized and maintained in the secondary cache.

Stage 2:

1. Create a new SqlSession object;
2. Query out the student2 object, take the data directly from the secondary cache, so no SQL statement is sent, this is the third time 3. Query, but only one hit, so hit ratio 1/3 = 0.333333;

4. Query out the student3 object, take the data directly from the secondary cache, so no SQL statement is sent, this is the fourth time to query, this time plus one hit a total of two times, so the hit ratio is 2/4=0.5;
5. Because readOnly="true", student2 and student3 are deserialized to be the same instance.
 

2.2.4.Detailed cache tag configuration

Looking at the dtd file, you can see the following constraints:

<!ELEMENT cache (property*)>
<!ATTLIST cache
type CDATA #IMPLIED
eviction CDATA #IMPLIED
flushInterval CDATA #IMPLIED
size CDATA #IMPLIED
readOnly CDATA #IMPLIED
blocking CDATA #IMPLIED
>

You can see from this:

Any number of property child elements can appear in the cache;
cache has some optional attribute types, eviction, flushInterval, size, readOnly, blocking.

1.type

Typee specifies the implementation type of the cache, PERPETUAL by default, corresponding to the cache implementation class org.apache.ibatis.cache.impl.PerpetualCache of mybatis itself.

Subsequently, if we want to implement our own cache or use third-party caches, we need to change here.

2.eviction

eviction corresponds to a recycling policy, which defaults to LRU.
LRU: least recently used, removing objects that have not been used for the longest time.
FIFO: FIFO removes objects in the order in which they enter the cache.
SOFT: Soft reference to remove objects based on garbage collector status and soft reference rules.
WEAK: Weak reference, removing objects based on garbage collector status and weak reference rules.

3.flushInterval

flushInterval corresponds to the refresh interval, in milliseconds. The default value is not set, that is, there is no refresh interval, and the cache refreshes only when the statement is refreshed.
If set, the corresponding time will expire and the data will need to be retrieved from the database for a second query.

4.size

size corresponds to the number of references, that is, the most cached object data, defaulting to 1024.

5.readOnly

readOnly is read-only and defaults to false
false: Readable and writable, which gets a copy of the cached object by deserialization when the object is created. Therefore, it is slower but safer.
true: Read-only, read-only caching returns the same instance of the cached object to all callers. Therefore, performance is good, but if the object is modified, it may cause problems with the program.

6.blocking

Blocking is blocking and the default is false. When true is specified, it is encapsulated using BlockingCache.
Using BlockingCache locks the corresponding Key when querying the cache, and releases the lock if the cache hits, or it releases the lock after querying the database, which prevents multiple threads from querying data concurrently.

 

summary

Because the cache is refreshed when updating, you need to be aware of the occasions when queries are frequently used and updates are low, that is, select is often used, delete, insert, update is relatively rare.
Caches are in namespace units, and operations under different namespaces do not affect each other. But refreshing the cache is refreshing the entire namespace cache, that is, if you update one, the entire cache is refreshed. It is best to refresh in "Only form operations."The namespace of the table is cached, and operations on the table are in the namespace. Otherwise, data inconsistencies may occur.

Posted by angershallreign on Sun, 26 Sep 2021 10:19:44 -0700