到了级联最后的章节,上次中我们讨论了延迟加载,但是最后有点凌乱,估计大家摸不着头脑,希望大家hold住,这里我们再升华为理论知识,就十分简单了。
1、树形层级加载:
上篇,我们将全局参数变为:
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
我们发现其延迟加载规则,貌似不可触摸,于是大家都很懵懂,不过不要紧我们我们回到第一篇
的模型图,我们可以得到这样的一张树形图:
有了这个图,这样就方便论述了,
加载员工的时候,MyBatis会把员工和鉴别器,健康情况当做树形的第一层级加载,所以运行了2个SQL。
我们通过延迟加载员工卡信息的时候,因为负责项目信息和员工卡是同一个层级,所以MyBatis会连同该员工负责的项目也同时加载进来。
这便是MyBatis的树形加载。
2、非全局定义延迟加载策略:
在很多情况下,作为公司的管理层,往往对员工卡不感兴趣,而对其负责的项目情况则十分感兴趣,因此我们需要这样的一个场景,在加载员工信息的时候,顺便连同把其负责的项目信息也加载进来,而不需要把员工卡和健康情况这些信息加载进来。
但是按上篇要么全部延迟加载要么全部延迟加载,再或者是按树形加载。但是不要紧,mybatis为我们提供了可以非全局配置的延迟加载功能,让我们学习它们。
在关联的元素(association ,collection ,discriminator)中,我们存在一个属性:fetchType来决定是否需要延迟加载,如果配置它,它将覆盖掉原有在MyBatis设置的策略。
对于它而言,它有两个取值:
- lazy: 延迟加载
- eager:即刻加载
由它来决定是否需要延迟或者即刻加载。
这样让我们修改Mapper的配置,来保证加载员工信息的同事即刻加载项目信息,同时不要加载其他的信息,我们先把xml的配置修改过来:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
然后加入如下的配置:
<?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.ykzhen2015.csdn.mapper.EmployeeMapper">
<resultMap id="employeeMap" type="com.ykzhen2015.csdn.pojo.Employee">
<id property="id" column="id" />
<result property="empName" column="emp_name" />
<result property="sex" column="sex" />
<association property="employeeCard" column="id" fetchType="lazy"
select="com.ykzhen2015.csdn.mapper.EmployeeCardMapper.getEmployeeCardByEmpId" />
<collection property="projectList" column="id" fetchType="eager"
select="com.ykzhen2015.csdn.mapper.ProjectMapper.findProjectByEmpId" />
<discriminator javaType="int" column="sex">
<case value="1" resultMap="maleEmployeeMap" />
<case value="2" resultMap="femaleEmployeeMap" />
</discriminator>
</resultMap>
<select id="getEmployee" parameterType="int" resultMap="employeeMap">
select id, emp_name as empName, sex from t_employee where id =#{id}
</select>
<resultMap id="maleEmployeeMap" type="com.ykzhen2015.csdn.pojo.MaleEmployee" extends="employeeMap">
<collection fetchType="lazy" property="prostateList" select="com.ykzhen2015.csdn.mapper.MaleEmployeeMapper.findProstateList" column="id" />
</resultMap>
<resultMap id="femaleEmployeeMap" type="com.ykzhen2015.csdn.pojo.FemaleEmployee" extends="employeeMap">
<collection fetchType="lazy" property="uterusList" select="com.ykzhen2015.csdn.mapper.FemaleEmployeeMapper.findUterusList" column="id" />
</resultMap>
</mapper>
请注意,关联的元素(association ,collection ,discriminator)中的fetchType属性,它的范围是:eager和lazy。我们这里将取员工卡和健康情况的设置为lazy,意味着它将延迟加载,把获取项目情况的设置为eager,意味着加载员工信息后,就会加载项目情况,好让我们再次运行程序,
public static void main(String []args) {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = employeeMapper.getEmployee(1);
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
可以得到如下日志:
DEBUG 2016-05-16 14:15:54,793 org.apache.ibatis.logging.LogFactory: Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
DEBUG 2016-05-16 14:15:54,883 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-16 14:15:54,883 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-16 14:15:54,883 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-16 14:15:54,883 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-16 14:15:54,883 org.apache.ibatis.datasource.pooled.PooledDataSource: PooledDataSource forcefully closed/removed all connections.
DEBUG 2016-05-16 14:15:54,993 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
DEBUG 2016-05-16 14:15:55,227 org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 1037324811.
DEBUG 2016-05-16 14:15:55,227 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select id, emp_name as empName, sex from t_employee where id =?
DEBUG 2016-05-16 14:15:55,259 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Preparing: SELECT a.id, a.proj_name as projName FROM t_project a, t_employee_project b where a.id = b.proj_id and b.emp_id = ?
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ====> Parameters: 1(Integer)
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <==== Total: 2
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dd4520b]
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dd4520b]
DEBUG 2016-05-16 14:15:55,347 org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 1037324811 to pool.
好,它打印了我们运行的轨迹,正是我们所需要的,在加载员工信息后,它会马上加载项目信息,而对于其他的一律都不予加载,通过fetchType属性,我们可以自定义那些可以加载,那些不加载,这样就可以很灵活的处理级联的延迟加载问题。
3、延迟加载的设计原理
延迟加载是使用动态代理来实现的,关于动态代理可以看到我的文章:
MyBATIS插件原理第一篇——技术基础(反射和JDK动态代理)
我们拿员工信息和员工卡信息作为例子谈谈,延迟加载。当我们获取了员工的信息,此时,我们生成一个员工卡的代理对象,而这个对象只是记录一些关键的关联字段,而没有发送SQL去把员工卡的信息拿出来,当我们调用员工卡的信息的时候,它就会进入动态代理的invoke方法,在访问信息前,通过SQL和记录的关联信息找到数据库的信息进行回填,这便是延迟加载的原理。
在MyBatis中,如果是在3.3.0版本(不含)以下,采用的是CGLib动态代理,以上(含)采用的是JAVASSIST。有关它们的研究可以参考别的书籍。
4、总结
好了,所有的我们都谈完了,都是笔者自己经过多次研究和反复的结果,如果有误欢迎指正,学到知识的,要感谢博主哦,写这些真的不是那么简单的,希望大家能够掌握MyBatis的级联哦。