Simple use of Level 1 and Level 2 caches in Mybatis Basic Learning

Keywords: Java MySQL Mybatis

Preface:

Hello, ladies and gentlemen, I'm running like a snail rz. Of course you can call me Snail Jun. I'm a beginner who has studied Java for more than half a year. At the same time, I have a great dream that I will become an excellent Java architect one day.

This Mybatis Basic Learning series is used to record the whole process of my learning the fundamentals of the Mybatis framework (this series was written with reference to the latest Mybatis tutorial from Station B. Since it was sorted out before, but it was not published at that time, there may be errors in some places, I hope you can correct them in time!)

After that, I will try to update this series as soon as possible in two days, and those who have not yet learned the Mybatis3 framework can refer to my blog to learn it. Of course, the little buddy who has studied can also review the basics with me by the way. Finally, I hope I can make progress with you! Come on! Boys!

Special Reminder: If you are interested in the Mybatis Basic Learning Series, you can read this series of future blogs:
First: The Beginning of Mybatis Basic Learning Mybatis
Article 2: The first Mybatis program for basic Mybatis learning
Article 3: CRUD Addition and Deletion Check for Mybatis Basic Learning
Article 4: Map and Fuzzy Query for Mybatis Basic Learning
Article 5: Configuration Analysis of Mybatis Basic Learning (Previous)
Article 6: Configuration Analysis of Mybatis Basic Learning (Next)
Article 7: Mybatis Basic Learning uses ResultMap to resolve field name inconsistencies
Article 8: Simple use of the Mybatis Basic Learning Log Factory
Article 9: Simple use of data paging for basic Mybatis learning
Article 10: Development of Use Notes for Mybatis Basic Learning
Article 11: Simple use of Lombok for Mybatis Basic Learning
Article 12: Mybatis Basic Learning Many-to-One Relationship Processing
Article 13: One-to-many relationship processing in Mybatis basic learning
Article 14: Simple use of dynamic SQL for basic Mybatis learning

Today we are at the 14th stop of Mybatis Basic Learning: Simple Use of Dynamic SQL. Say nothing more. Let's start today's learning.

Simple use of Level 14 and Level 2 caches

14.1 Introduction

Have you ever thought about whether it would be more resource intensive to connect to a database every time you query data? But if we give the result of a query a temporary place to get it directly, such as memory (cache); When you query the same data again, you can go directly to the cache instead of the database! So what exactly is caching? Here are some basic caching concepts

14.1.1 What is caching

Cache refers to the temporary data in memory, which is used to query the data frequently queried by users in the cache (memory). The query data does not need to be queried from the disk (relational database file) to query from the cache, which improves the query efficiency and solves the problem of high concurrent system performance.

14.1.2 Why use caching

Here is a system design diagram. Between multiple servers and databases, we can use the cache to reduce the number of interactions with the database, reduce overhead, and improve system efficiency

14.1.3 What kind of data can be cached

Caching can be used for data that is queried frequently and changes infrequently

After a brief introduction to the basic concept of caching, let's take a look at how caching is defined in Mybatis

14.2 MyBatis Cache

14.2.1 What is MyBatis Cache

MyBatis includes a very powerful query cache feature that makes it very easy to customize and configure the cache, which greatly improves query efficiency

14.2.2 Two-level cache in MyBatis

MyBatis defines two levels of caching by default: first-level caching and second-level caching

  • By default, only one level of cache is turned on (first level cache is SqlSession level cache, also known as local cache)
  • The secondary cache, which is namespace-based, requires manual opening and configuration
  • To improve scalability, Mybatis defines the cache interface Cache, which allows you to customize secondary caching by implementing the Cache interface

Cleanup Policy in 14.2.3 MyBatis

There are four main cleanup strategies in Mybatis:

  • LRU (least recently used): Remove objects that have not been used for the longest time
  • FIFO: Remove the object in the order it enters the cache
  • SOFT: Remove objects based on garbage collector status and soft reference rules
  • WEAK: Remove objects more actively based on garbage collector status and weak reference rules

Note: The default cleanup policy in Mybatis is LRU

