Mybatis 懒加载的应用
什么是懒加载?懒加载的意思就是在应用的时候才去加载,不应用不去加载,相同的就叫饥饿加载或者立刻加载。懒加载在 Mybatis 中个别是存在与联结查问的状况,比方查问一个对象的同时连带查问相干的表对应的数据。在 Mybatis 中查问能够通过 ResultMap 设置查问对象返回一个汇合属性,也就是说像这样的:
@Datapublic class User implements Serializable { private int id; private int age; private String name; private List<Order> orderList;}
这里的 orderList 就是一个汇合,在 mapper.xml 中配置如下:
<resultMap id="userMap" type="mybatis.model.User"> <id column="id" property="id"/> <result property="age" column="age"/> <result property="name" column="name"/> <collection property="orderList" ofType="mybatis.model.Order" column="id" select="findByUid"/></resultMap><select id="findByUid" resultType="mybatis.model.Order"> select * from `order` where uid = #{id}</select><select id="selectById" resultMap="userMap"> select * from user where id = #{id}</select>
能够看到这里查问 User 对象的时候还查问了 Order 列表,这个用户关联的订单信息。如果只是这样查问那么后果是饥饿加载:
@Testpublic void testLazyLoad(){ SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectById(1); System.out.println(user.getName());}
输入后果,执行了两个 sql 语句查问,阐明查问 User 的同时也查问了 Order
09:52:56.575 [main] INFO mybatis.plugins.MyPlugin - 对办法进行加强....==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer)<== Columns: id, age, name<== Row: 1, 18, 灵犀Cache Hit Ratio [mybatis.mapper.UserMapper]: 0.009:52:56.613 [main] INFO mybatis.plugins.MyPlugin - 对办法进行加强....====> Preparing: select * from `order` where uid = ? ====> Parameters: 1(Integer)<==== Columns: id, uid, order_name, price<==== Row: 1, 1, 苹果, 8.00<==== Row: 3, 1, 笔记本电脑, 8000.00<==== Total: 2<== Total: 1灵犀Process finished with exit code 0
配置懒加载:
<resultMap id="userMap" type="mybatis.model.User"> <id column="id" property="id"/> <result property="age" column="age"/> <result property="name" column="name"/> <collection property="orderList" ofType="mybatis.model.Order" column="id" select="findByUid" fetchType="lazy"/></resultMap>
这里的 collection 标签中的 fetchType 属性能够设置为 lazy 或者 eager ,默认就是 eager 饥饿加载,配置完之后执行:
09:56:22.649 [main] INFO mybatis.plugins.MyPlugin - 对办法进行加强....==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer)<== Columns: id, age, name<== Row: 1, 18, 灵犀<== Total: 1灵犀
能够看到只执行了查问 user 的 sql 语句,而查问订单 order 的 sql 语句没有执行,只有在应用 orderList 这个属性的时候才会去执行 sql 查问:
@Testpublic void testLazyLoad(){ SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectById(1); System.out.println(user.getName()); // 懒加载 System.out.println(user.getOrderList());}
输入后果:
09:58:02.681 [main] INFO mybatis.plugins.MyPlugin - 对办法进行加强....==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer)<== Columns: id, age, name<== Row: 1, 18, 灵犀<== Total: 1灵犀Cache Hit Ratio [mybatis.mapper.UserMapper]: 0.009:58:02.746 [main] INFO mybatis.plugins.MyPlugin - 对办法进行加强....==> Preparing: select * from `order` where uid = ? ==> Parameters: 1(Integer)<== Columns: id, uid, order_name, price<== Row: 1, 1, 苹果, 8.00<== Row: 3, 1, 笔记本电脑, 8000.00<== Total: 2[Order(id=1, uid=1, orderName=苹果, price=8.00), Order(id=3, uid=1, orderName=笔记本电脑, price=8000.00)]Process finished with exit code 0
能够看到执行查问订单的 sql 语句并且打印了订单信息
Mybatis 懒加载原理及源码解析
Mybatis 懒加载的原理要搞清楚的话,就须要去找到返回后果的时候看看 Mybatis 是如何封装的,找到 ResultSetHandler ,因为这个接口就是专门用于后果集封装的,默认实现为 DefaultResultSetHandler ,依据查问数据流程不难发现封装后果集的时候调用的是 handleResultSets 办法:
@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // 获取ResultSet的包装器 ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); // 验证后果数量 validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); // 处理结果集 handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }
点击处理结果集的办法:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { // 解决每行的数据 handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }
点击解决每行的数据办法:
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { // 如果存在嵌套的后果集 if (resultMap.hasNestedResultMaps()) { // 平安行束缚查看,如果是嵌套查问须要敞开平安行约束条件 ensureNoRowBounds(); // 查看后果处理器是否合乎嵌套查问束缚 checkResultHandler(); // 执行嵌套查问后果集解决 handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 简略的后果集分装解决 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
因为咱们写的这个后果是简略后果集,所以进入 handleRowValuesForSimpleResultMap :
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 获取每行的值 Object rowValue = getRowValue(rsw, discriminatedResultMap, null); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } }
挑重点,间接进入获取每行值办法中:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // 创立后果值 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }
持续进入获取每行后果值的办法, createResultObject :
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List<Class<?>> constructorArgTypes = new ArrayList<>(); final List<Object> constructorArgs = new ArrayList<>(); // 创立后果对象 ,应用ObjectFactory 反射进行创立 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 // 查看属性是否是懒加载的属性 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // 应用动静代理创立一个代理对象作为后果对象返回进来,默认应用javassist 进行创立 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; }
这里就先是通过反射创立出这个对象 resultObject ,而后遍历去查看这些属性是否是懒加载的,如果是那么就通过代理工厂去创立一个代理对象,因为这里创立的是一个返回对象,不是一个接口因而动静代理实现是通过 cglib 实现的, Mybatis 这里应用 javassist 包下的代理进行创立代理对象,代理工厂默认就是 JavassistProxyFactory :
static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) { ProxyFactory enhancer = new ProxyFactory(); enhancer.setSuperclass(type); try { type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (LogHolder.log.isDebugEnabled()) { LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { enhancer.setInterfaces(new Class[] { WriteReplaceInterface.class }); } catch (SecurityException e) { // nothing to do here } Object enhanced; Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]); Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]); try { // 创立代理对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } ((Proxy) enhanced).setHandler(callback); return enhanced; }
实际上这里也是通过反射进行创立,只是在里面封装成了 ProxyFactory 这个对象,当咱们调用 getOrderList 办法的时候就会执行到 invoke 办法中,并且判断是否是提早加载的,如果是那么就会执行 lazyLoader.load 办法执行提早加载,也就是执行 sql 查问数据:
@Override public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable { 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 { if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) { if (aggressive || lazyLoadTriggerMethods.contains(methodName)) { lazyLoader.loadAll(); } 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); } } }
load 办法就会执行真正的查问 sql 语句,将数据赋值给 User 对象,这样就实现了真正的懒加载操作,所以 Mybatis 的懒加载实际上就是利用动静代理将对象的参数封装进行了提早加载,当须要时再去调用真正的查问操作并返回数据。