[MyBatis notes] about the L1 cache and L2 cache of mabati

Before testing the L1 cache and L2 cache respectively, make some preparations for the class and database.

PoJo class:

  Employee.java

package com.atguigu.mybatis.bean;

/**
 * @author xiaoyi
 * @create 2021-11-21-21:06
 */
public class Employee {
    private Integer id;
    private String lastName;
    private char gender;
    private String email;
    private Dept dept;

    public Employee() {
    }

    public Employee(Integer id, String lastName, char gender, String email) {
        this.id = id;
        this.lastName = lastName;
        this.gender = gender;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", gender=" + gender +
                ", email='" + email + '\'' +
                ", dept=" + dept +
                '}';
    }
}

  Dept.java

package com.atguigu.mybatis.bean;

import java.util.List;

/**
 * @author xiaoyi
 * @create 2021-11-26-19:05
 */
public class Dept {
    private Integer deptId;
    private String deptName;
    private List<Employee> employees;

    public Dept() {
    }

    public Dept(Integer deptId, String deptName, List<Employee> employees) {
        this.deptId = deptId;
        this.deptName = deptName;
        this.employees = employees;
    }

    public Integer getDeptId() {
        return deptId;
    }

    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }

    public String getDeptName() {
        return deptName;
    }

    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptId=" + deptId +
                ", deptName='" + deptName + '\'' +
                ", employees=" + employees +
                '}';
    }
}

Interface class:

EmployeeMapperPlus.java / / enhanced class of EmployeeMapper: you can query the Department information of an employee

 

package com.atguigu.mybatis.dao;

import com.atguigu.mybatis.bean.Employee;

/**
 * @author xiaoyi
 * @create 2021-11-25-21:04
 */
public interface EmployeeMapperPlus {

    Employee getEmployeeByIdPlus(Integer id);

    Employee getEmployeeAndDeptByIdMethod1(Integer id);

    Employee getEmployeeAndDeptByIdMethod2(Integer id);

    Employee getEmployeeAndDeptByStep(Integer id);
}

 

  DeptMapper.java

package com.atguigu.mybatis.dao;

import com.atguigu.mybatis.bean.Dept;

/**
 * @author xiaoyi
 * @create 2021-11-27-0:05
 */
public interface DeptMapper {
    Dept getDeptById(Integer id);

    Dept getDeptAndEmployeesById(Integer id);

    Dept getDeptAndEmployeesByStep(Integer id);
}

 

Mapper class:

EmployeeMapperPlus.xml / / enhanced version of EmployeeMapper class: the attribute of select uses resultMap

