文章内容输出来源:拉勾教育Java高薪训练营;
系列文章连接:
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);
}
}
}