After a brief overview of cache definitions and cleanup strategies in Mybatis, let's focus on Level 1 and Level 2 caches

14.3 Level 1 Cache

14.3.1 Level 1 Cache Concepts

  • Level 1 cache is also called local cache, SqlSession level cache
  • Data queried during the same session as the database is placed in the local cache
  • If you need to get the same data later, take it directly from the cache without having to query the database

14.3.2 Level 1 Cache Use

1. Create projects and import resource dependencies

  • Create a Maven project to import resource-dependent jar packages into the pom.xml file
<!-- Import Lombok resources dependence -->
<dependencies>
    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
    </dependency>
</dependencies>

2. Write core profile

1-1 Write db.properties configuration file
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
pwd=123456
1-2 Write mybatis-config.xml configuration file
<?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: Core Configuration -->
<configuration>
    
    <!-- Introduce external profiles: use external profiles first -->
    <properties resource="db.properties"></properties>
    
    <settings>
        <!-- Set Standard Log Output -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    <!-- By aliasing the package -->
    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>
    
    <!-- Set default environment as development environment -->
    <environments default="development">
        <!-- Set up an environment for development -->
        <environment id="development">
            <!-- transactionManager: Represents a transaction manager (MyBatis The default manager for is JDBC) -->
            <transactionManager type="JDBC"/>
            <!-- dataSource: Represents a data source, Main role: Connect to database
                 (Be careful: MyBatis The default data source type is POOLED,This is a pooled connection) -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${pwd}"/>
            </dataSource>
        </environment>
        
    </environments>
    
    <!-- Binding interface: Use class File Binding Registration-->
    <mappers>
        <!-- Same name as package, Recommended Use class file, Notes can also be used to complement interface profiles -->
        <mapper class="com.kuang.dao.UserMapper"/>
    </mappers>
    
</configuration>

3. Write entity and tool classes

3-1 Writing User Entity Classes
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data // Using the @Data annotation, introduce parameterless constructs, get and set methods, and toString methods
@AllArgsConstructor // Introducing a parametric construction method
@NoArgsConstructor // Introducing parameterless construction method
public class User {
    
    private int id; // User Number
    private String name; // User name
    private String pwd; // Password
    
}
3-2 Writing the MybatisUtils tool class
package com.kuang.utils;

 /**
  * SqlSessionFactoryBuilder -- Build a factory
  * --> sqlSessionFactory -- Production sqlSession
  * --> sqlSession  
  */
public class MybatisUtils {
    
    // Get SqlSessionFactory
    private static SqlSessionFactory sqlSessionFactory;
    