<?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.atguigu.mybatis.dao.EmployeeMapperPlus">


    <!--
        id:by resultMap Set unique id,Convenient reference
        type:designated Java type
    -->
    <resultMap id="myEmp1" type="com.atguigu.mybatis.bean.Employee">
        <!--
        <id />: Specifies the encapsulation rule for the primary key column
            column: Which column is specified in the database
            property: Specify the corresponding JavaBean attribute
        -->
        <id column="id" property="id"/>
        <!-- Define encapsulation rules for common columns -->
        <result column="last_name" property="lastName"/>
        <!--Other unspecified columns are automatically encapsulated-->
    </resultMap>

    <!-- use resultMap Custom encapsulation rules-->
    <select id="getEmployeeByIdPlus" resultMap="myEmp1">
        select * from tbl_employee where id = #{id}
    </select>


    <!--
        Requirement 1: according to id Query employee information and department information
            Method 1: cascade query
    -->
    <!--
        Method 1:
        id: by resultMap Label assignment unique id Property for easy reference
        type: Specifies the object type returned by the query result
     -->
    <resultMap id="MyDifEmp1" type="com.atguigu.mybatis.bean.Employee">
        <id column="id" property="id" />
        <result column="last_name" property="lastName"/>
        <!-- Cascade assignment -->
        <result column="deptId" property="dept.deptId" />
        <result column="deptName" property="dept.deptName"/>
    </resultMap>
    <select id="getEmployeeAndDeptByIdMethod1" resultMap="MyDifEmp1">
        SELECT e.id id,last_name,gender,email,d_id deptId,dept_name deptName
        FROM tbl_employee e JOIN tbl_dept d ON  e.d_id = d.id
        WHERE e.id = #{id}
    </select>

    <!--
            Mode 2: use association Label specifies the association
    -->
    <resultMap id="MyDifEmp2" type="com.atguigu.mybatis.bean.Employee">
        <id column="id" property="id" />
        <result column="last_name" property="lastName"/>
        <!--association Label specifies the encapsulation rule of the associated object
            property:Associated object properties
            javaType: Specifies the current property type
            Note: this way javaType Must be specified, indicating supervisor The type of is Teacher,Otherwise, an error will be reported
        -->
        <association property="dept" javaType="com.atguigu.mybatis.bean.Dept">
            <id column="deptId" property="deptId"/>
            <result column="deptName" property="deptName"/>
        </association>
    </resultMap>
    <select id="getEmployeeAndDeptByIdMethod2" resultMap="MyDifEmp2">
        SELECT e.id id,last_name,gender,email,d_id deptId,dept_name deptName
        FROM tbl_employee e JOIN tbl_dept d ON  e.d_id = d.id
        WHERE e.id = #{id}
    </select>

    <!--
        Method 3: use association Label distribution query

            Advanced: lazy loading (delayed loading): written sql Statement, the corresponding attribute will be loaded only when the corresponding attribute needs to be called sql sentence
                    In global profile mybatis-config.xml Medium setting
                    <setting name="lazyLoadingEnabled" value="true"/>
                    <setting name="aggressiveLazyLoading" value="false"/>
    -->
    <resultMap id="MyDifEmpByStep" type="com.atguigu.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!--
            association Define encapsulation rules for associated objects:
            select: Indicates that the current property is a call select Specified method
            according to select Method assignment within property Associated objects within
            Process: Using select Specified method (passed in) column Query the specified object and encapsulate it to property attribute
        -->
        <association property="dept"
                     select="com.atguigu.mybatis.dao.DeptMapper.getDeptById" column="d_id"/>
    </resultMap>
    <select id="getEmployeeAndDeptByStep" resultMap="MyDifEmpByStep">
        SELECT *
        FROM tbl_employee
        WHERE id = #{id}
    </select>

