MyBatis框架全面解析(三)延迟加载的原理以及手写实现

文章内容输出来源:拉勾教育Java高薪训练营;
系列文章连接:

MyBatis框架全面解析(一)为何我们需要mybatis

MyBatis框架全面解析(二)如何手写一个简易的mybatis框架

MyBatis框架全面解析(四)mybatis插件原理+手写自定义插件delete转update



前言

本文将在上一在篇博文手写的myabtis简易框架的基础上,尝试对mybatis的延迟加载功能进行实现


一、延迟加载

概念

MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。

注意:延迟加载的应用要求:关联对象的查询与主加载对象的查询必须是分别进行的select语句,不能是使用多表连接所进行的select查询

加载时机

mybatis对于延迟加载的时机支持三种形式
直接加载:执行完对主加载对象的 select 语句,马上执行对关联对象的 select 查询。
侵入式延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情属性时,就会马上执行关联对象的select查询。
深度延迟: 执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的 select 查询。

使用示例

配置文件开启延迟加载配置:

<settings>
        <!-- 开启延迟加载,默认值是true -->
        <setting name="lazyLoadingEnabled" value="true" />
        <!-- 设置积极懒加载,默认值是true -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

mapper文件查询语句:

<mapper namespace="com.lagou.mapper.IOrderMapper">

    <resultMap id="orderMap" type="com.lagou.pojo.Order">
        <result column="id" property="id"/>
        <result column="ordertime" property="ordertime"/>
        <result column="total" property="total" />
        <association property="user" column="uid" select="com.lagou.mapper.IUserMapper.selectOne"/>
    </resultMap>
    
    <select id="selectList" resultMap="orderMap">
        select * from orders
    </select>
</mapper>

测试:

public class MybatisDemoTest {

    @Test
    public void test() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orders = mapper.selectList();
        for (Order order:orders){
            System.out.println(order.getId());
        }
        System.out.println("延迟加载");
        System.out.println(orders.get(0).getUser());
    }
}
执行结果:
1
2
3
延迟加载
13:02:30,228 DEBUG selectOne:159 - ==>  Preparing: select * from user where id = ? 
13:02:30,229 DEBUG selectOne:159 - ==> Parameters: 1(Integer)
13:02:30,232 DEBUG selectOne:159 - <==      Total: 1
User{id=1, username='lucy', password='123', birthday='2019-12-12'}

二:手动实现

非延迟加载

1.在上篇博文中,我们只实现了单条sql的语句执行封装,在实现延迟加载之前,我们应该先修改代码使其支持嵌套sql的查询
为了解析方便,我们定义mapper语句为下:

<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="com.lagou.mapper.IOrderMapper">

    <resultMap type="com.lagou.pojo.Order" id="orderMap">
        <result column="id" property="id"/>
        <result column="ordertime" property="ordertime"/>
        <result column="total" property="total"/>
        <!--方便解析 -->
        <result property="user" select="com.lagou.mapper.IUserMapper.selectById" column="uid">
        </result>
    </resultMap>
    
    <select id="selectList" resultMap="orderMap">
        select * from orders
    </select>

</mapper>

2.这种情况下,之前定义的MapperStatement就无法完全存储我们需要的信息,故需要扩展实体类,并进行解析封装。
注意:以下为了代码简洁,省略get、set、toString方法

/**
*解析sql语句的封装类
*/
public class MapperStatement {

    /**
     * sql唯一标识
     */
    private String id;

    /**
     * 自定义sql语句
     */
    private String sqlText;

    /**
     * 返回值类型,类的全路径
     */
    private String resultType;

    /**
     * 参数值类型,类的全路径
     */
    private String parameterType;

    /**
     * 解析带有resultMap的标签
     */
    private ResultMap resultMap;
}


 
public class ResultMap {

    /**
     * 对应的type
     */
    private String type;

    /**
     * 对应的id
     */
    private String id;
	
	  /**
     * 对应解析的result标签
     */
    private List<ResultMapping> resultMappings;

}
public class ResultMapping {