    // Static Method Body
    static {
        try {
            // Read Configuration File
            String resource = "mybatis-config.xml";
            // Parse Profile Stream
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // Get Factory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
   /**
    * SqlSession Provides all the methods required to execute SQL commands in a database
    */
   public static SqlSession getSqlSession() {
       // Set parameter to true for automatic submission
       return sqlSessionFactory.openSession(true);
   }
    
}

4. Write Mapper interfaces and configuration files

4-1 Write Mapper interface
package com.kuang.dao;

public interface UserMapper {
    
    // Specify user based on id query
    User queryUserById(@Param("id") int id);
    
}
4-2 Write UserMapper.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.kuang.dao.UserMapper">
    
	<!-- adopt id Query specified user information -->
    <!-- parameterType="_int": int Type parameters can be aliased to "_id",You can also omit
         resultType="user": Aliasing in core profile to use -->
    <select id="queryUserById"  resultType="user">
        Select * from user where id = #{id}
    </select>
    
</mapper>

5. Write test classes and test results

5-1 Writing MyTest test classes
  • Test a Session to query the same record twice
package com.kuang.dao;

public class MyTest { 
    
	// Query specified user information through id
    @Test
    public void queryUserById() {
        
        // Get sqlSession object
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // Get UserMapper interface
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // Call the queryUserById method to query user information with id 1
        User user = mapper.queryUserById(1);
        System.out.println(user);
        System.out.println("==================");
        // Call the queryUserById method to query user information with id 2
        User user2 = mapper.queryUserById(2);
        System.out.println(user2);
        // Determine if the users of the two queries are equal
        System.out.println(user==user2);
        
        // Close sqlSession object
        sqlSession.close();
    }
    
}
5-2 Test Results

Result: Successfully queried two user information with id 1!

6. Test Summary

After observation, the precompiled sql executed only once, but both records were queried out, so the cache was not refreshed during this process. Since the output value of user==user2 is "true", it can be determined that the two user information are the same. Therefore, two identical records were successfully queried in the same Session!

14.3.3 Cache Failure

1. Write MyTest test class

1-1 Additions and deletions in a Session
  • Testing uses different methods in a Session, such as querying and modifying users
package com.kuang.dao;

public class MyTest {
    
    // Query and modify user information
    @Test
    public void updateUser() {
        
        // Get sqlSession object
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // Get UserMapper interface
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // Call the queryUserById method to query user information with id 1
        User user = mapper.queryUserById(1);
        System.out.println(user);
        // Call method updateUser to modify user information with id 6
        mapper.updateUser(new User(6,"Jacky Cheung","zxy123456"));
        System.out.println("==================");
        // Call the queryUserById method to query the user information with id 1 again
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        // Determine if the users of the two queries are equal
        System.out.println(user==user2);
        
        // Close sqlSession object
        sqlSession.close();
    }
    
}
1-2 Manual Cleanup Cache
  • Test a Session to query the same record twice, then set up the Clear Cache manually
package com.kuang.dao;
public class MyTest {    
    
	// Query specified user information through id
    @Test
    public void queryUserById() {

        // Get sqlSession object
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        // Get UserMapper interface
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        // Call the queryUserById method to query user information with id 1
        User user = mapper.queryUserById(1);
        System.out.println(user);
         // Clean up the cache manually
        sqlSession.clearCache();
        System.out.println("==================");
        // Call the queryUserById method to query user information with id 1
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);
        // Determine if the users of the two queries are equal
        System.out.println(user==user2);
        
        // Close sqlSession object
        sqlSession.close();
    }
    
}

2. Test results and analysis

2-1 Modification and Query in a Session

Result: User information with id 1 was successfully queried twice, and user information with id 6 was updated!

Analysis:

Although user information with id 1 was found twice, the precompiled sql executed twice before and after updating user information with id 6. So you can be sure that the cache was refreshed in the process!

2-2 Manual Cleanup Cache

Result: Successfully queried two user information with id 1!

Analysis:

Although user information with id 1 was queried twice, after each information was queried, the sql statement was precompiled again, so the cache was refreshed during the process

3. Test Summary

Cache failure:

  • Query different information
  • Add-delete operations may change the original data, so the cache must be refreshed
  • Query different Mapper.xml
  • Clean up the cache manually

14.3.4 Level 1 Cache Summary

The first level cache is turned on by default and only works in one SqlSession, that is, to get the segment connected to a closed connection. The first level cache is equivalent to a Map, which you can fetch directly when you use it.

14.4 Level 2 Cache

14.4.1 Secondary Cache Concepts

Second-level caches are also called global caches. Because the scope of first-level caches is too low, second-level caches are created, which are based on namespace-level caches, a space name, corresponding to a second-level cache.

14.4.2 Secondary Cache Working Mechanism

  • A session queries a piece of data, which is placed in the first level cache of the current session
  • If the current session is closed, the corresponding first-level cache for this session will be gone. But all we want is that the session is closed and the data in the first level cache is saved in the second level cache
  • New session queries for information to get content from the secondary cache
  • Data found by different mapper s will be placed in their own cache (map)

14.4.3 Open Level 2 Cache Step

By default, only local session caching (i.e., first-level caching) is enabled, which only caches data in one session. To enable global second-level caching, you only need to add tags to the SQL map file.

1. Turn on global cache

  • Explicitly turn on global caching in the core configuration file mybatis-config.xml file
<!-- Explicitly open global cache -->
<setting name="cacheEnabled" value="true"/>

2. Turn on the secondary cache in the corresponding Mapper.xml