</mapper>

  DeptMapper.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.atguigu.mybatis.dao.DeptMapper">

    <select id="getDeptById" resultType="com.atguigu.mybatis.bean.Dept">
        select id deptId,dept_name deptName from tbl_dept where id = #{id}
    </select>

    <!--
        Requirement 2: according to Department id Query the corresponding department information and all employee information of the Department
            Method 1: use collection Label nested query
    -->
    <resultMap id="getDeptAndEmployeesById" type="com.atguigu.mybatis.bean.Dept">
        <id column="id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
        <!--
            use collection Label nested query
            property: Specifies the name of the collected object (corresponding to JavaBean Object name of the encapsulated Collection)
            ofType: Specifies the type in the collection element
        -->
        <collection ofType="com.atguigu.mybatis.bean.Employee" property="employees">
        <!-- Define the encapsulation rules for this collection element -->
            <id column="eid" property="id" />
            <result column="last_name" property="lastName" />
            <result column="gender" property="gender" />
            <result column="email" property="email" />
        </collection>
    </resultMap>
    <select id="getDeptAndEmployeesById" resultMap="getDeptAndEmployeesById">
        SELECT d.id id,d.dept_name dept_name,e.id eid,e.last_name last_name,e.gender gender,e.email email
        FROM tbl_dept d LEFT JOIN tbl_employee e
        ON d.id = e.d_id
        WHERE d.id = #{id}
    </select>

    <!--
        Method 2: use collection Label step-by-step query
    -->
    <resultMap id="getDeptAndEmployeesByStep" type="com.atguigu.mybatis.bean.Dept">
        <id column="id" property="deptId"/>
        <result column="dept_name" property="deptName"/>
        <!--
            according to column(The value of a column (name) found below is passed into the select In the specified method and returns the query result to property Attribute
        -->
        <collection property="employees"
                    select="com.atguigu.mybatis.dao.EmployeeMapperPlus.getEmployeeByIdPlus" column="id"/>
    </resultMap>
    <select id="getDeptAndEmployeesByStep" resultMap="getDeptAndEmployeesByStep">
        select id,dept_name from tbl_dept where id = #{id}
    </select>
</mapper>

Preparation of database:

  tbl_dept table

CREATE TABLE tbl_dept(
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 dept_name VARCHAR(255)
);

 

  tbl_employee table

CREATE TABLE tbl_employee(
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 last_name VARCHAR(255),
 gender CHAR(1),
 email VARCHAR(255)
);

ALTER TABLE tbl_employee ADD COLUMN d_id INT(11);

ALTER TABLE tbl_employee ADD CONSTRAINT fk_emp_dept
FOREIGN KEY(d_id) REFERENCES tbl_dept(id);

 

 

 

 

 

Test:  

package com.atguigu.mybatis.test;

import com.atguigu.mybatis.bean.Dept;
import com.atguigu.mybatis.bean.Employee;
import com.atguigu.mybatis.dao.DeptMapper;
import com.atguigu.mybatis.dao.EmployeeMapper;
import com.atguigu.mybatis.dao.EmployeeMapperPlus;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;


/**
 * @author xiaoyi
 * @create 2021-11-25-21:12
 */
public class MyBatisPlusTest {
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resources = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        return sqlSessionFactory;
    }
/**
     * Use of L1 cache
     */
    @Test
    public void testFirstLevelCache() {
        SqlSession openSession = null;
        try {
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            openSession = sqlSessionFactory.openSession();
            EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
            Employee employee1 = mapper.getEmployeeAndDeptByIdMethod1(1); // Query employee id Employee information and department information for 1

            System.out.println(employee1);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            openSession.close();
        }
    }
}

The running result is:

At this time, it is found that a call query statement will parse the SQL statement once.

What happens if you query the employee information and department information with employee id 1 again? As follows:

 

 

  The output results are as follows:

 

 

  You will be surprised to find that although the employee information of the two output statements is consistent, the SQL statement is still parsed only once, and the first address values of the obtained employee1 and employee2 objects are the same (the obtained two objects are the same object).

  It can be concluded that employee2 is obtained in the cache and will not query the SQL statement passed in to the database again.

Level 1 cache (local cache): it refers to the cache of sqlsession objects in mybatis. After we execute the query, the query results will be stored in an area provided by sqlsession. The structure of the area is a Map. When we query the same data again, mybatis will first query whether there is any in sqlsession. If so, it will be used directly, When the sqlsession object disappears, the first level cache of mybatis disappears. At the same time, the first level cache is the cache within the sqlsession range. When the modify, add, delete, commit(),close and other methods of sqlsession are called, the first level cache will be emptied.

L1 cache invalidation (if the current L1 cache is not used, the effect is that you need to send another query to the database):
  1. sqlSession is different
  2. The sqlSession is the same, but the query conditions are different. (this data is not available in the L1 cache at present)
  3. The sqlSession is the same, and the addition, deletion and modification are performed between the two queries (this addition, deletion and modification may have an impact on the current data)
  4. The same as sqlSession, the L1 cache is cleared manually (CACHE emptying: clearCache())

There is such a situation: if multiple users query the same record of the database, it is bound to need to open multiple sqlsessions to query and interact with the database many times. At this time, L1 cache will no longer be applicable. At this time, we need to introduce L2 cache.

 

L2 cache (global cache): L2 cache is a mapper level cache. Multiple sqlsessions operate the sql statement of the same mapper. Multiple sqlsessions can share L2 cache. L2 cache is cross sqlsessions. EmployeeMapperPlus has a L2 cache area

(by namespace), other mappers also have their own L2 cache area (by namespace). Each namespace mapper has a L2 cache area. If the namespaces of the two mappers are the same, the two mappers execute sql queries and find that the data will exist in the same L2 cache area.

