精通Mybatis之结果集处理流程与映射体系(无死角懒加载讲解)
Posted 木兮君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了精通Mybatis之结果集处理流程与映射体系(无死角懒加载讲解)相关的知识,希望对你有一定的参考价值。
前言
开始上班了,小编继上篇博客精通Mybatis之结果集处理流程与映射体系(重点mybatis嵌套子查询,循环依赖解决方案)(二),今天讲结果集处理与映射的倒数第二篇,本来以为三篇能结束的结果发现不够用了,先讲懒加载吧。话不多说进入主题。
懒加载
懒加载是为改善解析对象属性时,大量的嵌套子查询的并发问题。设置懒加载后,只有在使用指定属性时才会加载,从而分散SQL请求。mybatis懒加载原理是基于动态代理实现的。
基本配置以及代码示例(小编依旧使用公司下多个部门的嵌套查询,设置部门查询的时候使用懒加载):
<resultMap id="DepartmentMap" type="entity.Department">
<id property="id" column="id"/>
<result property="companyId" column="company_id" />
<result property="departmentName" column="department_name"/>
</resultMap>
<resultMap id="CompanyMap2" type="entity.Company">
<id property="id" column="id"/>
<result property="companyName" column="company_name" jdbcType="VARCHAR"/>
<collection property="departmentList" column="id" select="selectDepartmentByCompanyId" fetchType="lazy"/>
</resultMap>
<select id="selectDepartmentByCompanyId" resultMap="DepartmentMap">
select * from department where company_id = #{companyId}
</select>
<select id="selectCompanyById" resultMap="CompanyMap2" parameterType="java.lang.Long">
select * from company where id = #{id}
</select>
测试代码:
public class LazyLoadTest {
SqlSessionFactory sqlSessionFactory;
Configuration configuration;
@Before
public void init() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
configuration = sqlSessionFactory.getConfiguration();
configuration.setLazyLoadTriggerMethods(new HashSet<>());
}
@Test
public void collectionLazyTest() {
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)){
CompanyMapper companyMapper = sqlSession.getMapper(CompanyMapper.class);
Company company = companyMapper.selectCompanyById(1L);
System.out.println(company.getDepartmentList().size());
}
}
}
好了小编根据示例先讲一下懒加载的配置和细节:
- 在嵌套子查询中指定 fetchType=“lazy” 即可设置懒加载。在调用company.getDepartmentList时才会真正加载。此外调用company的:“equals”, “clone”, “hashCode”, “toString” 均会触发当前对象所有未执行的懒加载。(“equals”, “clone”, “hashCode”, “toString” 这些方法是在configuration中lazyLoadTriggerMethods的属性,类型为Set,上面小编为了不让company直接懒加载[因为debug下会执行toString方法]从而设置了空hashSet)
- 通过设置全局参数aggressiveLazyLoading=true ,也可指定调用对象任意方法触发所有懒加载。
参数 | 描述 |
---|---|
lazyLoadingEnabled | 全局懒加载开关 默认false |
aggressiveLazyLoading | 任意方法触发加载 默认false。 |
fetchType | 加载方式 eager实时 lazy懒加载。默认eager |
- set方法覆盖,当调用setXXX方法手动设置属性之后,对应的属性懒加载将会被移除,不会覆盖手动设置的值。
- 当对象经过序列化和反序列化之后,默认不在支持懒加载。但如果在全局参数中设置了configurationFactory类,而且采用JAVA原生序列化是可以正常执行懒加载的。其原理是将懒加载所需参数以及配置一起进行序列化,反序列化后在通过configurationFactory获取configuration构建执行环境。需要配置:
public static class ConfigurationFactory {
public static Configuration getConfiguration() {
return configuration; }
}
第三与第四点小编通过代码再次说明一下:
set方法覆盖
//set方法覆盖
@Test
public void collectionLazySetTest() {
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)){
CompanyMapper companyMapper = sqlSession.getMapper(CompanyMapper.class);
Company company = companyMapper.selectCompanyById(1L);
company.setDepartmentList(new ArrayList<>());
System.out.println(company.getDepartmentList().size());
}
}
测试结果:
set方法覆盖
没set方法覆盖:
对象序列化反序列化
@Test
public void collectionLazySerializableTest() throws Exception{
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)){
CompanyMapper companyMapper = sqlSession.getMapper(CompanyMapper.class);
Company company = companyMapper.selectCompanyById(1L);
byte[] bytes = writeObject(company);
Company companySer = (Company)readObject(bytes);
System.out.println(companySer.getDepartmentList());
}
}
private byte[] writeObject(Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(out);
outputStream.writeObject(obj);
return out.toByteArray();
}
private Object readObject(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ObjectInputStream inputStream = new ObjectInputStream(in);
return inputStream.readObject();
}
测试结果:
修改完毕后:
<settings>
//这里是内部静太类用$标识
<setting name="configurationFactory" value="test.LazyLoadTest$ConfigurationFactory"/>
</settings>
@Test
public void collectionLazySerializableTest() throws Exception{
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)){
CompanyMapper companyMapper = sqlSession.getMapper(CompanyMapper.class);
Company company = companyMapper.selectCompanyById(1L);
byte[] bytes = writeObject(company);
Company companySer = (Company)readObject(bytes);
System.out.println(companySer.getDepartmentList());
}
}
public static class ConfigurationFactory {
public static Configuration getConfiguration() {
return configuration; }
}
测试结果:
懒加载原理及内部结构
讲完了懒加载的结论,那小编肯定得证明原理,首先根据上面的示例代码,查看一下company的结构:
代理之后Bean会包含一个MethodHandler,内部在包含一个Map用于存放待执行懒加载,执行前懒加载前会移除。LoadPair用于针对反序列化的Bean准备执行环境。ResultLoader用于执行加载操作,执行前原执行器如果关闭会创建一个新的执行器(这里会引出一个小编之前没有讲解的Executor => ClosedExecutor)。特定属性如果加载失败,不会在进行二次加载。
内部结构图以及执行流程:
代理bean的创建过程
代理过程发生在结果集解析,创建对象之后(DefaultResultSetHandler.createResultObject),如果对应的属性设置了懒加载,则会通过ProxyFactory 创建代理对象,该对象继承自原对象,然后将对象的值全部拷贝到代理对像。并设置相应MethodHandler(原对象直接抛弃)
懒加载源码阅读
接下来小编带大家来一起阅读一下源码:
先是代理对象的创建:
DefaultResultSetHandler#createResultObject方法
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
...
//创建company对象
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
//三个映射 ,再department的时候为子查询且是懒加载
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
//对company对象进行代理
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
JavassistProxyFactory#createProxy方法
public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
final Class<?> type = target.getClass();
//创建EnhancedResultObjectProxyImpl
EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
//根据EnhancedResultObjectProxyImpl 创建compay的代理对象
Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
//之后是将原对象的属性copy进入代理对象
PropertyCopier.copyBeanProperties(type, target, enhanced);
return enhanced;
}
创建完代理对象后,然后获取:
JavassistProxyFactory#invoke方法
@Override
public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
//原对象的method方法
final String methodName = method.getName();
try {
synchronized (lazyLoader) {
//不会进这儿
if (WRITE_REPLACE_METHOD.equals(methodName)) {
Object original;
if (constructorArgTypes.isEmpty()) {
original = objectFactory.create(type);
} else {
original = objectFactory.create(type, constructorArgTypes, constructorArgs);
}
PropertyCopier.copyBeanProperties(type, enhanced, original);
if (lazyLoader.size() > 0) {
return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
} else {
return original;
}
} else {
//懒加载大于0并且不是finalize方法
if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
//是否任意方法就加载或者在"equals", "clone", "hashCode", "toString"方法里面
if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
lazyLoader.loadAll();
//如果是set方法 则将懒加载移除,直接赋值
} else if (PropertyNamer.isSetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
lazyLoader.remove(property);
//为get方法,根据属性名称获取懒加载器有就加载
} else if (PropertyNamer.isGetter(methodName)) {
final String property = PropertyNamer.methodToProperty(methodName);
if (lazyLoader.hasLoader(property)) {
lazyLoader.load(property);
}
}
}
}
}
return methodProxy.invoke(enhanced, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
ResultLoaderMap#load
public boolean load(String property) throws SQLException {
//取出LoadPair ,并且移除懒加载属性(懒加载之前所以如果pair.load()失败则不会进行二次加载)
LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));
if (pair != null) {
//开始加载
pair.load();
return true;
}
return false;
}
ResultLoaderMap$LoadPair#load
public void load(final Object userObject) throws SQLException {
if (this.metaResultObject == null || this.resultLoader == null) {
if (this.mappedParameter == null) {
throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
+ "required parameter of mapped statement ["
+ this.mappedStatement + "] is not serializable.");
}
final Configuration config = this.getConfiguration();
final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
if (ms == null) {
throw new ExecutorException("Cannot lazy load property [" + this.property
+ "] of deserialized object [" + userObject.getClass()
+ "] because configuration does not contain statement ["
+ this.mappedStatement + "]");
}
this.metaResultObject = config.newMetaObject(userObject);
//这里有个ClosedExecutor 继承BaseExecutor,只是将isClosed设置为true
this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
metaResultObject.getSetterType(this.property), null, null);
}
/* We are using a new executor because we may be (and likely are) on a new thread
* and executors aren't thread safe. (Is this sufficient?)
*
* A better approach would be making executors thread safe. */
//这段代码有点多余,因为上面已经赋值了,即使进去也是一样的
if (this.serializationCheck == null) {
final ResultLoader old = this.resultLoader;
this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
}
//设置属性值
//this.resultLoader.loadResult()
this.metaResultObject.setValue(property, this.resultLoader.loadResult());
}
ResultLoader#selectList
public Object loadResult() throws SQLException {
//通过executor 直接查询库
List<Object> list = selectList();
//设置值
resultObject = resultExtractor.extractObjectFromList(list, targetType);
return resultObject;
}
private <E> List<E> selectList() throws SQLException {
Executor localExecutor = executor;
if (Thread.currentThread(以上是关于精通Mybatis之结果集处理流程与映射体系(无死角懒加载讲解)的主要内容,如果未能解决你的问题,请参考以下文章
精通Mybatis之结果集处理流程与映射体系(重点mybatis嵌套子查询,循环依赖解决方案)