    /**
     * 关联查询的sqlId,根据此是否为空判断是否需要嵌套查询
     */
    private String selectId;

    private String property;

    private String column;
 
}

解析xml文件XmlMapperBuilder需要进行修改:

public class XmlMapperBuilder {

    /**
     * 定义configuration变量,是为了方便以后有复用的需求,若不保存,以后每次都要重新解析
     */
    private Configuration configuration;

    public XmlMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {
        Document mapperDocument = new SAXReader().read(inputStream);
        String nameSpace = mapperDocument.getRootElement().attributeValue("namespace");
        //1.解析resultMap节点,封装成ResultMap对象
        List<Element> resultMapElements = mapperDocument.selectNodes("//resultMap");
        Map<String,ResultMap> resultMapMap = new HashMap<>();
        for (Element element:resultMapElements){
            ResultMap resultMap = new ResultMap();
            resultMap.setId(element.attributeValue("id"));
            resultMap.setType(element.attributeValue("type"));
            List<ResultMapping> resultMappings = new ArrayList<>();
            List<Element> list = element.selectNodes("//result");
            for (Element mapElement:list){
                ResultMapping resultMapping = new ResultMapping();
                resultMapping.setColumn(mapElement.attributeValue("column"));
                resultMapping.setProperty(mapElement.attributeValue("property"));
                resultMapping.setSelectId(mapElement.attributeValue("select"));
                resultMappings.add(resultMapping);
            }
            resultMap.setResultMappings(resultMappings);
            resultMapMap.put(resultMap.getId(),resultMap);
        }
        //1.这里只解析select节点的语句
        List<Element> sqlElements = mapperDocument.selectNodes("//select");
        for (Element sqlElement:sqlElements){
            MapperStatement statement = new MapperStatement();
            //2.注意id的格式,我们一般采用namespace.id的形式
            statement.setId(nameSpace+"."+sqlElement.attributeValue("id"));
            statement.setParameterType(sqlElement.attributeValue("parameterType"));
            statement.setResultType(sqlElement.attributeValue("resultType"));
            //解析resultMap
            String resultMapId = sqlElement.attributeValue("resultMap");
            if (resultMapId!=null){
                statement.setResultMap(resultMapMap.get(resultMapId));
            }
            statement.setSqlText(sqlElement.getTextTrim());
            //3.放进configuration对象中的mappers对象里
            configuration.getMappers().put(statement.getId(),statement);
        }
    }
}

3.对SimpleExecutor类query方法查询的结果集封装进行修改

注意对入参类型为int进行特殊处理,之前针对得是对象,嵌套查询时会遇到入参为id得情况

public class SimpleExecutor implements Executor {

