Level 2 Cache
The principle of level 2 cache is the same as level 1 cache. The first query stores data in the cache, and the second query retrieves data from the cache. However, level 1 cache is based on SqlSession, while level 2 cache is based on Mapper’s namespace. This means multiple SqlSessions can share the level 2 cache in one Mapper, and if two Mappers have the same namespace, even if they are two different Mappers, they will cache to the same region.
MyBatis provides two-level caching mechanism: level 1 cache and level 2 cache. Level 1 cache is SqlSession-level, limited to the current session, enabled by default. Level 2 cache is cross-SqlSession, with Mapper mapping file-level scope, requiring manual configuration to enable.
Working Principles
Level 2 cache is a shared caching area. Stored query results can be reused by multiple SqlSessions, thereby reducing database query operations and improving performance.
Query Process:
- First, look up data from level 2 cache
- If level 2 cache misses, look up from level 1 cache
- If level 1 cache also misses, execute SQL query the database and store results in level 1 cache and level 2 cache
Update Process:
- When an operation (such as INSERT, UPDATE, or DELETE) causes data to change, MyBatis automatically clears the level 2 cache related to that Mapper
Advantages and Disadvantages
Advantages:
- Reduced database access: Improves system performance
- Cross SqlSession sharing: Particularly useful for frequently queried data
Disadvantages:
- Data consistency issues: If database data changes, cached data may become inconsistent
- Increased memory usage: Caching consumes memory to store data
- Increased complexity: Requires additional management of cache clearing and updates
Enable Cache
Level 2 cache is disabled by default. We need to enable it by modifying sqlMapConfig.xml:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
Second, we need to enable caching in the corresponding Mapper. Here we use UserMapper as an example, adding:
<!--Enable level 2 cache-->
<cache></cache>
As you can see, that’s just an empty tag in the Mapper.xml file. Actually, some parameters can be added here. If not added, the default implementation class is used. We can also implement the Cache interface to use our own cache class (use the type parameter to change the implementation class).
Notes
Since level 2 cache is enabled, the model objects in the cache also need to implement the Serializable interface to deserialize cached data when retrieving it. This is because level 2 cache data may be stored on different media. If it’s stored in Redis, disk, etc., serialization and deserialization are required.
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WzkUser implements Serializable {
private int id;
private String username;
private String password;
private Date birthday;
private List<WzkOrder> orderList;
private List<WzkRole> roleList;
}
Write Code
Here we test level 2 cache.
public class WzkicuCache03 {
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
List<WzkUser> users1 = userMapper1.findAll();
sqlSession1.close();
List<WzkUser> users2 = userMapper2.findAll();
sqlSession2.close();
}
}
Test Code
After running the above code, the console output is as follows:
24/11/13 10:52:09 DEBUG UserMapper.findAll: ==> Preparing: select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
24/11/13 10:52:09 DEBUG UserMapper.findAll: ==> Parameters:
24/11/13 10:52:09 DEBUG UserMapper.findAll: <== Total: 3
After enabling level 2 cache, you can see that with two different SqlSessions, the second query still doesn’t send SQL.
Write Code
Here we test by clearing the cache through commit
public class WzkicuCache04 {
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
List<WzkUser> users1 = userMapper1.findAll();
sqlSession1.close();
WzkUser wzkUser = WzkUser
.builder()
.id(1)
.username("wzk-update")
.password("123-update")
.build();
userMapper2.updateById(wzkUser);
sqlSession2.commit();
sqlSession2.close();
List<WzkUser> users3 = userMapper3.findAll();
sqlSession3.close();
}
}
Test Code
After executing the above code, the results are:
24/11/13 10:58:50 DEBUG UserMapper.updateById: ==> Preparing: UPDATE wzk_user SET username=?, password=? WHERE id = ?
24/11/13 10:58:50 DEBUG UserMapper.updateById: ==> Parameters: wzk-update(String), 123-update(String), 1(Integer)
24/11/13 10:58:50 DEBUG UserMapper.updateById: <== Updates: 1
24/11/13 10:58:50 DEBUG jdbc.JdbcTransaction: Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4690b489]
24/11/13 10:58:50 DEBUG jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4690b489]
24/11/13 10:58:50 DEBUG jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4690b489]
24/11/13 10:58:50 DEBUG pooled.PooledDataSource: Returned connection 1183888521 to pool.
24/11/13 10:58:50 DEBUG mapper.UserMapper: Cache Hit Ratio [icu.wzk.mapper.UserMapper]: 0.0
24/11/13 10:58:50 DEBUG jdbc.JdbcTransaction: Opening JDBC Connection
24/11/13 10:58:50 DEBUG pooled.PooledDataSource: Checked out connection 1183888521 from pool.
24/11/13 10:58:50 DEBUG jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4690b489]
24/11/13 10:58:50 DEBUG UserMapper.findAll: ==> Preparing: select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
24/11/13 10:58:50 DEBUG UserMapper.findAll: ==> Parameters:
As you can see, after commit, the level 2 cache is also cleared, so subsequent queries still send SQL.
Additional Configuration
MyBatis also has useCache and flushCache configuration items. useCache is used to set whether to disable level 2 cache. Setting useCache = false in a statement can disable level 2 cache for the current select statement, meaning every query sends SQL. The default is true, meaning that SQL uses level 2 cache:
<select id="findAll" resultMap="userMap" useCache="false">
select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
</select>
Generally, after executing commit, the cache needs to be refreshed. flushCache = true means refresh cache, which can avoid database dirty reads, so no setting is needed, keeping the default.