MyBatis loads the policy

What is lazy loading?

The problem

Through the previous learning, we have mastered the configuration and implementation of one-to-one, one-to-many and many-to-many relationships in Mybatis, which can realize the associated query of objects. A lot of the time during actual development we don’t always need to load the user’s order information when they are loaded. This is what we call lazy loading.

Take a chestnut

* In one-to-many, when we have a user and it has 100 orders to query the user, should we check the associated orders? When querying orders, do you want to find out the associated users? * Answer when querying the user, the user should place the order, when to use, when to query. When querying an order, the information of the user to which the order belongs should be queried along with the order.Copy the code

Lazy loading

Load data only when it is needed and not when it is not needed. Lazy loading is also called lazy loading.

* Advantages: first from a single table query, when necessary from the associated table to associated query, greatly improve the performance of the database, because the query of a single table than the associated query of multiple tables faster. * Disadvantages: because only when the need to use data, database query, so in a large number of data query, because the query work also consumes time, so it may cause users to wait for a long time, resulting in a decline in user experience. * In multiple tables: one-to-many, many-to-many: lazy loading is usually used one-to-one (many-to-one) : loading immediately is usually used * Note: lazy loading is implemented based on nested queriesCopy the code

implementation

Local delay loading

There is a fetchType attribute in both the Association and Collection tags, and you can modify the local load policy by changing its value.

<! -- Enable one-to-many lazy loading --> 
<resultMap id="userMap" type="user">
    <id column="id" property="id"></id> 
    <result column="username" property="username"></result> 
    <result column="password" property="password"></result> 
    <result column="birthday" property="birthday"></result> 
    <! FetchType ="eager" --> 
    <collection property="orderList" ofType="order" column="id"
                select="com.lagou.dao.OrderMapper.findByUid" fetchType="lazy">
    </collection> 
</resultMap> 

<select id="findAll" resultMap="userMap"> 
    SELECT * FROM `user` 
</select>
Copy the code

Sets the method to trigger lazy loading

After configuring lazy loading, you can see that a query is triggered when you call equals, Clone, hashCode, and toString of the current object, even if you don’t call any of its methods.

We can override the above four methods using the lazyLoadTriggerMethods configuration item in the configuration file.

<settings> 
    <! All methods will be lazily loaded 
    <setting name="lazyLoadTriggerMethods" value="toString()"/> 
</settings>
Copy the code

Global lazy loading

The global loading policy can be modified using the setting tag in the core configuration file of Mybatis.

<settings> 
    <! -- Enable global lazy loading --> 
        <setting name="lazyLoadingEnabled" value="true"/> 
</settings>
Copy the code
  • Notice A local load policy has a higher priority than a global load policy.
<! -- Disable one-to-one lazy loading --> 
<resultMap id="orderMap" type="order"> 
    <id column="id" property="id"></id> 
    <result column="ordertime" property="ordertime"></result> 
    <result column="total" property="total"></result> 
    <! FetchType ="eager" --> 
    <association property="user" column="uid" javaType="user"
                 select="com.lagou.dao.UserMapper.findById" fetchType="eager">
    </association> 
</resultMap> 

<select id="findAll" resultMap="orderMap"> 
    SELECT * from orders 
</select>
Copy the code

MyBatis cache

Why cache?

When the user frequently queries certain fixed data, the data is queried from the database for the first time and stored in the cache. When the user queries the data again, the user does not query the data through the database, but in the cache. Reduce the loss of network connection and database query, thus improving our query efficiency and reducing the system performance problems caused by high concurrent access. In a word: Query frequently for data that doesn’t change often, and use caching to improve query efficiency. Like most persistence frameworks, Mybatis also provides a caching policy to reduce the number of database queries and improve performance. The cache in Mybatis is divided into level 1 cache and level 2 cache.

Level 1 cache

introduce