    @Override
    public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params)throws Exception {
      
        //1.获取连接对象
        Connection connection = configuration.getDataSource().getConnection();
        //2.解析sql语句,主要是替换占位符#{}为?,同时将要替换的参数名取出
        BoundSql boundSql = getBoundSql(mapperStatement.getSqlText());
        //3.生成PreparedStatement对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
        //4.获取要替换的参数名
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (mapperStatement.getParameterType()!=null){
            if (mapperStatement.getParameterType().equals("int")){
                preparedStatement.setObject(1,params[0]);
            }else{
                //5.获取sqlMapper定义的入参类型
                Class<?> paramTypeClass = getClassType(mapperStatement.getParameterType());
                for (int i=0;i<parameterMappings.size();i++){
                    ParameterMapping parameterMapping = parameterMappings.get(i);
                    //参数名
                    String content = parameterMapping.getContent();
                    //6.通过反射的形式,获取入参对象中对应参数名的属性值,并封装到PreparedStatement对象中
                    Field declaredField = paramTypeClass.getDeclaredField(content);
                    declaredField.setAccessible(true);
                    Object o = declaredField.get(params[0]);
                    preparedStatement.setObject(i+1,o);
                }
            }
        }

        //7.获取查询结果集
        ResultSet resultSet = preparedStatement.executeQuery();

        List<Object> resultList = new ArrayList<>();

        //之前解析resultType节点的代码
        if (mapperStatement.getResultType()!=null) {
            //8.获取sqlMapper定义的返回值类型
            Class<?> resultTypeClass = Class.forName(mapperStatement.getResultType());
            while (resultSet.next()) {
                Object o = resultTypeClass.newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    Object value = resultSet.getObject(columnName);
                    //9.java提供的内省工具包,进行对象的复制
                    PropertyDescriptor propertyDescriptor = new
                            PropertyDescriptor(columnName, resultTypeClass);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(o, value);
                }
                resultList.add(o);
            }
        }else {
            //这里解析resultMap节点
            ResultMap resultMap = mapperStatement.getResultMap();
            String type = resultMap.getType();
            List<ResultMapping> resultMappings = resultMap.getResultMappings();
            //转换成map,方便根据数据库字段查询到相应的配置信息
            Map<String,ResultMapping> map = resultMappings.stream().collect(Collectors.toMap(ResultMapping::getColumn,resultMapping -> resultMapping));
            Class<?> resultTypeClass = Class.forName(type);
            while (resultSet.next()) {
                Object o = resultTypeClass.newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    Object value = resultSet.getObject(columnName);
                    ResultMapping resultMapping = map.get(columnName);
                    //为了方便,要求每个从数据库查询出的字段都需要配置在xml文件中
                    PropertyDescriptor propertyDescriptor = new
                            PropertyDescriptor(resultMapping.getProperty(), resultTypeClass);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    if (resultMapping.getSelectId()!=null){
                        //这里证明该字段需要嵌套查询,复用查询的代码
                        List<Object> result = query(configuration, configuration.getMappers().get(resultMapping.getSelectId()), value);
                        //本版本仅支持一对一的查询,不再判断返回值是否是list
                        writeMethod.invoke(o, result.get(0));
                    }else {
                        writeMethod.invoke(o, value);
                    }
                }
                resultList.add(o);
            }
        }

        return (List<E>)  resultList;
    }

  

    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (parameterType == null){
            return null;
        }
        return Class.forName(parameterType);
    }

    private BoundSql getBoundSql(String sql){
        //对该sqlText进行转换,解析占位符,解析的工具类是直接从mybatis中提取出来的,并未进行自己实现,理论上实现一个简易的也并不困难
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{","}",tokenHandler);
        String sqlText = parser.parse(sql);
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql();
        boundSql.setSql(sqlText);
        boundSql.setParameterMappings(parameterMappings);
        return boundSql;
    }
}

执行测试:

public class IPersistenceTest {
    @Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //调用
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orders = mapper.selectList();
        for (Order order:orders){
            System.out.println(order);
        }
    }
}
测试结果:
Order{id=1, ordertime=2019-12-12, total=3000.0, user=User{id=1, username='lucy', password='123', birthday='2019-12-12'}}
Order{id=2, ordertime=2019-12-12, total=4000.0, user=User{id=1, username='lucy', password='123', birthday='2019-12-12'}}
Order{id=3, ordertime=2019-12-12, total=5000.0, user=User{id=2, username='tom', password='123', birthday='2019-12-12'}}

至此我们已实现非延迟加载的嵌套查询,在此基础上尝试实现延迟加载的功能

延迟加载

1.实现原理:
归纳起来,我们的目标是查询返回的实体类在调用指定字段的关联方法时,触发sql查询。那么可以将实体类进行代理,在调用方法时进行拦截,如果判断需要触发sql查询,则此时执行查询,将结果封装。
此处jdk动态代理不适用,我们可以选择cglib代理的方式(mybatis提供了两种实现,javassist代理和cglib代理,此处我们尝试用cglib)

2.代码实现
修改对resultMap节点封装的处理方法

