Mybatis延迟加载的实现以及使用场景

  首先我们先思考一个问题,假设:在一对多中,我们有一个用户,他有 100 个账户。

  问题 1:在查询用户的时候,要不要把关联的账户查出来?

  问题 2:在查询账户的时候,要不要把关联的用户查出来?

  解答:在查询用户的时候,用户下的账户信息应该是我们什么时候使用,什么时候去查询。

     在查询账户的时候,账户的所属用户信息应该是随着账户查询时一起查询出来。

  搞清楚这两个简单的问题后,我们就可以引出延迟加载和立即加载的特性。

  延迟加载:在真正使用数据的时候才发起查询,不用的时候不查询关联的数据,延迟加载又叫按需查询(懒加载)

  立即加载:不管用不用,只要一调用方法,马上发起查询。

  使用场景:在对应的四种表关系中,一对多、多对多通常情况下采用延迟加载,多对一、一对一通常情况下采用立即加载。

  理解了延迟加载的特性以后再看 Mybatis 中如何实现查询方法的延迟加载,在 MyBatis 的配置文件中通过设置 settings 的 lazyLoadingEnabled 属性为 true 进行开启全局的延迟加载,通过 aggressiveLazyLoading 属性开启立即加载。看一下官网的介绍,然后通过一个实例来实现 Mybatis 的延迟加载,在例子中我们展现一对多表关系情况下,通过实现查询用户信息同时查询出该用户所拥有的账户信息的功能展示一下延迟加载的实现方式以及延迟加载和立即加载的结果的不同之处。

lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。 true | false false (在 3.4.1 及之前的版本默认值为 true)

  

 

  1. 用户类以及账户类

public class User implements Serializable{
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    private List<Account> accountList;
get和set方法省略.....      

}

public class Account implements Serializable{
private Integer id;
private Integer uid;
private Double money;

get和set方法省略.....      

}

注意因为我们是查找用户的同时查找出其所拥有的账户所以我们需要在用户类中增加账户的集合的属性,用来封装返回的结果。

  2. 在 UserDao 接口中声明 findAll 方法

/**
     * 查询所有的用户
     *
     * @return
     */
    List<User> findAll();

  3. 在 UserDao.xml 中配置 findAll 方法的映射

<resultMap id="userAccountMap" type="com.example.domain.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 property="accountList" ofType="com.example.domain.Account" column="id"
                    select="com.example.dao.AccountDao.findAllByUid"/>
    </resultMap>
    <select id="findAll" resultMap="userAccountMap">
        SELECT * FROM USER;
    </select>

  主要的功能实现位于 <collection property="accountList" ofType="com.example.domain.Account" column="id" select="com.example.dao.AccountDao.findAllByUid"/> 中,对于账户列表的信息通过 collection 集合来映射,通过 select 指定集合中的每个元素如何查询,在本例中 select 的属性值为 AccountDao.xml 文件的 namespace   com.example.dao.AccountDao 路径以及指定该映射文件下的 findAllByUid 方法,通过这个唯一标识指定集合中元素的查找方式。因为在这里需要用到根据用户 ID 查找账户,所以需要同时配置一下 findAllByUid 方法的实现。

  4. 配置 collection 中 select 属性所使用的方法 findAllByUid

AccountDao 接口中添加
 /**
     * 根据用户 ID 查询账户信息
     * @return
     */
    List<Account> findAllByUid(Integer uid);

AccountDao.xml 文件中配置
<select id="findAllByUid" resultType="com.example.domain.Account">
SELECT * FROM account WHERE uid = #{uid};
</select>

  5. 在 Mybatis 的配置文件中开启全局延迟加载

configuration>
    <settings>
        <!--开启全局的懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--关闭立即加载,其实不用配置,默认为 false-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--开启 Mybatis 的 sql 执行相关信息打印-->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    <typeAliases>
        <typeAlias type="com.example.domain.Account" alias="account"/>
        <typeAlias type="com.example.domain.User" alias="user"/>
        <package name="com.example.domain"/>
    </typeAliases>
    <environments default="test">
        <environment id="test">
            <!--配置事务-->
            <transactionManager type="jdbc"></transactionManager>
            <!--配置连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test1"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置映射文件的路径-->
    <mappers>
        <mapper resource="com/example/dao/UserDao.xml"/>
        <mapper resource="com/example/dao/AccountDao.xml"/>
    </mappers>
</configuration>

  6. 测试方法

    private InputStream in;
    private SqlSession session;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> UserDao userDao;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> AccountDao accountDao;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> SqlSessionFactory factory;
@Before
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> init()<span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception{
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取配置文件</span>
    in = Resources.getResourceAsStream("SqlMapConfig.xml"<span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取工厂</span>
    factory = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SqlSessionFactoryBuilder().build(in);

    session </span>=<span style="color: rgba(0, 0, 0, 1)"> factory.openSession();

    userDao </span>= session.getMapper(UserDao.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
    accountDao </span>= session.getMapper(AccountDao.<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
}
@After
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span> destory()<span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Exception{
    session.commit();
    session.close();
    in.close();
}
@Test
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> findAllTest(){
    List</span>&lt;User&gt; userList =<span style="color: rgba(0, 0, 0, 1)"> userDao.findAll();

// for (User user: userList){
// System.out.println("每个用户的信息");
// System.out.println(user);
// System.out.println(user.getAccountList());
// }
}

测试说明:当我们注释了 findAllTest() 方法中的 for 循环打印的时候,我们将不会需要用户的账户信息,按照延迟加载的特性程序只会查询用户的信息,而不会查询账户的信息。当我我们放开 for 循环打印的时候我们使用到了用户和账户的信息,程序会同时将用户以及对应的账户信息打印出来。

  7. 测试结果

  (1)注释 for 循环,不使用数据,这时候不需要查询账户信息,我们能在控制台中看到 sql 执行结果是因为在 Mybatis 配置文件中添加了 logImpl 的配置,具体参考第 5 步中的配置信息

    (2)通过 for 循环打印查询的数据,使用数据,这时候因为使用了数据所以将查询账户信息,我们发现用户和账户查询都进行了执行