Level 1 cache is SqlSession level cache, which is enabled by default. Therefore, if the parameters are exactly the same as SQL, we call a Mapper method using the same SqlSession object, usually only execute SQL once, because after the first query using SelSession, MyBatis will put it in the cache, and SqlSession will fetch the current cache data, and will not send SQL to the database again, if there is no declaration to refresh, and the cache does not time out.

validation

@Test 
public void testOneCache(a) throws Exception { 
    SqlSession sqlSession = MyBatisUtils.openSession(); 
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 
    
    User user1 = userMapper.findById(1); 
    System.out.println("First query user:" + user1); 
    
    User user2 = userMapper.findById(1); 
    System.out.println("Second query user:" + user2); 
    
    sqlSession.close(); 
}
Copy the code

We can see that although we queried twice in the above code, only one database operation was performed. This is where the first level cache provided by Mybatis comes into play. Because of the existence of the level-1 cache, the second query for the record whose ID is 1 does not issue a SQL statement to query the data from the database, but from the level-1 cache.

Analysis of the

Level 1 cache is the CACHE of the SqlSession scope. The CACHE will be cleared by performing the C (add) U (update) D (delete) operation of the SqlSession, or by calling clearCache(), COMMIT (), and close() methods.

1. Query information about the user whose ID is 41 for the first time. Check whether the user whose ID is 41 exists in the cache. 2. Obtain user information and store it in level-1 cache. 3. If the sqlSession commit operation (insert, update, or delete) is performed, the level-1 cache in the sqlSession is cleared so that the cache stores the latest information and avoids dirty reads. 4. Query information about the user whose ID is 41 for the second time. Check whether the user whose ID is 41 exists in the cache.Copy the code

remove

@Test 
public void testClearOneCache(a) throws Exception { 
    SqlSession sqlSession = MybatisUtils.openSession(); 
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 
    User user1 = userMapper.findById(41); 
    System.out.println("First query user:" + user1); 
    
    // Call sqlSession to clear the cache
    sqlSession.clearCache(); 
    
    User user2 = userMapper.findById(41); 
    System.out.println("Second query user:" + user2); 
}
Copy the code
<! Caches are cleared each time a query is made 
< select flushCache="true"></select>
Copy the code

The second level cache

introduce

Level 2 cache is namspace level (cross-SQLSession) cache. Level 2 cache is not enabled by default. To implement level 2 cache, MyBatis requires that poJOs returned must be serializable. The Serializable interface can be easily configured to enable level 2 caching in a mapping XML file.

validation

A) Configure the core configuration file

<settings>
    <! Since cacheEnabled is set to true by default, this step can be omitted. If the value is true, level 2 cache is enabled. False indicates that level-2 caching is disabled. -->
    <setting name="cacheEnabled" value="true"/>
</settings>
Copy the code

B) Configure the usermapper. XML mapping

<mapper namespace="com.lagou.dao.UserMapper">
    <! -- Enable level 2 cache for current mapping files -->
    <cache></cache>
    <! Set useCache= "true" in the <select> tag to indicate that the current statement uses level 2 caching. Note: Use useCache="false" to disable level 2 caching when the latest data SQL is required for each query. -->
    <select id="findById" parameterType="int" resultType="user" useCache="true">
        SELECT * FROM `user` where id = #{id}
    </select>
</mapper>
Copy the code

C) Modify the User entity

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    private List<Role> roleList;
    private List<Order> orderList;
}
Copy the code

D) Test results

@Test
public void testTwoCache(a) throws Exception {
    SqlSession sqlSession = MyBatisUtils.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findById(41);
    System.out.println("First query user:" + user);
    sqlSession.close();

    SqlSession sqlSession1 = MyBatisUtils.openSession();
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    User user1 = userMapper1.findById(41);
    System.out.println("Second query user:"+user1);
    sqlSession1.close();
}
Copy the code

Analysis of the

