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
    
    
    
    
    
  • 相关阅读:
    UVa 12174 (滑动窗口) Shuffle
    UVa 1607 (二分) Gates
    CodeForces ZeptoLab Code Rush 2015
    HDU 1525 (博弈) Euclid's Game
    HDU 2147 (博弈) kiki's game
    UVa 11093 Just Finish it up
    UVa 10954 (Huffman 优先队列) Add All
    CodeForces Round #298 Div.2
    UVa 12627 (递归 计数 找规律) Erratic Expansion
    UVa 714 (二分) Copying Books
  • 原文地址:https://www.cnblogs.com/jerry1999/p/3740150.html
Copyright © 2011-2022 走看看