Cache mechanism under Mybatis concurrency

Cache mechanism under Mybatis concurrency

Mybatis level 1 and level 2 cache

Level 1 cache: It is enabled by default. For multiple identical operations in a session, only the first time will go to the database to query, and then directly go to the cache to get the data!
1. Query the record for the first time, and write the queried data into the cache
2. In the second query, first read the data from the cache. If there is data in the cache, return it directly instead of accessing the database.
3. If this session performs add, modify, delete, commit, close, clear the level 1 cache of the current session.

L2 cache: It is turned off by default.
1. Open the second level cache configuration: then the core configuration file mybatis-config.xml

         <setting name="cacheEnabled" value="true"/>

2. If the query operation in the mapping file needs to use the second-level cache, students can directly add a label to the top of the mapping file.
All queries in this mapping file will enable L2 cache!

3. The way of annotation to realize the second level cache
@CacheNamespace implements a second-level cache for all queries in the annotated interface

Note: The objects in the second level cache must implement the serialization interface. If there are too many cached objects, Mybatis can cache the objects in the file.

So what is the caching mechanism of mybatis concurrently? Next, start the test. This test uses the mysql database. In order to test comprehensively, the scope of this article is too long...

preparation stage:

Project structure:

mybatis tool class:

/**
 *  mybatis A session factory that provides static methods for acquiring and closing sessions
 */
public class MybatisSqlSessionFactoryUtils {
    private static SqlSessionFactory factory;

    static {
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            factory = builder.build(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     *  get session object
     * @return session object
     */
    public static SqlSession getSqlSession() {
        return factory.openSession();
    }

    /**
     *  release resources
     * @param sqlSession session object
     */
    public static void close(SqlSession sqlSession) {
        if (sqlSession != null) {
            sqlSession.close();
        }
    }

Database user table:

User wrapper class:

public class User implements Serializable {
    private static final long serialVersionUID = 1L;  //L2 cache usage
    private String loginName;
    private String userName;
    private String password;
    private Date createDate;

    public User() {
    }

    public int getId() {
        return id;
    }

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

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", loginName='" + loginName + '\'' +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                ", createDate=" + createDate +
                '}' + "\n";
    }

‚Äč dao layer:

@CacheNamespace	 //Used when the second level cache is enabled
public interface UserMapper {

    //query all users
    @Select("select * from tb_user")
    @Results({
            @Result(property = "id", column = "id"),
            @Result(property = "loginName", column = "login_name"),
            @Result(property = "userName", column = "user_name"),
            @Result(property = "createDate", column = "create_date")
    })
    List<User> findAll();

    //Modify user by id
    @Update("update tb_user set user_name = #{userName} where id = #{id}")
    @Results({
            @Result(property = "id", column = "id"),
            @Result(property = "loginName", column = "login_name"),
            @Result(property = "userName", column = "user_name"),
            @Result(property = "createDate", column = "create_date")
    })
    int update(User user);
}
  1. Commit transaction within this session:
/**
 *  1.Mybatis The first level cache of commit transactions within this session
 */
@Test
public void testCacheSelect() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisSqlSessionFactoryUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //Query the user table data for the first time
        List<User> users1 = userMapper.findAll();
        System.out.println(users1);

        User user = new User();
        user.setId(3);
        user.setUserName("marshal canopy");  //Changed Zhu Bajie to Marshal Tianpeng
        int row = userMapper.update(user);
        //commit transaction
        sqlSession.commit();
        System.out.println(row);

        System.out.println("----------------------------");

        //Query the user table data for the second time
        List<User> users2 = userMapper.findAll();
        System.out.println(users2);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        MybatisSqlSessionFactoryUtils.close(sqlSession);
    }
}

Console output:

Commit the transaction in this session: It is clearly seen through the log that sql is executed twice, that is, the cache is updated

  1. Mybatis Level 1 Cache Different sessions submit transactions concurrently:
/**
 *  2.Mybatis Level 1 cache Different sessions submit transactions concurrently:
 */
public void testCacheSelect1() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisSqlSessionFactoryUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //Query the user table data for the first time
        List<User> users1 = userMapper.findAll();
        System.out.println(users1);

        System.out.println("----------------------------");
        //Sleep for 10 seconds, first let other threads submit transactions
        Thread.sleep(10000);
        
        //Query the user table data for the second time
        List<User> users2 = userMapper.findAll();
        System.out.println(users2);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        MybatisSqlSessionFactoryUtils.close(sqlSession);
    }
}

public void testCacheUpdate1() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisSqlSessionFactoryUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setId(3);
        user.setUserName("marshal canopy");
        int row = userMapper.update(user);
        //commit transaction
        sqlSession.commit();
        System.out.println(row);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        MybatisSqlSessionFactoryUtils.close(sqlSession);
    }
}

Console output of testCacheSelect1 querying the user table:

testCacheUpdate1 modifies the console output of the user table:

Database data:

The first level cache under Mybatis concurrency:
After the first query, even if other sessions commit the transaction, read the unupdated data in the cache repeatedly

  1. Mybatis secondary cache concurrently submit transactions from different sessions

First enable the second level cache:
The article header details the steps to enable the second-level cache, and I won't go into details. It is worth noting that the User class needs to be serialized, otherwise the console will report NotSerializableException

/**
 *  3.Mybatis 2nd level cache:
 */
public void testCacheSelect2() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisSqlSessionFactoryUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

         //Query the user table data for the first time
        List<User> users1 = userMapper.findAll();
        System.out.println(users1);

        System.out.println("----------------------------");
        //Sleep for 10 seconds, first let other threads submit transactions
        Thread.sleep(10000);

        //Query the user table data for the second time
        List<User> users2 = userMapper.findAll();
        System.out.println(users2);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        MybatisSqlSessionFactoryUtils.close(sqlSession);
    }
}

public void testCacheUpdate2() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MybatisSqlSessionFactoryUtils.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setId(3);
        user.setUserName("marshal canopy");
        int row = userMapper.update(user);
         //commit transaction
        sqlSession.commit();
        System.out.println(row);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        MybatisSqlSessionFactoryUtils.close(sqlSession);
    }
}

console output of testCacheSelect2 querying the user table:

testCacheUpdate2 modifies the console output of the user table:

Database data:

Second-level cache under Mybatis concurrency:
After the first query, even if other sessions commit the transaction, read the unupdated data in the cache repeatedly

Summarize

1. Submit a transaction within the same session, mybatis will update the cache and re-execute the sql statement to get data from the database.

2. Different sessions submit transactions concurrently. If a session has already performed a database query, that is, loaded the data into the cache, even if different sessions submit the transaction, when the same query method is executed again, mybatis will not update the cache, but directly hit cache. Considering that this caching mechanism is similar to the transaction isolation level "repeatable read" mechanism of mysql (the database used in this test is mysql), and it is affected by the database type factor, we will continue to test whether different databases have different results later...

Tags: Java MySQL Mybatis Concurrent Programming

Posted by miramichiphoto on Mon, 16 May 2022 18:49:30 +0300