1. One level cache
1.1. In one sqlsessions, query the User table twice according to its id to see how they issue sql statements
@Test public void test1() { // According to sqlSessionFactory, create a session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // For the th query, issue the sql statement and put the query results into the cache User u1 = userMapper.selectUserByUserId(1); System.out.println(u1); // For the second query, since it is the same sqlSession, the query result will be cached // If yes, it will be directly fetched from the cache without any interaction with the database User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); sqlSession.close(); }
To view console printing:
1.2. The user table is also queried twice, but an update operation is performed between the two queries.
@Test public void test2() { // According to sqlSessionFactory, create a session SqlSession sqlSession = sessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // For the th query, issue the sql statement and put the query results into the entry cache User u1 = userMapper.selectUserByUserId(1); System.out.println(u1); // Step two performed the one update operation, sqlsession commit() u1.setSex("girl"); userMapper.updateUserByUserId(u1); sqlSession.commit(); // The second query, because it is the same sqlsession Commit() will clear the cache information // The query will also issue sql statements User u2 = userMapper.selectUserByUserId(1); System.out.println(u2); sqlSession.close(); }
To view console printing:
1.3 summary
- For the first time, query the user information with user id 1. First, find out whether there is user information with id 1 in the cache. If not, query the user information from the database. Get the "user information" and store the "user information" in the "level cache.
- If the intermediate sqlsession performs "commit" (insert, update, delete), the "level cache" in the sqlsession will be emptied. This is done so that the latest information is stored in the cache to avoid dirty reading.
- For the first time, initiate a query on the user information with the user id of 1. First, find out whether there is the user information with the id of 1 in the cache. If there is in the cache, directly obtain the user information from the cache
2. Research on the principle and source code analysis of one level cache
What is a "level 1" cache? When is the "level 1" cache created and what is the "level 2" cache workflow? I believe you should have this question now. In this section, we will study the essence of the "lower level cache"
You can think of it this way. In the past, we mentioned the "level cache" directly, so we can't bypass the SqlSession when we mentioned the "level cache". Therefore, we can directly look at the SqlSession to see if there are any cache creation or cache related attributes or methods
After investigating the one circle, it is found that among all the above methods, it seems that only clearCache() and cache have some relationship. Then, let's directly analyze the one circle. When analyzing the source code, we need to see who it (this class) is, and who its classes and subclasses are carefully After understanding the above relationship, you will have a deeper understanding of this class. After analyzing the circle, you may get the following flow chart
After further analysis, after the process is transferred to the clear() method in the perpetual cache, its cache will be adjusted Clear () side what is the cache? Click to find that cache is actually private Map cache = new HashMap(); That is, a map, so the cache Clear () is actually a map Clear (), that is to say, the cache is actually "map objects" stored locally. Every "SqISession" will store "map object references". When was the cache created?
What do you think is the most likely place to create a cache? I think it's the Executor. Why do you think so? Because the Executor is the Executor, which executes SQL requests, and the method of clearing the cache is also executed in the Executor, it is likely that the creation of the cache is also possible in the Executor. After looking at the circle, it is found that there is a createCacheKey method in the Executor, which is very similar to the method of creating the cache. Follow it and you will find that the createCacheKey method.Method is executed by BaseExecutor. The code is as follows
CacheKey cacheKey = new CacheKey(); //id of MappedStatement // id is the location of the SQL statement package name + class name + SQL name cacheKey.update(ms.getId()); // offset is 0 cacheKey.update(rowBounds.getOffset()); // limit is integer MAXVALUE cacheKey.update(rowBounds.getLimit()); //Specific SQL statements cacheKey.update(boundSql.getSql()); //The last face is the parameter in the sql that is update d cacheKey.update(value); ... if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); }
Creating a cache key will go through the "update" method of the "udate" method. The "udate" method is executed by the "CacheKey" object. This
In the update method, the list of updateList finally stores the five values. By comparing the code of face above and the diagram of face below, you should be able to understand what the five values are
Note the last value under configuration getEnvironment(). Getid () what is this? This is actually defined in mybatis config The tag in the XML, see is as follows.
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments>
So let's get back to the point. Where should I go after creating the cache? You will never create a cache out of thin air, will you? Absolutely not. After exploring the "level cache", we found that "level cache" is more about query operations. After all, it is also called query cache. Why is it called query cache. Let's first look at where the cache is. We trace the query method as follows:
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); //Create cache CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings("unchecked") Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { //This mainly deals with stored procedures. handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, boundSql); } ... } // queryFromDatabase method private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
If not, query from the database. In queryFromDatabase, write to the localcache. The put method of the localcache object is finally handed over to the Map for storage
private Map<Object, Object> cache = new HashMap<Object, Object>(); @Override public void putObject(Object key, Object value) { cache.put(key, value); }