//这里解析resultMap节点
            ResultMap resultMap = mapperStatement.getResultMap();
            String type = resultMap.getType();
            List<ResultMapping> resultMappings = resultMap.getResultMappings();
            //转换成map,方便根据数据库字段查询到相应的配置信息
            Map<String,ResultMapping> map = resultMappings.stream().collect(Collectors.toMap(ResultMapping::getColumn,resultMapping -> resultMapping));
            Class<?> resultTypeClass = Class.forName(type);
            while (resultSet.next()) {

			   //此处封装需要延迟加载的字段
                List<LazyLoad> lazyField = new ArrayList<>();

                Object o = resultTypeClass.newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnName(i);
                    Object value = resultSet.getObject(columnName);
                    ResultMapping resultMapping = map.get(columnName);
                    //为了方便,要求每个从数据库查询出的字段都需要配置在xml文件中
                    PropertyDescriptor propertyDescriptor = new
                            PropertyDescriptor(resultMapping.getProperty(), resultTypeClass);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    if (resultMapping.getSelectId()!=null){

						//这里修改代码,不再直接查询,将查询返回字段值,mapper里的配置属性封装,传给代理类,用于查询
                        LazyLoad lazyLoad = new LazyLoad();
                        lazyLoad.setArgs(value);
                        lazyLoad.setColumn(resultMapping.getColumn());
                        lazyLoad.setConfiguration(configuration);
                        lazyLoad.setProperty(resultMapping.getProperty());
                        lazyLoad.setSelectId(resultMapping.getSelectId());
                        lazyField.add(lazyLoad);




                    }else {
                        writeMethod.invoke(o, value);
                    }
                }
                //有延迟加载的字段,生成代理类
                if (lazyField.size()>0){
                    o = new CglibProxyFactory().createProxy(o,lazyField);
                }
                resultList.add(o);
            }
        }

封装的对象LazyLoad

public class LazyLoad {
    private String selectId;

    private String property;

    private String column;

    private Configuration configuration;
	//sql语句查询出来的值,也是嵌套sql的请求参数
    private Object args;

	//该方法用来执行嵌套sql的查询,并封装到代理对象中
    public void load(Object target)throws Exception{
        PropertyDescriptor propertyDescriptor = new
                PropertyDescriptor(property, target.getClass());
        Method writeMethod = propertyDescriptor.getWriteMethod();
        List<Object> query = new SimpleExecutor().query(configuration, configuration.getMappers().get(selectId), args);
        writeMethod.invoke(target, query.get(0));
    }
}

CglibProxyFactory实现:

public class CglibProxyFactory {

    public Object createProxy(Object target,List<LazyLoad> lazyLoader) {
        return new EnhancedResultObjectProxyImpl().createProxy(target, lazyLoader);
    }

    private  class EnhancedResultObjectProxyImpl implements MethodInterceptor {
        private Map<String, LazyLoad> lazyLoaderMap;

        public EnhancedResultObjectProxyImpl() {
        }

        private EnhancedResultObjectProxyImpl(List<LazyLoad> lazyLoader) {
            //将需要延迟加载字段,暂存到map中
            this.lazyLoaderMap = lazyLoader.stream().collect(Collectors.toMap(LazyLoad::getProperty,lazyLoad -> lazyLoad));
        }

        public  Object createProxy(Object target, List<LazyLoad> lazyLoader) {
            Class type = target.getClass();
            EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(lazyLoader);
            Enhancer enhancer = new Enhancer();
            enhancer.setCallback(callback);
            enhancer.setSuperclass(type);
            Object result = enhancer.create();
            
            //这里用来复制字段值
            PropertyCopier.copyBeanProperties(type, target, result);
            return result;
        }

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            String name = method.getName();
            //这里判断方法名是不是get方法
            if (PropertyNamer.isGetter(name)) {
                //这里获取方法对应的是哪个字段
                String property = PropertyNamer.methodToProperty(name);
                //该字段需要延迟加载
                if (lazyLoaderMap.size() > 0 && lazyLoaderMap.containsKey(property)) {
                    //只需要延迟加载一次
                    lazyLoaderMap.remove(property).load(o);
                }
            }
            return  methodProxy.invokeSuper(o, objects);
        }
    }
}

