04 mybatis deferred loading and caching (based on XML configuration)

Posted by greenday on Thu, 09 Dec 2021 02:34:30 +0100

Mybatis user manual (4 of 5, continuously updated), collection + attention, don't get lost, hope to be helpful to my friends~

The source code link is at the end of the text ↓↓

1. Mybatis deferred loading policy

1.1 introduction to delayed loading

Mybatis deferred loading is to load data only when it is needed. When it is not needed, data is not loaded. Lazy loading is also called lazy loading.

  • advantage

The single table query is performed first. When the slave table data needs to be used, the query is performed by associating the slave table, which improves the performance of the database.

  • shortcoming

When querying a large number of data, it may increase the waiting time of users and reduce the user experience.

1.1.1 delayed loading cases

In a one to many query, a user has multiple accounts.
When querying users, you need to use account information before querying account information (delayed loading).
When querying an account, the user information corresponding to the account should be queried together with the account information (loaded immediately).

1.1.2. How to query multiple tables

One to many, many to many: delayed loading is generally adopted
Many to one, one to one: immediate loading is generally adopted

1.2 case requirements

One to one (immediate load) and one to many (delayed load) queries for accounts and users.

1.3. assocation delayed loading

1.3.1. Enable the delayed loading strategy of Mybatis

In the main configuration file of Mybatis, a delayed loading policy is enabled

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- aggressiveLazyLoading When on, the call of any method will load all deferred load properties of the object,
That is, as long as any operation on this class will completely load all the properties of the whole class, that is, the cascading operation will be executed SQL sentence. -->
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>

1.3.2 account persistence layer interface

public interface AccountDao {
    /**
     * Query all accounts
     *
     * @return Return to the account list, including the User information of the account (load now)
     */
    List<Account> findAll();
}

1.3.3. Account persistence layer mapping file

<mapper namespace="com.junlong.dao.AccountDao">
  <!-- Establish correspondence -->
  <resultMap id="accountUserMap" type="account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!-- select: Need to call select Mapped id -->
    <!-- column: Need to pass to select Mapped parameters -->
    <!-- fetchType="eager" Indicates immediate loading -->
    <!-- Specifies the properties of the referenced entity from the table -->
    <association
                 property="user"
                 javaType="user"
                 column="uid"
                 select="com.junlong.dao.UserDao.findById"
                 fetchType="eager"
                 />
  </resultMap>

  <!-- Query all account information and load the corresponding user information of the account immediately -->
  <select id="findAll" resultMap="accountUserMap">
    select *
    from account
  </select>
</mapper>

1.3.4 user persistence layer interface

public interface UserDao {
    /**
     * Query user by user id
     *
     * @param id User id
     * @return Returns the User id used for the User id pair
     */
    User findById(Integer id);
}

1.3.5. User persistence layer mapping file

<mapper namespace="com.junlong.dao.UserDao">
  <!-- According to user id Query user -->
  <select id="findById" resultType="user" parameterType="int">
    select *
    from user
    where id = #{id}
  </select>
</mapper>

1.3.6 test and query account information and corresponding user information (load immediately)

public class AccountTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private AccountDao accountDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        accountDao = sqlSession.getMapper(AccountDao.class);
    }

    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    /**
     * Test and query all account information, and immediately load the user information corresponding to the account
     */
    @Test
    public void testFindAll() {
        List<Account> accounts = accountDao.findAll();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
}

As the run result shows, the SQL of the user who executed the query will be loaded immediately.

1.4 delayed loading of collection

1.4.1. Enable the delayed loading strategy of mybatis

In the main configuration file of Mybatis, a delayed loading policy is enabled

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- aggressiveLazyLoading When on, the call of any method will load all deferred load properties of the object,That is, as long as any operation on this class will completely load all the properties of the whole class, that is, the cascading operation will be executed SQL sentence. -->
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>

1.4.2 user persistence layer interface

public interface UserDao {
    /**
     * Query all users
     *
     * @return Return to the user list, including the user's Account information Account (delayed loading)
     */
    List<User> findAll();
}

1.4.3 user persistence layer mapping file

<mapper namespace="com.junlong.dao.UserDao">
  <!-- Establish correspondence -->
  <resultMap id="userAccountMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>
    <!-- collection It is used to establish the corresponding relationship of set attributes in one to many
            ofType Specifies the data type of the collection element
            select It is used to specify the unique ID of the query account (the name of the account) dao Fully qualified class name plus method name)
            column Is used to specify which field value to use as the query criteria
        -->
    <!-- fetchType="lazy" Indicates delayed loading -->
    <collection
                property="accounts"
                ofType="account"
                column="id"
                select="com.junlong.dao.AccountDao.findByUid"
                fetchType="lazy"
                />
  </resultMap>

  <!-- Query all users and delay loading account information -->
  <select id="findAll" resultMap="userAccountMap">
    select *
    from user
  </select>
</mapper>

1.4.4 interface of account persistence layer

public interface AccountDao {
    /**
     * According to user uid
     *
     * @param uid User id
     * @return Return all account information of the user id
     */
    List<Account> findByUid(Integer uid);
}

1.4.5. Account persistence layer mapping file