Level-2 cache is the cache of mapper mapping level. Multiple SQLsessions can operate the SQL statement of the same MAPper mapping. Multiple SQLsessions can share level-2 cache, and level-2 cache is cross-SQLSession.

1. All select statements in the mapping statement file will be cached. 2. All INSERT, UPDATE, and DELETE statements in the mapping statement file flush the cache.Copy the code

Pay attention to problems (dirty reading)

Mybatis level 2 cache is namespace level, so it can cause dirty read problems when performing multiple table queries

summary

1. The cache of Mybatis does not require us to store and obtain data manually. Mybatis is automatically maintained. 2. Mybatis enabled level 2 cache, then query order: Level 2 cache -- "Level 1 cache --" database note: Mybatis level 2 cache will have a dirty read problem, need to use the third-party cache technology to solve the problem.Copy the code

MyBatis annotations

MyBatis common annotations

Annotation development has become more and more popular in recent years, Mybatis can also use annotation development mode, so we can write Mapper map files less. We’ll start with some basic CRUD and then look at complex mapped multi-table operations.

</ Update > * @update: < Update ></ Update > * @select: < Update ></ Update > < Result ></ Result > * @results: < Result ></ Result > * @results: Can be used with @result to encapsulate multiple Result sets instead of <resultMap></resultMap> * @one: Implements one-to-one Result set encapsulation instead of <association></association> * @many: Implement one-to-many result set encapsulation instead of <collection></collection>Copy the code

MyBatis annotation add delete change check

Create the UserMapper interface

public interface UserMapper {

    @Select("SELECT * FROM `user`")
    public List<User> findAll(a);

    @Insert("INSERT INTO `user`(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
    public void save(User user);

    @Update("UPDATE `user` SET username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} WHERE id = #{id}")
    public void update(User user);

    @Delete("DELETE FROM `user` where id = #{id}")
    public void delete(Integer id);
}
Copy the code

Write the core configuration file

<! -- We use annotated mapping files instead, so we just need to load the annotated Mapper interface -->
<mappers>
    <! -- Scan Mapper classes with annotations -->
    <mapper class="com.lagou.mapper.UserMapper"></mapper>
</mappers>
Copy the code
<! You can also specify the package to scan for the interface that contains the mapping.
<mappers>
    <! -- Scan the package of the Mapper class that uses annotations -->
    <package name="com.lagou.mapper"></package>
</mappers>
Copy the code

The test code

public class TestUser extends TestBaseMapper {

    / / query
    @Test
    public void testFindAll(a) throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = userMapper.findAll();
        for(User user : list) { System.out.println(user); }}/ / add
    @Test
    public void testSave(a) throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsername("Yu Qian");
        user.setBirthday(new Date());
        user.setSex("Male");
        user.setAddress("Beijing Deyun Society");
        userMapper.save(user);

    }

    / / update
    @Test
    public void testUpdate(a) throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setId(49);
        user.setUsername("Guo Degang");
        user.setBirthday(new Date());
        user.setSex("Male");
        user.setAddress("Beijing Deyun Society");
        userMapper.update(user);
    }

    / / delete
    @Test
    public void testDelete(a) throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        userMapper.delete(49); }}Copy the code

Use annotations for complex mapping development

Previously, we implemented complex relational mapping in the mapping file by configuring <resultMap>, < Association >, and < Collection >.

After annotation development, we can use @results, @Result, @One, @many annotation combinations to complete the configuration of complex relationships.

One-to-one query

introduce

Requirement: Query an order, and at the same time query the user that the order belongs to

  • One-to-one query statement
SELECT * FROM orders;
SELECT * FROM `user` WHERE id = #{order uid};
Copy the code

Code implementation

A) OrderMapper interface

public interface OrderMapper {

    @Select("SELECT * FROM orders")
    @Results({ @Result(id = true, column = "id", property = "id"), @Result(column = "ordertime", property = "ordertime"), @Result(column = "money", property = "money"), @Result(property = "user", javaType = User.class, column = "uid", one = @One(select = "com.lagou.mapper.UserMapper.findById", fetchType = FetchType.EAGER)) })
    public List<Order> findAllWithUser(a);

}
Copy the code