这里有两个工具类直接采用了mybatis的实现:
PropertyCopier用来复制字段,目的是将源对象的字段值复制到代理对象中,主要是那些不需要延迟加载的字段

PropertyNamer用来对方法名进行处理,判断方法名是否是get、set方法,获取方法名操作的是那个字段属性。
以下附代码:

public final class PropertyCopier {

    private PropertyCopier() {
        // Prevent Instantiation of Static Class
    }

    public static void copyBeanProperties(Class<?> type, Object sourceBean, Object destinationBean) {
        Class<?> parent = type;
        while (parent != null) {
            final Field[] fields = parent.getDeclaredFields();
            for(Field field : fields) {
                try {
                    field.setAccessible(true);
                    field.set(destinationBean, field.get(sourceBean));
                } catch (Exception e) {
                    // Nothing useful to do, will only fail on final fields, which will be ignored.
                }
            }
            parent = parent.getSuperclass();
        }
    }

}
public final class PropertyNamer {

    private PropertyNamer() {
        // Prevent Instantiation of Static Class
    }

    public static String methodToProperty(String name) {
        if (name.startsWith("is")) {
            name = name.substring(2);
        } else if (name.startsWith("get") || name.startsWith("set")) {
            name = name.substring(3);
        } else {
            throw new RuntimeException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
        }

        if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
        }

        return name;
    }

    public static boolean isProperty(String name) {
        return name.startsWith("get") || name.startsWith("set") || name.startsWith("is");
    }

    public static boolean isGetter(String name) {
        return name.startsWith("get") || name.startsWith("is");
    }

    public static boolean isSetter(String name) {
        return name.startsWith("set");
    }

}

测试结果:
为了更加结果更加清晰,再SimpleExecutor得执行处加入sql语句打印

System.out.println(“查询sql:”+mapperStatement.getSqlText());

测试:

public class IPersistenceTest {
    @Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //调用
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orders = mapper.selectList();
        System.out.println("-----------------------------toString方法暂时不触发加载----------------------");
        for (Order order:orders){
            System.out.println(order);
        }
        System.out.println("-----------------------------延迟加载----------------------");
        for (Order order:orders){
            System.out.println(order.getUser());
        }
        System.out.println("-----------------------------再次查询----------------------");
        for (Order order:orders){
            System.out.println(order);
        }

    }
}
执行结果:
查询sql:select * from orders
-----------------------------toString方法暂时不触发加载----------------------
Order{id=1, ordertime=2019-12-12, total=3000.0, user=null}
Order{id=2, ordertime=2019-12-12, total=4000.0, user=null}
Order{id=3, ordertime=2019-12-12, total=5000.0, user=null}
-----------------------------延迟加载----------------------
查询sql:select * from user where id = #{id}
User{id=1, username='lucy', password='123', birthday='2019-12-12'}
查询sql:select * from user where id = #{id}
User{id=1, username='lucy', password='123', birthday='2019-12-12'}
查询sql:select * from user where id = #{id}
User{id=2, username='tom', password='123', birthday='2019-12-12'}
-----------------------------再次查询----------------------
Order{id=1, ordertime=2019-12-12, total=3000.0, user=User{id=1, username='lucy', password='123', birthday='2019-12-12'}}
Order{id=2, ordertime=2019-12-12, total=4000.0, user=User{id=1, username='lucy', password='123', birthday='2019-12-12'}}
Order{id=3, ordertime=2019-12-12, total=5000.0, user=User{id=2, username='tom', password='123', birthday='2019-12-12'}}

反思:
代码功能已经实现,从整个架构上来说,代码结构还是比较粗糙,结果集得处理过于冗长,后续可以用设计模式进行优化。延迟加载得对象封装过于简单,load方法也可以进行优化。主要还是了解下延迟加载得思想。

优化

引入配置文件控制延迟加载得开启
规定配置文件得格式为:

   <settings>
        <setting name="lazyLoad" value="true"></setting>
    </settings>

Configuration新加配置字段

