zoukankan      html  css  js  c++  java
  • mybatis源代码分析:深入了解mybatis延迟加载机制

    下文从mybatis(3.2.7)延迟加载样例讲起,逐步深入其实现机制。

    下面的例子是Student类关联一个Teacher对象,在访问Student对象时,不立即加载其关联的Teacher对象,而是等到访问Teacher对象的属性时,才加载Teacher对象。

    源代码下载:http://download.csdn.net/detail/u014569459/7363097

    1.Student.java

    package dao.domain;
    
    public class Student {
    	public int id;
    	public String name;
    	public int teacher_id;
    	public Teacher teacher;
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getTeacher_id() {
    		return teacher_id;
    	}
    	public void setTeacher_id(int teacher_id) {
    		this.teacher_id = teacher_id;
    	}
    	public Teacher getTeacher() {
    		return teacher;
    	}
    	public void setTeacher(Teacher teacher) {
    		this.teacher = teacher;
    	}
    
    }
    


    2.Teacher.java

    package dao.domain;
    
    public class Teacher {
    	public int id;
    	public String name;
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	
    }
    

    3.StudentDAO.java

    package dao;
    
    import dao.domain.Student;
    
    public interface StudentDAO {
    	public Student getStudentByID(int id);
    }
    

    4.StudentDAOImpl.java

    package dao.impl;
    
    import java.io.IOException;
    import java.io.Reader;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import dao.StudentDAO;
    import dao.domain.Student;
    
    public class StudentDaoImpl implements StudentDAO {
    	private static SqlSession session = null;
    	private static StudentDAO mapper = null;
    	
    	static{
    		String resouce = "config/ibatisConfiguration.xml";
    		Reader reader = null;
    		try {
    			reader = Resources.getResourceAsReader(resouce);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    		
    		SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
    		session = factory.openSession();
    		mapper = session.getMapper(StudentDAO.class);
    		
    		try {
    			reader.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    
    	@Override
    	public Student getStudentByID(int id) {
    		return mapper.getStudentByID(id);
    	}
    	
    }
    

    5.TeacherMapper.xml

    <?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="dao.TeacherDAO">
    	<select id="getTeacherByID" parameterType="int" resultType="Teacher">
    		select id,name
    		from p_teacher
    		where
    		id = #{id}
    	</select>
    </mapper>

    6.StudentMapper.xml

    <?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="dao.StudentDAO">
    	<resultMap id="StudentMap" type="Student">
    		<id property="id" column="id" />
    		<result property="name" column="name" />
    		<association property="teacher" column="teacher_id"
    			select="dao.TeacherDAO.getTeacherByID" />
    	</resultMap>
    
    	<select id="getStudentByID" resultMap="StudentMap"
    		parameterType="int">
    		select *
    		from p_stu
    		where
    		id = #{id}
    	</select>
    </mapper>

    7.ibatisConfiguration.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
    	<settings>
    		<setting name="lazyLoadingEnabled" value="true" />
    		<setting name="aggressiveLazyLoading" value="true" />
    	</settings>
    	<typeAliases>
    		<typeAlias alias="Student" type="dao.domain.Student" />
    		<typeAlias alias="Teacher" type="dao.domain.Teacher" />
    	</typeAliases>
    	<environments default="development">
    		<environment id="development">
    			<transactionManager type="JDBC" />
    			<dataSource type="POOLED">
    				<property name="driver" value="com.mysql.jdbc.Driver" />
    				<property name="url"
    					value="jdbc:mysql://localhost:3307/test?characterEncoding=UTF-8" />
    				<property name="username" value="root" />
    				<property name="password" value="root" />
    			</dataSource>
    		</environment>
    	</environments>
    	<mappers>
    		<mapper resource="config/StudentMapper.xml" />
    		<mapper resource="config/TeacherMapper.xml" />
    	</mappers>
    </configuration>

    8.测试类

    import dao.StudentDAO;
    import dao.domain.Student;
    import dao.impl.StudentDaoImpl;
    
    public class Test {
    	public static void main(String[] args) {
    		StudentDAO dao = new StudentDaoImpl();
    		Student article = dao.getStudentByID(1);
    		
    		System.out.println(article.getTeacher().getName());
    	}
    }
    


    9.mybatis延迟加载说明:

    1)在mybatis配置文件中,配置如下两个配置项:

    	<settings>
    		<setting name="lazyLoadingEnabled" value="true" />
    		<setting name="aggressiveLazyLoading" value="true" />
    	</settings>

    2)查询语句样例,下面通过association将Student对象一个属性与一个查询语句关联起来:

    	<resultMap id="StudentMap" type="Student">
    		<id property="id" column="id" />
    		<result property="name" column="name" />
    		<association property="teacher" column="teacher_id"
    			select="dao.TeacherDAO.getTeacherByID" />
    	</resultMap>

    这样,在查询出StudentMap这个结果集时,对于teacher字段就会采取延迟加载了,等到访问teacher对象属性时,才会去加载关联的teacher对象。

    10.逐步深入延迟加载细节

    1)在Test类中如下行打上断点,然后进行Debug

    System.out.println(article.getTeacher().getName());

    可以看到article对象为(每次测试,结果会不同):dao.domain.Student$$EnhancerByCGLIB$$aa39207a@adc40c

    这就是一个cglib自动生成的代理对象。

    2)那这个代理对象什么时候生成的呢? 经过反复不停的debug跟踪,得到下面这样一个调用栈。

    Thread [main] (Suspended (breakpoint at line 61 in CglibProxyFactory))	
    	CglibProxyFactory.createProxy(Object, ResultLoaderMap, Configuration, ObjectFactory, List<Class<?>>, List<Object>) line: 61	
    	DefaultResultSetHandler.createResultObject(ResultSetWrapper, ResultMap, ResultLoaderMap, String) line: 512	
    	DefaultResultSetHandler.getRowValue(ResultSetWrapper, ResultMap) line: 331	
    	DefaultResultSetHandler.handleRowValuesForSimpleResultMap(ResultSetWrapper, ResultMap, ResultHandler, RowBounds, ResultMapping) line: 291	
    	DefaultResultSetHandler.handleRowValues(ResultSetWrapper, ResultMap, ResultHandler, RowBounds, ResultMapping) line: 266	
    	DefaultResultSetHandler.handleResultSet(ResultSetWrapper, ResultMap, List<Object>, ResultMapping) line: 236	
    	DefaultResultSetHandler.handleResultSets(Statement) line: 150	
    	PreparedStatementHandler.query(Statement, ResultHandler) line: 60	
    	RoutingStatementHandler.query(Statement, ResultHandler) line: 73	
    	SimpleExecutor.doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql) line: 60	
    	SimpleExecutor(BaseExecutor).queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) line: 267	
    	SimpleExecutor(BaseExecutor).query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) line: 137	
    	CachingExecutor.query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql) line: 96	
    	CachingExecutor.query(MappedStatement, Object, RowBounds, ResultHandler) line: 77	
    	DefaultSqlSession.selectList(String, Object, RowBounds) line: 108	
    	DefaultSqlSession.selectList(String, Object) line: 102	
    	DefaultSqlSession.selectOne(String, Object) line: 66	
    	MapperMethod.execute(SqlSession, Object[]) line: 68	
    	MapperProxy<T>.invoke(Object, Method, Object[]) line: 52	
    	$Proxy0.getStudentByID(int) line: not available	
    	StudentDaoImpl.getStudentByID(int) line: 40	
    	Test.main(String[]) line: 12	
    

    查看原图

    可以看到在查询Student对象时,DefaultResultSetHandler(orgapacheibatisexecutor esultset)类会对数据库查询的每一行结果进行封装处理。

    通过下面的代码可以看得更清楚,当结果对象中存在嵌套查询时,当前对象就会被替换为一个代理对象。

    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
        final List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();
        final List<Object> constructorArgs = new ArrayList<Object>();
        final Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
          final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
          for (ResultMapping propertyMapping : propertyMappings) {
            if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { // issue gcode #109 && issue #149
              return configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
            }
          }
        }
        return resultObject;
      }


    3)那当访问代理对象时,会发生什么事情呢?

    如调用System.out.println(article.getTeacher().getName())时,得到如下的调用栈信息

    Thread [main] (Suspended (breakpoint at line 143 in CglibProxyFactory$EnhancedResultObjectProxyImpl))	
    	owns: ResultLoaderMap  (id=38)	
    	CglibProxyFactory$EnhancedResultObjectProxyImpl.intercept(Object, Method, Object[], MethodProxy) line: 143	
    	Student$$EnhancerByCGLIB$$e1afda28.getTeacher() line: not available	
    	Test.main(String[]) line: 10	
    

    代码执行到了如下地方:

                if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
                  if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                    lazyLoader.loadAll();
                  } else if (PropertyNamer.isProperty(methodName)) {
                    final String property = PropertyNamer.methodToProperty(methodName);
                    if (lazyLoader.hasLoader(property)) {
                      lazyLoader.load(property);
                    }
                  }
                }
              

    通过调用lazyLoader.loadAll来对需要加载的对象进行加载.

    继续顺藤摸瓜,得到如下的调用栈:

    Thread [main] (Suspended)	
    	owns: ResultLoaderMap  (id=38)	
    	BeanWrapper.set(PropertyTokenizer, Object) line: 57	
    	MetaObject.setValue(String, Object) line: 133	
    	ResultLoaderMap$LoadPair.load(Object) line: 207	
    	ResultLoaderMap$LoadPair.load() line: 172	
    	ResultLoaderMap.load(String) line: 80	
    	ResultLoaderMap.loadAll() line: 90	
    	CglibProxyFactory$EnhancedResultObjectProxyImpl.intercept(Object, Method, Object[], MethodProxy) line: 143	
    	Student$$EnhancerByCGLIB$$e1afda28.getTeacher() line: not available	
    	Test.main(String[]) line: 10	
    

    如下是BeanWrapper中setValue方法:

    public void set(PropertyTokenizer prop, Object value) {
        if (prop.getIndex() != null) {
          Object collection = resolveCollection(prop, object);
          setCollectionValue(prop, collection, value);
        } else {
          setBeanProperty(prop, object, value);
        }
      }

    通过setBeanProperty方法,就完成向Student对象设置Teacher属性的过程(上面代码中的object就是Student对象)。

    下面通过一张图,展示一下代理对象与实际对象之间的关系,“Student$$EnhancerByCGLIB$$aa39207a@adc40c”为代理对象,是Student类的子类,当访问到Student的getTeacher方法时,其通过关联的EnhancedResultObjectProxyImpl类来加载teacher对象,具体关联见下图,其中BeanWrapper中object就是代理对象本身,通过对object的设置,将teacher属性设置为从数据库查询出来的对象,完成延迟加载。

    至此,mybatis的整个延迟加载过程就分析完了。






    参考链接:
    mybatis下载主页:https://github.com/mybatis/mybatis-3
    
    
    
    
    
  • 相关阅读:
    常用dos命令
    反射
    干货|技术小白如何在45分钟内发行通证(TOKEN)并上线交易(附流程代码
    基于以太坊发布属于自己的数字货币(代币)完整版
    基于以太坊实现代币发布
    FTRL的理解
    FM-分解机模型详解
    深度学习总结
    DIN
    git上传新项目
  • 原文地址:https://www.cnblogs.com/jerry1999/p/3740150.html
Copyright © 2011-2022 走看看