<mapper namespace="com.junlong.dao.AccountDao">
  <!-- According to user id Query all accounts -->
  <select id="findByUid" resultType="account" parameterType="int">
    select *
    from account
    where UID = #{uid}
  </select>
</mapper>

1.4.6 the test only queries the user, not the account information (delayed loading)

public class UserTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    /**
     * Test and query all users and delay loading the user's account information
     */
    @Test
    public void testFindAll() {
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user.getUsername());
        }
    }
}

As shown in the running results, the Account related information is not used, so only the user information is queried, and the Account related SQL is not executed (delayed loading).

2. Mybatis cache

Mybatis provides a caching strategy to reduce the number of queries in the database to improve performance.
Mybatis's cache is divided into L1 cache and L2 cache.

2.1. L1 cache

The L1 cache is a cache in the SqlSession range. When the modify, add, delete, commit(), close(), clearCache() methods of SqlSession are called, the L1 cache will be emptied.

2.1.1 code test

  • User persistence layer interface
public interface UserDao {
    /**
     * Query user by user id
     *
     * @param id User id
     * @return Return User object
     */
    User findById(int id);

    /**
     * Update user information
     *
     * @param user User object to update
     */
    void updateUser(User user);
}
  • User persistence layer mapping file
<mapper namespace="com.junlong.dao.UserDao">
  <!-- According to user id Query user -->
  <select id="findById" resultType="user" parameterType="int">
    select *
    from user
    where id = #{id}
  </select>

  <!-- Update user information -->
  <update id="updateUser" parameterType="user">
    update user
    set username = #{username},
    address  = #{address}
    where id = #{id}
  </update>
</mapper>
  • Test L1 cache
public class UserTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    private UserDao userDao;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sqlSessionFactory.openSession();
        userDao = sqlSession.getMapper(UserDao.class);
    }

    @After
    public void destroy() throws IOException {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }

    /**
     * The test queries the user according to the user id
     */
    @Test
    public void test1stCache() {
        User user1 = userDao.findById(48);
        System.out.println("Users queried for the first time:" + user1);

        // mybatis L1 cache is at the SqlSession level. It exists as long as SqlSession does not have close, commit or clearCache
        //        sqlSession.clearCache();
        //        sqlSession.commit();

        // When SqlSession modify, add, delete, commit(), close() and other methods are called, the L1 cache will be emptied
        //        user1.setUsername("new user");
        //        user1.setAddress("Shijiazhuang");
        //        userDao.updateUser(user1);

        User user2 = userDao.findById(48);
        System.out.println("Second query user:" + user2);

        System.out.println(user1 == user2);
    }
}

As shown in the running results, mybatis queries the database for the first time and gets the query results from the first level cache for the second time (without querying the database). The results of the two queries are the same object, which improves the performance.

2.2 L2 cache

L2 cache is Mapper mapping level cache. L2 cache is cross SqlSession, and multiple sqlsessions share L2 cache.

2.2.1 opening and closing L2 cache

  1. Mybatis master profile enables L2 cache support
<!-- Enable L2 cache -->
<settings>
  <!-- cacheEnabled The default value is true๏ผŒMismatch can be omitted -->
  <setting name="cacheEnabled" value="true"/>
</settings>
  1. Configure mapper mapping file
<?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.junlong.dao.UserDao">
  <!-- Enable L2 cache support, indicating the current mapper The mapping will use L2 cache to mapper of namespace Value as a distinction -->
  <cache/>
</mapper>
  1. Configure the useCache property of the statement
<!-- According to user id Query user information -->
<!-- userCache="true" Represents the current statement The L2 cache is required. If it is not used (the query of the latest data is required for each query), it is set to false -->
<select id="findById" parameterType="int" resultType="User" useCache="true">
  select *
  from user
  where id = #{id}
</select>

2.2.2 code test

public class UserTest {
    private InputStream inputStream;
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @After
    public void destroy() throws IOException {
        inputStream.close();
    }

    /**
     * Test L2 cache
     *
     * Note: L2 cache will not take effect until sqlSession is closed or committed
     */
    @Test
    public void testFindById() {
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
        User user1 = userDao1.findById(48);
        System.out.println(user1);
        sqlSession1.close();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
        User user2 = userDao2.findById(48);
        System.out.println(user2);
        sqlSession2.close();

        System.out.println(user1 == user2);
    }
}

As shown in the running results, after the first query, the first level cache is closed, and the second query does not execute the SQL of the query database, but uses the second level cache. The result of the two queries is not the same object, because the L2 cache uses serialization to save the object, and each query will produce a new object.

2.2.3 precautions for L2 cache

  1. The L2 cache will not take effect until the sqlSession is closed or committed;
  2. The cached class must implement the java.io.Serializable interface, so that the object can be saved by serialization.

3. Source download

Source download link ๐Ÿ‘‰ Click direct download address

The Mybatis user manual is constantly updated

Thank you for your attention! Collection + attention don't get lost~

Related content
๐Ÿ‘‰ Mybatis manual
๐Ÿ‘‰ Java se manual

Highlights of previous periods
๐Ÿ‘‰ Java Xiaobai learning manual - SE

Topics: Java Mybatis xml Back-end Cache