Using L2 cache also requires some prerequisite work. As follows:

  1. Enable global L2 cache configuration: < setting name = "cacheenabled" value = "true" / >
  2. To configure L2 cache in mapper.xml:
  3. Our POJO needs to implement the serialization interface

1.

<!-- Global configuration -->
    <settings>
        <!-- Enable L2 cache -->
        <setting name="cachedEnabled" value="true"/>
    </settings>

2.

<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
    <!--
    eviction:Cache recycle policy:
        LRU – Least recently used: removes objects that have not been used for the longest time.
        FIFO – First in first out: removes objects in the order they enter the cache.
        SOFT – Soft reference: removes objects based on garbage collector status and soft reference rules.
        WEAK – Weak references: more actively remove objects based on garbage collector state and weak reference rules.
        The default is LRU. 
    flushInterval: Cache refresh interval
        How often is the cache emptied? It is not emptied by default. Set a millisecond value
    readOnly:Read only:
        true: Read only; mybatis It is considered that all operations to obtain data from the cache are read-only operations and will not modify the data. mybatis In order to speed up the acquisition speed, the reference of the data in the cache will be directly given to the user. Unsafe, fast
        false: Non read only: mybatis I think the obtained data may be modified. mybatis Will use serialization&Reverse sequence technology to clone a new data to you. Safe, slow
    size: How many elements are stored in the cache;
    type="": Specify the full class name of the custom cache;
        realization Cache Interface is enough.
    -->

3.

public class Employee implements Serializable{
}
public class Dept implements Serializable {
}

At this point, let's test whether L2 cache is effective.

Run code:

/**
     * Use of L2 cache
     */
    @Test
    public void testSecondLevelCache() {
        SqlSession openSession1 = null;
        SqlSession openSession2 = null;
        try {
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            openSession1 = sqlSessionFactory.openSession(); // Open for the first time sqlSession
            EmployeeMapperPlus mapper1 = openSession1.getMapper(EmployeeMapperPlus.class);
            Employee employee1 = mapper1.getEmployeeAndDeptByIdMethod1(2);
            System.out.println(employee1);

            openSession2 = sqlSessionFactory.openSession(); // Open the second time sqlSession
            EmployeeMapperPlus mapper2 = openSession2.getMapper(EmployeeMapperPlus.class);
            Employee employee2 = mapper2.getEmployeeAndDeptByIdMethod1(2);
            System.out.println(employee2);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            openSession1.close();
            openSession2.close();
        }
    }

Operation results:

 

  At this time, it is found that the SQL statement is still parsed twice under the same conditions.

We found the reason: two sqlSession sessions have accessed the database, and the SQL statement has been parsed twice. Therefore, the L1 cache is still here, and the L2 cache has not been accessed.

As mentioned above, to invalidate the L1 cache, you need to advance openSession1.close() to   Before mapper2.getEmployeeAndDeptByIdMethod1(2), take a look at the running results

Run code:

/**
     * Use of L2 cache
     */
    @Test
    public void testSecondLevelCache() {
        SqlSession openSession1 = null;
        SqlSession openSession2 = null;
        try {
            SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
            openSession1 = sqlSessionFactory.openSession(); // Open for the first time sqlSession
            EmployeeMapperPlus mapper1 = openSession1.getMapper(EmployeeMapperPlus.class);
            Employee employee1 = mapper1.getEmployeeAndDeptByIdMethod1(2);
            System.out.println(employee1);

            openSession1.close(); // take openSession1.close() Arrive early mapper2.getEmployeeAndDeptByIdMethod1(2) before


            openSession2 = sqlSessionFactory.openSession(); // Open the second time sqlSession
            EmployeeMapperPlus mapper2 = openSession2.getMapper(EmployeeMapperPlus.class);
            Employee employee2 = mapper2.getEmployeeAndDeptByIdMethod1(2);
            System.out.println(employee2);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            openSession2.close();
        }
    }

 

Operation results:

 

 

 

Posted by illuz1on on Sun, 28 Nov 2021 14:35:29 -0800