mybatis源代码分析:深入了解mybatis延迟加载机制
2014-05-17 15:16 每天努力一点点 阅读(609) 评论(0) 编辑 收藏 举报下面的例子是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(org\apache\ibatis\executor\resultset)类会对数据库查询的每一行结果进行封装处理。
通过下面的代码可以看得更清楚,当结果对象中存在嵌套查询时,当前对象就会被替换为一个代理对象。
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的整个延迟加载过程就分析完了。