public class Configuration {
	//默认关闭
    private Boolean lazy = false;

    /**
     * 封装成数据源
     */
    private DataSource dataSource;

    /**
     * 将所有的自定义sql语句封装到map中
     * key为sql语句的唯一标识,value为封装了sql语句配置信息的对象
     */
    private Map<String,MapperStatement> mappers =  new HashMap<>();
}

XmlConfigBuilder添加解析

  //5.读取setting字段,配置延迟加载
        List<Element> settingElement = document.selectNodes("//setting");
        for (Element element:settingElement){
            String name = element.attributeValue("name");
            if (name.equals("lazyLoad")){
                configuration.setLazy(Boolean.parseBoolean(element.attributeValue("value")));
            }
        }

SimpleExecutor对resultMap得解析添加判断:

 if (resultMapping.getSelectId()!=null){
    if (configuration.getLazy()) {
        LazyLoad lazyLoad = new LazyLoad();
        lazyLoad.setArgs(value);
		lazyLoad.setColumn(resultMapping.getColumn());
		lazyLoad.setConfiguration(configuration);
		lazyLoad.setProperty(resultMapping.getProperty());
		lazyLoad.setSelectId(resultMapping.getSelectId());
 		lazyField.add(lazyLoad);
     }else {
        //这里证明该字段需要嵌套查询,复用查询的代码
        List<Object> result = query(configuration, configuration.getMappers().get(resultMapping.getSelectId()), value);
        //本版本仅支持一对一的查询,不再判断返回值是否是list
        writeMethod.invoke(o, result.get(0));
       }

测试:
关闭延迟加载

   <settings>
        <setting name="lazyLoad" value="false"></setting>
    </settings>
public class IPersistenceTest {
    @Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = SqlSessionFactoryBuilder.build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //调用
        IOrderMapper mapper = sqlSession.getMapper(IOrderMapper.class);
        List<Order> orders = mapper.selectList();
        for (Order order:orders){
            System.out.println(order);
        }
    }
}

执行结果
Order{id=1, ordertime=2019-12-12, total=3000.0, user=User{id=1, username='lucy', password='123', birthday='2019-12-12'}}
Order{id=2, ordertime=2019-12-12, total=4000.0, user=User{id=1, username='lucy', password='123', birthday='2019-12-12'}}
Order{id=3, ordertime=2019-12-12, total=5000.0, user=User{id=2, username='tom', password='123', birthday='2019-12-12'}}

附录:
mybatis得延迟加载实现:


  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<Class<?>>();
    final List<Object> constructorArgs = new ArrayList<Object>();
    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()) {
        //此处就是生成代理对象得方法
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    this.useConstructorMappings = (resultObject != null && !constructorArgTypes.isEmpty()); // set current mapping result
    return resultObject;
  }
public class CglibProxyFactory implements ProxyFactory {

  private static final Log log = LogFactory.getLog(CglibProxyFactory.class);
  private static final String FINALIZE_METHOD = "finalize";
  private static final String WRITE_REPLACE_METHOD = "writeReplace";

  public CglibProxyFactory() {
    try {
      Resources.classForName("net.sf.cglib.proxy.Enhancer");
    } catch (Throwable e) {
      throw new IllegalStateException("Cannot enable lazy loading because CGLIB is not available. Add CGLIB to your classpath.", e);
    }
  }