  • Use the </cache>tag in the UserMapper.xml mapping file to turn on the secondary cache
2-1 Use original label
<!-- In the current Mapper.xml Use secondary caching in files -->
<cache/>
2-2 Use custom tags
<!-- In the current Mapper.xml Use secondary caching in mapping files -->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
<!-- adopt id Query specified user information(useCache="true": Represents using a cache -->
<select id="queryUserById"  resultType="user" useCache="true">
    Select * from user where id = #{id}
</select>

cache tag parameter explanation:

eviction="FIFO": Cache cleanup policy is FIFO (i.e. FIFO)
flushInterval="60000": refresh interval 60 seconds
size="512": up to 512 references that can store result objects or lists
readOnly="true": the returned object is read-only

14.4.4 Secondary Cache Usage Test

1. Turn on global cache in core profile

  • Explicitly turn on global caching in the core configuration file mybatis-config.xml file
<?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: Core Configuration -->
<configuration>
    
    <!-- Introduce external profiles: use external profiles first -->
    <properties resource="db.properties"></properties>
    
    <settings>
        <!-- Set Standard Log Output -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    <!-- Explicitly open global cache -->
    <setting name="cacheEnabled" value="true"/></settings>
    
    <!-- By aliasing the package -->
    <typeAliases>
        <package name="com.kuang.pojo"/>
    </typeAliases>
    
    <!-- Set default environment as development environment -->
    <environments default="development">
        <!-- Set up an environment for development -->
        <environment id="development">
            <!-- transactionManager: Represents a transaction manager (MyBatis The default manager for is JDBC) -->
            <transactionManager type="JDBC"/>
            <!-- dataSource: Represents a data source, Main role: Connect to database
                 (Be careful: MyBatis The default data source type is POOLED,This is a pooled connection) -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${pwd}"/>
            </dataSource>
        </environment>
        
    </environments>
    
    <!-- Binding interface: Use class File Binding Registration-->
    <mappers>
        <!-- Same name as package, Recommended Use class file, Notes can also be used to complement interface profiles -->
        <mapper class="com.kuang.dao.UserMapper"/>
    </mappers>
    
</configuration>

2. Open the secondary cache in the corresponding Mapper.xml mapping file

2-1 Do not use secondary caching
<?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.kuang.dao.UserMapper">
    
    <!-- adopt id Query specified user information -->
    <!-- parameterType="_int": int Type parameters can be aliased to "_id", You can also omit
         resultType="user": Aliasing in core profile to use user
         useCache="true": Use Cache -->
    <select id="queryUserById"  resultType="user">
        Select * from user where id = #{id}
    </select>
    
</mapper>
2-2 Open secondary cache using labels only
  • Open the secondary cache in the UserMapper.xml mapping file with a custom tag
<?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.kuang.dao.UserMapper">
    
    <!-- In the current Mapper.xml Use secondary caching in mapping files, Cache only works on cache Statements in the mapping file where the label is located -->
    <!-- eviction="FIFO": Cache Cleanup Policy is FIFO(FIFO)
         flushInterval="60000": Refresh interval is 60 seconds
         size="512": 512 references that can store up to result objects or lists
         readOnly="true": The returned object is read-only -->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    
    <!-- adopt id Query specified user information -->
    <!-- parameterType="_int": int Type parameters can be aliased to "_id",You can also omit
         resultType="user": Aliases in core profile for direct use user
         useCache="true": Use Cache -->
    <select id="queryUserById"  resultType="user" useCache="true">
        Select * from user where id = #{id}
    </select>
    
</mapper>

3. Write MyTest test class

3-1 does not use secondary caching
package com.kuang.dao;

public class MyTest {    
    
   // Query specified user information through id
    @Test
    public void queryUserById2() {
        
        // Get two sqlSession objects, respectively
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        
        // Get two Mapper interface objects, respectively
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        
        // Call the queryUserById method to query user information with id 1
        User user = mapper.queryUserById(1);
        // Call the queryUserById method to query the user information with id 1 again
        User user2 = mapper2.queryUserById(1);
        
        // Print user information
        System.out.println(user);
        System.out.println(user2);
        // Determine if two user information is equal
        System.out.println(user==user2);
        
        // Close two sqlSession objects separately
        sqlSession.close();
        sqlSession2.close();
    }
    
}
3-2 uses secondary caching
package com.kuang.dao;

public class MyTest {  
    
   // Query specified user information through id
    @Test
    public void queryUserById2() {
        
        // Get two sqlSession objects, respectively
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();

        // Get the first Mapper interface object
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // Call the queryUserById method to query user information with id 1
        User user = mapper.queryUserById(1);
        // Print user information
        System.out.println(user);
        // Close the first sqlSession object
        sqlSession.close();

        // Get the second Mapper interface object
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        // Call the queryUserById method to query the user information with id 1 again
        User user2 = mapper2.queryUserById(1);
        // Print user information
        System.out.println(user2);
        // Determine if two user information is equal
        System.out.println(user==user2);
        // Close the second sqlSession object
        sqlSession2.close();
        
    }
    
}

4. Test results

4-1 Do not use secondary caching

Result: User information with id 1 was successfully queried twice!

Analysis:

Looking at the results, the precompiled sql was executed twice, so the cache was refreshed once after each query for information

4-2 Open secondary cache using labels only

Result: Caused by: ava.io.NotSerializableException: com.kuang.pojo.User

Analysis:

The reason for the error is that the User entity class under the com.kuang.pojo package is not serialized

Conclusion: Therefore, it is necessary to serialize the entity class or an error will be reported

4-3 Open secondary cache using custom cache tags

Result: User information with id 1 was also successfully queried twice!

Analysis:

The precompiled sql executes only once after using the secondary cache; When the first user information query closes sqlSession, the first level cache is closed, but its data is saved in the second level cache. So the second time you query the same user information, you get it directly from the secondary cache, you don't need to access the database and execute precompiled sql statements

14.4.5 Resolve Entity Class Unserialization

1. Modify User entity class

  • Let User entity classes implement Serializable interfaces for serialization
package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;

@Data // Using the @Data annotation, introduce parameterless constructs, get, set, toString, and so on
@AllArgsConstructor // Introduce a parametric construction method using the @AllArgsConstructor annotation
@NoArgsConstructor // Re-introduce parameterless construction using the @NoArgsConstructor annotation

// Let User entity classes implement Serializable interfaces for serialization
public class User implements Serializable {
    
    private int id;
    private String name;
    private String pwd;
    
}

2. Write MyTest test class

  • Set up two sqlSession objects to query the same user data
package com.kuang.dao;

public class MyTest {    
    
   // Query specified user information through id
    @Test
    public void queryUserById2() {
        
        // Get two sqlSession objects, respectively
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();

        // Get the first Mapper interface object
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // Call the queryUserById method to query user information with id 1
        User user = mapper.queryUserById(1);
        // Print user information
        System.out.println(user);
        // Close the first sqlSession object
        sqlSession.close();

        // Get the second Mapper interface object
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        // Call the queryUserById method to query the user information with id 1 again
        User user2 = mapper2.queryUserById(1);
        // Print user information
        System.out.println(user2);
        // Determine if two user information is equal
        System.out.println(user==user2);
        // Close the second sqlSession object
        sqlSession2.close();
    }
    
}

3. Test results

Result: User information with id 1 was successfully queried twice!

Analysis:

After the first query for user information with id 1, the local cache is closed and the data is stored in the secondary cache. When you query this user information again, you do not need to execute precompiled SQL, query the secondary cache, and fetch the user information directly. But when you look closely, you see that the result of "user==user2" is false because the user's information is taken out of the secondary cache and the memory address changes, so the result is false

14.5.6 Level 2 Cache Usage Summary

  • Valid under the same Mapper as long as the secondary cache is turned on
  • All data is first placed in the first level cache
  • Committed to the secondary cache only when the session is committed or closed

Okay, that's the end of today's learning about simple use of Level 1 and Level 2 caches. You are welcome to actively study and discuss with your friends. If you like, you can give Jun Snail a little attention, and by the way, you can do it one click three times. Let's meet next time. Bye!

Reference video link: Mybatis latest complete tutorial IDEA version is easy to understand

Posted by karlovac on Fri, 26 Nov 2021 16:46:32 -0800