B) UserMapper interface

public interface UserMapper {

    @Select("SELECT * FROM `user` WHERE id = #{id}")
    public User findById(Integer id);

}
Copy the code

C) Test code

@Test
public void testOrderWithUser(a) throws Exception {
    OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);

    List<Order> list = orderMapper.findAllWithUser();

    for(Order order : list) { System.out.println(order); }}Copy the code

One-to-many query

introduce

Requirement: Query a user and at the same time query the orders that the user has

  • One-to-many query statement
SELECT * FROM `user`;
SELECT * FROM orders where uid = # {user id};
Copy the code

Code implementation

A) UserMapper interface

public interface UserMapper {

    @Select("SELECT * FROM `user`")
    @Results({ @Result(id = true, column = "id", property = "id"), @Result(column = "brithday", property = "brithday"), @Result(column = "sex", property = "sex"), @Result(column = "address", property = "address"), @Result(property = "orderList", javaType = List.class, column = "id" , many = @Many(select = "com.lagou.mapper.OrderMapper.findByUid")) })
    public List<User> findAllWithOrder(a);
}
Copy the code

B) OrderMapper interface

public interface OrderMapper {

    @Select("SELECT * FROM orders WHERE uid = #{uid}")
    public List<Order> findByUid(Integer uid);
}
Copy the code

C) Test code

@Test
public void testUserWithOrder(a) throws Exception {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    List<User> list = userMapper.findAllWithOrder();

    for(User user : list) { System.out.println(user); }}Copy the code

Many-to-many query

introduce

Requirement: Query all users and all roles of the user

  • Many-to-many query statement
SELECT * FROM `user`;

SELECT * FROM role r INNER JOIN user_role ur ON r.`id` = ur.`rid`
    WHERE ur.`uid` = # {user id};
Copy the code

Code implementation

A) UserMapper interface

public interface UserMapper {

    @Select("SELECT * FROM `user`")
    @Results({ @Result(id = true, column = "id", property = "id"), @Result(column = "brithday", property = "brithday"), @Result(column = "sex", property = "sex"), @Result(column = "address", property = "address"), @Result(property = "roleList", javaType = List.class, column = "id" , many = @Many(select = "com.lagou.mapper.RoleMapper.findByUid")) })
    public List<User> findAllWithRole(a);
}
Copy the code

B) RoleMapper interface

public interface RoleMapper {

    @Select("SELECT * FROM role r INNER JOIN user_role ur ON r.`id` = ur.`rid` WHERE ur.`uid` = #{uid}")
    public List<Role> findByUid(Integer uid);
}
Copy the code

C) Test code

@Test
public void testUserWithRole(a) throws Exception {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    List<User> list = userMapper.findAllWithRole();

    for(User user : list) { System.out.println(user); }}Copy the code

Annotation-based second-level caching

Configure the sqlmapconfig. XML file to enable level 2 cache support

<settings>
    <! Since cacheEnabled is set to true by default, this step can be omitted. If the value is true, level 2 cache is enabled. False indicates that level-2 caching is disabled. -->
    <setting name="cacheEnabled" value="true"/>
</settings>
Copy the code

Configure level 2 caching using annotations in the Mapper interface

@CacheNamespace
public interface UserMapper {... }Copy the code

Annotation lazy loading

Whether one-to-one or one-to-many, there is a fetchType attribute in the annotation configuration

* fetchType = fetchType.LAZY indicates LAZY loading. * fetchType = fetchTypeCopy the code

summary

1. Compared with XML configuration, annotation development is simpler and more efficient in terms of development efficiency. 2. In terms of maintainability, if annotations are to be modified, the source code must be modified, which will lead to increased maintenance costs. XML is more maintainable.Copy the code