  @Override
  public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  }

  public Object createDeserializationProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return EnhancedDeserializationProxyImpl.createProxy(target, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
  }

  @Override
  public void setProperties(Properties properties) {
      // Not Implemented
  }

  static Object crateProxy(Class<?> type, Callback callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Enhancer enhancer = new Enhancer();
    enhancer.setCallback(callback);
    enhancer.setSuperclass(type);
    try {
      type.getDeclaredMethod(WRITE_REPLACE_METHOD);
      // ObjectOutputStream will call writeReplace of objects returned by writeReplace
      if (log.isDebugEnabled()) {
        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;
    if (constructorArgTypes.isEmpty()) {
      enhanced = enhancer.create();
    } else {
      Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);
      Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);
      enhanced = enhancer.create(typesArray, valuesArray);
    }
    return enhanced;
  }

  private static class EnhancedResultObjectProxyImpl implements MethodInterceptor {

    private final Class<?> type;
    //这里是核心类,延迟加载对象得封装
    private final ResultLoaderMap lazyLoader;
    private final boolean aggressive;
    private final Set<String> lazyLoadTriggerMethods;
    private final ObjectFactory objectFactory;
    private final List<Class<?>> constructorArgTypes;
    private final List<Object> constructorArgs;

    private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      this.type = type;
      this.lazyLoader = lazyLoader;
      this.aggressive = configuration.isAggressiveLazyLoading();
      this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();
      this.objectFactory = objectFactory;
      this.constructorArgTypes = constructorArgTypes;
      this.constructorArgs = constructorArgs;
    }

    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      final Class<?> type = target.getClass();
      EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    @Override
    public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) 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 CglibSerialStateHolder(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);
              } else if (PropertyNamer.isGetter(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invokeSuper(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

  private static class EnhancedDeserializationProxyImpl extends AbstractEnhancedDeserializationProxy implements MethodInterceptor {

    private EnhancedDeserializationProxyImpl(Class<?> type, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      super(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }

    public static Object createProxy(Object target, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      final Class<?> type = target.getClass();
      EnhancedDeserializationProxyImpl callback = new EnhancedDeserializationProxyImpl(type, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

    @Override
    public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      final Object o = super.invoke(enhanced, method, args);
      return (o instanceof AbstractSerialStateHolder) ? o : methodProxy.invokeSuper(o, args);
    }

    @Override
    protected AbstractSerialStateHolder newSerialStateHolder(Object userBean, Map<String, ResultLoaderMap.LoadPair> unloadedProperties, ObjectFactory objectFactory,
            List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
      return new CglibSerialStateHolder(userBean, unloadedProperties, objectFactory, constructorArgTypes, constructorArgs);
    }
  }
}
  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis实现延迟加载原理是通过动态代理技术,当查询到需要延迟加载的属性时,不立即去数据库查询,而是返回一个代理对象,等到使用该属性时再去查询数据库。 下面是一个简单的示例: 1. 定义User类和Order类,User类中有一个List<Order>属性: public class User { private int id; private String username; private List<Order> orders; // getter和setter } public class Order { private int id; private String orderNo; // getter和setter } 2. 定义UserMapper接口,其中定义一个selectById方法,查询用户信息以及用户的订单信息: public interface UserMapper { User selectById(int id); } 3. 定义UserMapper.xml文件,实现selectById方法: <select id="selectById" resultMap="userResultMap"> select * from user where id = #{id} </select> <resultMap id="userResultMap" type="User"> <id column="id" property="id"/> <result column="username" property="username"/> <collection property="orders" ofType="Order" select="selectOrdersByUserId"/> </resultMap> <select id="selectOrdersByUserId" resultMap="orderResultMap"> select * from order where user_id = #{id} </select> <resultMap id="orderResultMap" type="Order"> <id column="id" property="id"/> <result column="order_no" property="orderNo"/> </resultMap> 4. 在Mybatis配置文件中配置延迟加载: <configuration> <settings> <setting name="lazyLoadingEnabled" value="true"/> </settings> </configuration> 5. 在Java代码中调用selectById方法获取用户信息: User user = userMapper.selectById(1); 此时,只有用户信息被查询出来,用户的订单信息并没有被查询出来,而是返回了一个代理对象。 6. 当使用用户的订单信息时,代理对象会去查询数据库,获取订单信息: List<Order> orders = user.getOrders(); 此时,代理对象会调用selectOrdersByUserId方法查询数据库,获取用户的订单信息。 通过以上过程可以看出,Mybatis实现延迟加载原理就是通过代理对象实现的。当需要延迟加载的属性被调用时,代理对象会去查询数据库获取数据,从而实现延迟加载
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值