因为万事万物皆可为对象,所以我们要从对象的角度,比如键值对,来思考这些配置文件各自的优缺点
特点:可以有属性和标签,他们都可以表达键值对,而属性和标签是我们知道的值,然后我们利用这些去索引我们想要的值
特点:数据和属性的层级分明
一般情况下,如果我们要配置一个对象的值,最好使用这种方法
springBoot主流配置方式,因为它的可读性最强,所以我们要优先考虑它,但是它不适合那种数量特别多,长度特别大,映射逻辑非常自由且复杂(resultMap那种可递归的配置)
注解配置,利用注解标识,扫描后自动执行相关配置
代码配置,一般是一个类的构建过程比较复杂,文件配置难以实现,就算实现,也太麻烦
为什么尽量不使用代码,因为一般不容易看懂,必须要懂一些原理才能使用代码配置
框架一般怎么处理异常?
框架的好多异常处理,都是抓住一个异常,然后抛出自己的自定义异常,这个方式有个优点,自定义的异常,可以包含自己想要的更多信息,也容易让全局异常处理器来处理,而且错误类型分明,方便读者去理解,关键还多抛出的异常,最好能够帮助读者直接定位问题
除了特别常见的IllegalArgumentException或者NullPointerException,其他的异常,反正能归于自己的问题尽量把问题归于自己,因为这个别人在修改的时候好下手,如果你抛出一个java的异常,他很可能不知道是哪个框架出现问题了
还有就是当报异常的时候,真心建议你看看内容,而不是直接百度,或者自己随便跟着感觉乱改,从时间来看,只要知道自己该怎么改,你解决问题的速度肯定会快很多的,此外,每次解决问题都应该总结一些内容,这样,让更多的问题会被更加轻松的解决
框架的一般结构是什么?
有几个接口,然后一个抽象类实现了一部分接口的函数,然后让子类来实现其他的函数,自己负责的一般是公共的部分,谈几点,为什么不在接口层面实现,如果我们有两类默认方法,还是抽象类实现比较好,而且抽象类可以有自己的属性,怎么理解公共部分,我觉得,我们可以把一个大的框架写在公共部分,然后这个大的框架的里面有一些特殊的逻辑,我们把这些逻辑放在其他方法中,但是不实现,或者让之类可以覆盖,这样的效果是最好的
这些设计模式和spring,mybatis的原理精密相关,建议理解一下
系统与一个代理对象交互,代理对象帮助我们完成额外的工作,而我们只需要完成核心的业务逻辑
比如springAOP帮助我们完成额外的登陆验证,基本日志等,mybatis也使用代理帮助我们把mapper接口做了实现,拦截器也是一种动态代理
和普通的静态代理不同的是,动态代理不需要我们手动去编写代理类,手动编写的不仅繁琐,而且代码臃肿
如果我们的一个申请,需要经过多方审核
比如我对某个想要调换自己监考的老师发出了一个申请,我想和他交换我的一门考试。当这个申请发出的时候,得经过系统的时间冲突判断,然后再经过他的确认,他在确认的时候,也会判断时间冲突,最后是管理的审核
这种就可以是多个拦截器,共同组合起来,一层一层的把我们的核心业务包裹,然后一个一个的处理
称作发布订阅模式更好,通俗来讲,就是一方发布消息,其他订阅者可以立马响应这种变化
我们在写的时候,不能自己主动触发。什么是自己主动触发?就是当我们发布的消息后,主动的调用订阅者相应这条消息的方法。这样有几个问题
当我们的订阅者变多的时候,我们需要修改自己的代码
而且订阅者做什么也不应该由我们来操作
一个订阅者的方法执行逻辑出错会影响到其他订阅者
那,怎么解决这个问题,想想现实的世界,我们直接给他们发通知,让他们自己去处理,比如他们订阅了我们的报纸,我们发消息,告诉他们,他们看与不看,什么时候看,怎么看,都与我们无关
代码实现
发布者
public class Publisher extends Observable {//继承这个类,这个类帮助我们实现了观察者的基本方法
private List<String> productsList;
private static Publisher instance;//单例模式
private Publisher(){}//私有化构造器
public static Publisher getInstance(){
if(instance==null){//双重判断,大部分情况判断不为null,直接返回
synchronized(Publisher.class){//少数情况并发判断为null
instance=new Publisher();
instance.productsList=new ArrayList<>();
}
}
return instance;
}
public void addProduct(String product){
productsList.add(product);
//下面两个方法,少一个都不能运行
this.setChanged();
//标识变量,并发问题中常常有这个变量,访问的时候需要加锁
this.notifyObservers(product);//下面有我的源码分析
}
}
notifyObservers源码分析
Object[] arrLocal;
synchronized (this) {
//这个地方锁了this,我上面那个方法不能锁,是因为那个是静态方法,不可能锁对象只能是XXX.class
if (!changed)
//如果没有改变,那么结束函数,也就是说,调用这个函数之前,得setChanged
return;
arrLocal = obs.toArray();//转化为数组,因为数组效率高,没有并发的问题
clearChanged();
//把changed这个标识变量设置为false,和!changed再次呼应,不然会出现并发通知的情况
}
//上面这段代码,在新的观察者进来是会有问题的,因为他不在下面这个数组中
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);//this是发送通知的一方,arg是发生的参数,明显是有一定顺序的发生,不是那种并发随机的,也没有必要
虽然名字是叫notify但是和线程无关,靠的是执行订阅者列表的接口方法
观察者
public class xxxxObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println(xxx+"正在处理新的消息:"+arg);
}
}
使用
有一说一,还是有些奇怪的,我觉得应该是订阅者主动订阅的?希望有大佬解答我的疑惑,就是说,我要是不想订阅你,订阅了另一个,但我只能有一个update方法,我这不得主动判断一下是谁发给我的通知吗?
有的对象类型类似,而且创建起来麻烦,这个时候我们需要一个生产和组装对象的工厂,帮助我们来创建这些对象,从解耦合的角度来看,就是把对象的创建和使用分离
抽象工厂
对象类型很多,我们需要有一个抽象的工厂,然后分成几类工厂去继承抽象工厂,其实就相当于一个二级工厂分类
如果我们一个对象的内部构建过程是前后时序相关的,这个顺序不能被打乱
比如我们在构建汽车的时候,没有底盘,就不能构建车坐,再比如我们构造一个http访问的爬虫
build模式帮助我们构建了一个复杂的系统,这里的构建指的不仅是对象的初始化,他会自动的帮助我们安排好时序,因为构建过程中一般没有返回值,所以这里非常建议用链式编程
SqlSessionFactoryBuilder
构造器,根据配置或者代码构建SqlSessionFactory
,Build设计模式SqlSessionFactory
工厂接口,产生SqlSession
,工厂设计模式SqlSession
会话
Sql Mapper
映射器,利用接口和xml(或注解)来代理出一个可以执行SQL的类来源
SqlSessionFactoryBuilder提供一个session.Configuration
的构造器,这里里面可以配置插件
实现类
SqlSessionManager 和 DefaultSqlSessionManager
SqlSessionManager是多线程环境下的实习类,具体依靠的还是DefaultSqlSessionManager
作用
产生Mybatis的核心对象SqlSession
配置
官网有详细的配置
笔记里很少会记类似的记忆性知识,只会记录个人的总结
其他
这里额外发现一个叫做Resources
的类,他可以帮助我们直接从类路径,也就是maven项目的resources目录读取文件到inputStream
实现类
也有两个SqlSessionManager
多线程,DefaultSqlSession
单线程,类似于JDBC的Connection对象,代表着一个连接资源的启用
具体作用:
书上写的比较偏应用,而且很简单,有时间我再补充
SqlSessionFactoryBuilder
创建完SqlSessionFactory就失去他的作用
SqlSessionFactory
相当于数据库链接池,所以必须是单例的,而且生命周期要和Mybatis一样长
SqlSession
相当于连接对象,执行完一个业务的数据库操作,要把这个连接关闭并且归还给SqlSessionFactory
因为SqlSessionFactory
是一个连接池
Mapper
和SqlSession
一样
总结
一个SqlSessionFactory
有多个SqlSession
,然后一个SqlSession
对应多个Mapper
具体来说,一个应用对应一个SqlSessionFactory
,一次业务处理对应一个SqlSession
,然后业务中的一次数据库操作对应一个Mapper
TypeHandler是什么?
负责javaType和jdbcType的类型转换,大部分情况都会自动识别,不需要手动配置
源码分析
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
//设计理念就是顶层一个抽象类实现方法的公共部分,然后底层的这些类,实现具体的细节
//这里的泛型T指的是我们的javaType
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
//ps就是JDBC里面的sql语句,而且它可以防止SQL注入
//i是这个参数放置的位置下标,应该是具体sql传递过来的一个变量,一直处理到最接近数据库的层面才具体使用这个参数
//T就是我们的参数
//JdbcType就是SQL的类型
if (parameter == null) {//如果我们准备到语句为空
if (jdbcType == null) {//而且jdbc类型也没有,我们肯定是无法处理的
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
//这个时候,用户应该是想让我们使用空值,但是具体怎么实现,让用户来实现,这样就把扩展留出去了
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);//正常设置流程
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
@Override//根据列名获取结果
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
@Override//根据列位置获取,实在是不推荐
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e);
}
}
@Override//存储过程专用
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
//下面这些方法就让继承了他们的子类来实现
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
框架在捕获异常的时候,抛出了自己的异常
个人认为抛出的异常应该有如下几点
你会发现,异常的抛出和日志很类似,如果我们的日志和异常设计的非常好,能够帮助程序员快速的定位问题,这才是一个好的异常或日志,所以我们设计的时候,要从快速定位问题解决bug的角度来设计这些
存储过程是什么
把若干条SQL绑定在一起,可以传入一定的参数,到时候这些SQL就一起执行,就想我们写的聚合其他函数的函数一样,就是因为这些SQL总是在一起出现
代码分析
其实自定义XXX最好实现了,只要我们模仿他其中的一个类实现就可以,可以直接看自己最熟悉的那种实现
我发现好多实现类里面其实并没有写什么,大部分都是靠JDBC的原生方法实现了,只是自己处理了一些小细节
此外,我发现了Mybatis Plus也继承了这个BaseTypeHandler
,但是它又抽象了一层,这块的设计原理和原来的一模一样,之所以又抽象了一层,是因为他设计了三个处理json和对象转换的类型处理器,然后因为json处理的fastjson还有jackson代码比较类似,这给我们一点提示,就是看到抽象类,我们就要抽象的理解这些代码的公共部分,理清它的上层实现,从某种角度来看,也是一种代理模式
然后,我又产生了好奇,String由那么多jdbcType转换来,怎么处理呢,看一下源码,发现其实都是统一的,java并不在意jdbc类型是什么,反正对它来说,都是字符串
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
具体实现
其实我们定义的大部分转换是枚举类型的转换,我们可以参考一下这个文章的Spring boot 通用枚举转换器,注意可以配置全局的枚举处理器,也可以单独配置,如果order或枚举名称正好能符合你需求的话,可以使用mybatis自带的
此外,我发现很多在spring中的配置,现在变成spring Boot的项目之后,都放在了yaml的配置中,你可以在这个里面尝试一下,感受一下这种配置方式的优势所在
模糊配置会产生好多问题,这个时候结合注解来弥补他们之间的个性化配置是再好不过的解决方案
就像我们类型转换器的匹配javaType和jdbcType是不在
我们实现的时候,具体的函数填充什么?看了好多种实现,总结起来就是:
自己在这个地方做相应的类型转换,然后调用jdbc提供的方法,就可以了
分析一下MP实现枚举类的逻辑
分析一个源码,首先要看的,就这么用他,就是先了解使用,再看具体实现,有的时候,你在学习源码的过程中,会发现更好的使用方法,这些都被设计者精心的设计过
MP设计的枚举
有两种实现方式,一种是注释,一种是现实一个接口,其实从本质上来看,就是找一个和数据库对应的映射关系,可以说是对代码无入侵
不过这个实现类的复杂度超出我的想象了,边注释边分析一遍,里面有好多类都用的是Mybatis自带的类,可见作者对这个框架的研究之深
public class MybatisEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
//用一个线程安全的哈希表
//键值对,键是类名,值是这个枚举类中与字段直接相对的名称
private static final Map<String, String> TABLE_METHOD_OF_ENUM_TYPES = new ConcurrentHashMap<>();
//反射构造工厂
private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final Class<E> type;//枚举类
private final Invoker invoker;//反射执行器
public MybatisEnumTypeHandler(Class<E> type) {//mybatis在构建这个子类的时候,会调用这个方法完成初始化
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;//直接把它保存在属性中,mp有很多设计都是这样的
MetaClass metaClass = MetaClass.forClass(type, REFLECTOR_FACTORY);
String name = "value";//如果你继承了IEnum那你的name就是value
if (!IEnum.class.isAssignableFrom(type)) {
//如果你的枚举没有继承我IEnum那么你必须得有一个@EnumValue注解
name = findEnumValueFieldName(this.type).orElseThrow(() -> new IllegalArgumentException(String.format("Could not find @EnumValue in Class: %s.", this.type.getName())));
}
//这个是mybatis提供的方法,传入字段获取到了这个字段的get方法
this.invoker = metaClass.getGetInvoker(name);
}
/**
* 查找标记标记EnumValue字段
* @param clazz class
* @return EnumValue字段
*/
public static Optional<String> findEnumValueFieldName(Class<?> clazz) {
if (clazz != null && clazz.isEnum()) {
String className = clazz.getName();
//如果哈希表存了标记EnumValeu的字段是谁,我们就自己返回,反之,我们需要遍历字段去寻找
return Optional.ofNullable(CollectionUtils.computeIfAbsent(TABLE_METHOD_OF_ENUM_TYPES, className, key -> {
Optional<Field> optional = Arrays.stream(clazz.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(EnumValue.class))
.findFirst();
return optional.map(Field::getName).orElse(null);
}));
}
return Optional.empty();
}
/**
* 判断是否为MP枚举处理
* @param clazz class
*/
public static boolean isMpEnums(Class<?> clazz) {
return clazz != null && clazz.isEnum() && (IEnum.class.isAssignableFrom(clazz) || findEnumValueFieldName(clazz).isPresent());
}
@SuppressWarnings("Duplicates")
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
throws SQLException {
if (jdbcType == null) {
ps.setObject(i, this.getValue(parameter));
} else {
// see r3589
ps.setObject(i, this.getValue(parameter), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
if (null == rs.getObject(columnName) && rs.wasNull()) {
return null;
}
return this.valueOf(this.type, rs.getObject(columnName));
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
if (null == rs.getObject(columnIndex) && rs.wasNull()) {
return null;
}
return this.valueOf(this.type, rs.getObject(columnIndex));
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
if (null == cs.getObject(columnIndex) && cs.wasNull()) {
return null;
}
return this.valueOf(this.type, cs.getObject(columnIndex));
}
//上面四个方法基本调用的都是下面几个,从我前面总结的经验来看,就是做类型转换
private E valueOf(Class<E> enumClass, Object value) {
//获取枚举类的所有示例,以前只能考valueOf通过字符串获取,但是我们重写了方法,让任意一个都可以获取
E[] es = enumClass.getEnumConstants();
//依据值选择一个枚举返回
return Arrays.stream(es).filter((e) -> equalsValue(value, getValue(e))).findAny().orElse(null);
}
/**
* 值比较
* @param sourceValue 数据库字段值
* @param targetValue 当前枚举属性值
*/
protected boolean equalsValue(Object sourceValue, Object targetValue) {
String sValue = StringUtils.toStringTrim(sourceValue);//这个工具类很好,以前竟然没有发现
String tValue = StringUtils.toStringTrim(targetValue);
if (sourceValue instanceof Number && targetValue instanceof Number
&& new BigDecimal(sValue).compareTo(new BigDecimal(tValue)) == 0) {
//这个就是判断,如果是数字,而且数字相等,就返回true
return true;
}
return Objects.equals(sValue, tValue);//其他类型就调用对象本身的方法
}
//虽然我们平时的枚举映射,要么是数字,要么是字符串,但是框架就应该像这样考虑的全面,可以解决各种情况
//返回值是与数据库对应的值
private Object getValue(Object object) {
try {
return invoker.invoke(object, new Object[0]);
} catch (ReflectiveOperationException e) {
throw ExceptionUtils.mpe(e);
}
}
}
isAssignableFrom 和 instanceof 的区别
首先,instanceof
只能用于示例,是不能用于类的,而isAssignableFrom
可以,也就是说,反射的时候用isAssignableFrom
,其次,他们的逻辑是相反的,isAssignableFrom
用于判断后面的是否是调用者的子类,而instanceof
判断前面的是不是为后面的子类,个人不是很推荐使用instanceof
,因为一般这种情况下,用多态更好
要是一个接口有好几个,一般都是Defaultxx是主要的实现类,所以在这里发现了一个学习的好方法,我们可以把这些继承下来,然后在他们的基础上,我们只多添加一些日志,然后我们debug我们自己的类可以看日志的输出,好处就是我们对它有了一定的操控性,随便也可以学一下,怎么自定义这些内容
我们实习自定义的一般步骤也就是,模仿或继承原有的类,然后在配置中注册进去,这个肯定是要自己注册的,如果是扫描式,对框架的核心功能影响还是比较大的,抛开别的不提,启动速度就会变慢很多
UNPOOLED
怎么说,每次请求都会开一个新的链接,除非数据库对这个没有要求,不然真的不好
POOLED
利用池的概念把Connection对象管理起来,和线程池类似,如果我们某些对象经常处于创建和销毁的两个状态,池是一个很好的性能优化手段,它在初始化的时候,已经连接好数据库,所以我们每次请求,都不需要建立和验证,省去了很多时间,而且可以控制最大链接数,避免过多的系统瓶颈
很多情况下,我们使用的都是Druid链接池,这样的性能更好,关键是监控的很全面,一般配置项有
active 任意时间都会存在的连接数量
idle 任意时间空闲的数量,为大流量做准备
强制断开时间,就是不能让某些查询执行的时间太长
这里只分析一些思想,一般情况下,那些可能等待超时的事情,我们最好都配置一个重试周期,此外,有的情况下,我们可以直接等到一定时间,定时失败,为了保证稳定性,我们可以定时向数据库发送一些信息,做心跳检测
下面只记录在阅读官网文档中没有注意到或平时不怎么使用但有用的信息
<insert id="xxx" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
官方文档仅仅指定了一个,就是keyProperty,考虑到我们很少有这种id不对应的情况,所以我们可以直接采用官方的做法,设计一个,另一个根据名称自动映射,而且官方还细心的把这个取了出来,因为有的公司主键是用的xx_id,所以我们在设计的时候,扩充库一定要非常全面
insert>
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/>include>,
<include refid="userColumns"><property name="alias" value="t2"/>include>
from some_table t1
cross join some_table t2
select>
列名加别名这种形式确实很常用,非常建议把这些写在一起,传入灵活的参数,比如别名前缀,数据库别名,有这两个参数基本会符合绝大部分情况,还是那句话,遇到特殊的就手写,但聚合使用绝没有坏处,除非你在开始就觉得他对每个来说都很独立,注意一下,多个include拼接在一起,是要用逗号分割的
我们可以在这个里面指定javaType,jdbcType,typeHandler,numericScale(保留多少位小数),如果我们需要对参数进行特殊的处理可以查看官方文档,如果我们的处理比较复杂,我们可以自定义类型转换器,在代码层面解决复杂性,也就是数,类型转换器负责了javaType和jdbcType的双重转化
N+1问题
什么是N+1问题?
级联查询会查询出外面不需要的数据,在一定条件下,怎么办?我们多写几条SQL语句,一个一个的应对这样的情况吗?
答案是:延迟加载,只有我们通过pojo获取相关属性的时候,才执行SQL取出数据
fetchType,在单个association,collection里面设置
再读一遍官方文档!!!
不得不佩服官方文档,无论看多少遍,都能有新的收获
一般而言,mybatis是能够判断出你使用的是集合,所以我们在指定类型的时候,只需要指定集合里面的类型即可,对于返回结果来说,你只需要指定是POJO
还是List
这样,mybatis会帮助我们自动处理,但是如果是POJO
但我们返回的对象有多个,mybatis就会报错,常见的解决方案,就是加limit,或者子查询加limit,或者干脆分成多个SQL去实现
result
中的id真的非常重要,他是区别多条数据是否属于同一个对象,可以说必须要配置
resultOrdered
当我们处理的resultMap有嵌套结果,明明sql已经查询出来了,但是却没有放到嵌套结果集合里面,那我们就把这个参数设置为true
resultSet
从默认值来看,查询语句都会做缓存,但是insert,update,delete会清除查询语句的缓存
foreach
批量操作,比如批量插入,这个效率要比一个一个执行要快,因为要不断的提交事务,然后就是不要一次批量插入太多,500条最好,我们可以使用相关的工具类把我们的集合分成一块一块的。foreach的具体语法
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
foreach>
collection代表集合的名称,item就是集合的每一项,我们可以用separator把这些内容分开,相当于separator.join(xml里面的内容),其实一般也就是逗号,open是开头拼接的字符串,close是末尾拼接的字符串
association
用的是javaType,而collection
用的是ofType
discriminator
只是依据某个列的参数决定用哪个映射规则:resultMap或者resultType
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
discriminator>
对于大部分复杂的嵌套结果集来说,它就像写一个SQL一样,我们要一步一步的把他写出来,慢慢的测试它的效果,最好,你设计几条数据,里面要包含你这个sql和映射集所有可能处理的情况
回过头来,再看一次N+1问题
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
//注意看,column就是要执行的select里面的参数,所以我们可以在这里配置fetchType来取消这些性能消耗,这很像是你在执行遍历循环一个select的SQL
resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
select>
官方推荐的是join的多表查询方案
其实收获不是很多,唯一让我提升的就是,我以后会尝试写更通用的xml,而不是一个接口写一个了
一级缓存是在SqlSession上的缓存,二级缓存是在SqlSessionFactory上的缓存,默认情况下是一级缓存,而且不需要我们的对象实现可序列化接口
一级缓存只能存在与一个业务中,我们要是想让多次业务之间共享,我们可以让它实现可序列化接口,然后我们可以选择mybatis的二级缓存,也可以使用我们自己的缓存,比如redis,或者jvm缓存,一般情况下,如果存的数据是对象而且你的是单体,存到jvm里会方便一些,但是很少见,我们一般都弄到redis里面,缓存的读写问题本身就很麻烦,你分布式应用,如果每个jvm的缓存不同步,是一件很麻烦的事情,所以我觉得,应该把缓存放在redis里面,redis就像是我们的共享内存
关于缓存的一般配置(这些从缓存的公共角度理解的,可以用在redis,而且我以redis为主)
并发读写,并发读是完全没有问题的,但是我们不能出现并发写,或者,并发的读写,总之里面不能有写出现
缓存过期时间,过一定时间,会自动删除缓存,这样可以避免我们的内存产生有过大的浪费,ttl
缓存太多了,我们删除哪些缓存?
MongoDB也是可以作为缓存的,而且缓存对象是最好的,他的速度非常的快
if
标签里面的test里面执行的是SQL的语法,不要用&&等,更不要出现java的语法
此外还是力挺一下我最喜爱的框架,MP,他动态SQL的原理就是在代码层面判断是否要写入SQL,而且在代码层面的判断,更加强大,希望我有一天也能写出那样强大的构造器
重头戏来了
Configuration
对象,然后用这个来创建SqlSessionFactory
SqlSession
执行过程,涉及到反射和动态代理,我也发现了mybatis封装了自己的反射工具XMLConfigBuilder
解析xml文件,读取相关参数,并把这些参数放在Configuration
中,Configuration
采用的就是单例模式,保存了几乎所有的配置对象Configuration
构建一个默认的SqlSessionFactory
接口实现类DefaultSqlSessionFactory
我是怎么学习源码的
随便在一个地方输入类的名称,然后用idea点进去,如果类里面有属性,我想知道属性在哪些函数被用到,我会把他选中,这样idea有提示,但是不显眼,这种情况下,我会按下搜索,把这个属性搜索出来,来看看他是怎么使用的
而且我们要学会分析源码,我们分析源码的目的是为了学习它,为了能够更好的使用,所以我们要学会分析关键部分的源码,至少,我们知道这步是干什么的,如果我们想知道具体的细节,我们才会点进去看方法的实现,如果是一个接口,我们不确定是哪个该怎么办,一句话,debug就完事了
而且你不在关键位置标注释,我发现读起来是相当难受的,如果标注的话,我相信学习起来的难度会下降一大半
读取XML的配置文件
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
看一下我们熟悉的typeHandler是怎么读取的
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {//遍历所有子节点
if ("package".equals(child.getName())) {
//包扫描
//这在子节点中被扫描,就说明,我可以配置多个package而且可以和单独配置的相兼容
//这种需要做大量实验的知识,我们可以直接通过代码读取出来
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//我们甚至可以看到有哪些属性实际起到了作用
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {//如果我们没有配置jdbcType
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {//如果我们没有配置javaType,那么就直接注册typeHandler
typeHandlerRegistry.register(typeHandlerClass);
}
//如果你继续往下看这些方法,会发现,里面重载了特别多的register的方法,有的甚至还对typeHandler的注释做了判断,比如你没有弄jdbcType,但是你把这个弄在了注解上面,我们的框架是要能够处理了这种情况的
//如果有哪天,你好奇,那么种配置,可以自由的组合,我们怎么去实现呢,其实我们根本不需要处理那么复杂的逻辑判断,比如我们配置了javaType和jdbcType,那么我们就不需要看注解了,这样的情况,逻辑判断是可以解决的,但放在这里不太合适,因为这会让这个函数变得想到复杂,利用函数的调用来化简if语句的层数是我在这里学习到的
}
}
}
}
真的是可以看懂逻辑的,都不用我怎么去注释它
利用重载函数的调用来化简if语句的层数
说Configuration
是Mybatis的核心数据中心,我觉得一点问题都没有,就想是spring的好多数据和对象都要从Ioc容器里面拿一样
插件需要频发的访问映射器的内部组成
XmlConfigBuilder
解析XML
的时候,会将每一个SQL和其配置的内容存起来
MappedStatement
保存了我们的SQL,resultMap,SQL的id等几乎我们在xml里面配置的所有内容,此外还有一个非常重要的sqlSource,用来读取所有相关信息
sqlSource
提供BoundSql
对象的地方,他是一个接口,可以根据上下文参数生成需要的SQL
BoundSql
结果对象,是建立SQL和参数的地方
public final class MappedStatement {
private String resource;
private Configuration configuration;//核心配置类
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
可以看到很多配置属性都被转换为了对象,几乎包括了全部,数组类型的明显的告诉我们,这样的可以配置多个
Builder 模式的具体代码
省略了很多结构重复的代码
public static class Builder {
private MappedStatement mappedStatement = new MappedStatement();
public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
mappedStatement.configuration = configuration;
mappedStatement.id = id;
mappedStatement.sqlSource = sqlSource;
mappedStatement.statementType = StatementType.PREPARED;
mappedStatement.resultSetType = ResultSetType.DEFAULT;
mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
mappedStatement.resultMaps = new ArrayList<>();
mappedStatement.sqlCommandType = sqlCommandType;
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
String logId = id;
if (configuration.getLogPrefix() != null) {
logId = configuration.getLogPrefix() + id;
}
mappedStatement.statementLog = LogFactory.getLog(logId);
mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
}
public Builder resource(String resource) {
mappedStatement.resource = resource;
return this;
}
public String id() {
return mappedStatement.id;
}
public Builder parameterMap(ParameterMap parameterMap) {
mappedStatement.parameterMap = parameterMap;
return this;
}
public Builder resultMaps(List<ResultMap> resultMaps) {
mappedStatement.resultMaps = resultMaps;
for (ResultMap resultMap : resultMaps) {
mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
}
return this;
}
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
}
}
自己写一个Build试一试
public class Dict extends HashMap<String, Object> {
//如果我们在继承的时候指定了泛型的具体类型,我们下面的代码是不需要钻石运算符额外指定的
public static class Builder{//这个类必须是静态的,不然无法在类外部类的方法直接使用
//这就是我们要构建的字典,我把这个放在构建类中
private Dict dict;
public Builder(){
dict=new Dict();
}
public Builder set(String key,Object value){
dict.put(key, value);
return this;//注意要这样构建,这样的写法才能弄出链式编程的效果
}
public Dict build(){//构建方法返回字典
return dict;
}
}
public static Builder builder(){//开始构建
return new Builder();
}
}
补充了一些其他方法,下面是完整的类
public class Dict extends HashMap<String, Object> {
public static class Builder{
private Dict dict;
public Builder(){
dict=new Dict();
}
public Builder set(String key,Object value){
dict.put(key, value);
return this;
}
public Dict build(){
return dict;
}
}
public static Builder builder(){
return new Builder();
}
public String getString(String key){
Object o = this.get(key);
return String.valueOf(o);
}
public String getString(String key,String defaultValue){
Object o = this.get(key);
return o==null?defaultValue:String.valueOf(o);
}
public Integer getInteger(String key){
Object o = this.get(key);
return Integer.valueOf(o.toString());
}
public Integer getInteger(String key,Integer defaultValue){
Object o = this.get(key);
return o==null?defaultValue:Integer.valueOf(o.toString());
}
@SuppressWarnings("all")
public <T> T getObject(String key,Class<T> tClass){
Object o = this.get(key);
return (T) o;
}
@SuppressWarnings("all")
public <T> T getObject(String key,T defaultValue,Class<T> tClass){
Object o = this.get(key);
return o==null?defaultValue:(T) o;
}
}
使用方法
Dict dict=Dict.builder()
.set("name","常珂洁")
.set("age",21)
.set("birthDay", LocalDate.of(2001,5,10))
.build();
System.out.println(dict);
System.out.println(dict.getInteger("age"));
System.out.println(dict.getInteger("value",100));
System.out.println(dict.getString("name"));
LocalDate birthDay = dict.getObject("birthDay", LocalDate.class);
System.out.println(birthDay);
另外我发现这个初始化嵌套的也很不错
Dict dict = Dict.builder()
.set("name", "常珂洁")
.set("age", 21)
.set("birthDay", LocalDate.of(2001, 5, 10))
.set("inner", Dict.builder()
.set("inner_name", "岚殿")
.set("inner_age", 7)
.build())
.build();
System.out.println(dict);
我再想,能不能再简单一点,把Dict和builder省去?
或许java天生就支持?匿名内部类的静态方法
Map<String, Object> dict = new HashMap<String, Object>() {
{
put("name", "常珂洁");
put("age",12);
put("inner",new HashMap<String, Object>(){{
put("inner_name","岚殿");
}});
}
};
不过加括号确实很乱,这就是语法层面的一种缺陷
回归正题
SqlSource
处理很复杂的动态SQL,然后会得到一个BoundSql
,这个是处理好的SQL,我们在这个基础上操作
插件干涉SQL的核心作用类
parameterObject参数对象
@param
注解,那么Mybatis会把这些参数处理为一个map,具体来说,{“1”:p1,“2”:p2,“参数1的名字”:p1,“参数2的名字”:p2},所以,我们可以用#{参数1的名字}或者#{1}这种标号来,不过我们用的肯定是一个名字,因为标号确实不好,有名字尽量用名字,到目前为止,我从来没有见过用标号好的,因为可读性不强,而且最要命的是,当你修改了之后,会发生很多意想不到的变化parameterMappings
它是一个List
,对象描绘参数,有它的名称,表达式和javaType等,就是他把参数和Sql结合起来,以便PreparedStatement
能够通过它找到parameterObject
对象的属性设置参数
sql
书写在映射器,被SqlSource
解析后的SQL
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
有时候我们跟踪一个方法,会套很多层,没事,大部分就是做了简单的逻辑处理,甚至都没有处理,其实和我们写的mvc架构有点类似,总之,我们跟踪到最后,会有一个具体的实习类,此外,我在这个过程中要留意下参数的变化,额外处理了什么
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);//从一个工程里面获得
if (mapperProxyFactory == null) {//如果没有报错,这个mapper没有注册
//所以我猜测,mapper的注册应该也是一个比较关键的地方
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
我们可以看到上一层就做了一些判断,然后就丢给下一层了
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;//接口的反射
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();//把方式和方法的反射执行器绑定在了一个map中
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
//上面三个都是处理属性的普通方法
//下面两个方法合起来返回一个动态代理类
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//我们可以很明显的看到,是mapperProxy,proxy就是代理的意思
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
另外,如果我们分析到的一个类是xxx工程,那么我们分析到最后,肯定是想知道,这个工厂造出来的是什么?我们顺着这种思路来看看,它返回的产品是什么,我们直接点进那个MapperProxy接口,或者我们可以在某个地方输入MapperProxy
然后点进去
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
//用来执行SQL的sqlSession,接口的反射mapperInterface,方法的执行器methodCache
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {//静态代码块,类加载的时候,执行一次
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
//重点看这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
//如果是一个普通的类,不是接口那就直接执行
return method.invoke(this, args);
} else {
//这是我们要面对的一般情况,它是一个接口,这个接口在类里面
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
MapperMethodInvoker invoker = methodCache.get(method);
//如果我们保存了一个方法执行器的缓存,那么我们就返回它
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
//方法执行器接口
interface MapperMethodInvoker {
//有个方法,传入代理,对象,方法,和方法参数,来执行放
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
//如果我们的静态内部类想让别人使用,我们可以使用private修饰它
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
//书上标记的是这个方法,我们debug看看。。。有点离谱,没过来
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
}
computeIfAbsent
//从map中根据key获取value操作可能会有下面的操作
Object key = map.get("key");
if (key == null) {
key = new Object();
map.put("key", key);
}
//如果没有就添加
//上面的操作可以简化为一行,若key对应的value为空,会将第二个参数的返回值存入并返回!
Object key2 = map.computeIfAbsent("key", k -> new Object());
//compute就是计算的意思,absent就是缺席,不存在的意思
MapperMethod
这个类之所以大是因为里面包含很多与他息息相关的类
public class MapperMethod {
private final SqlCommand command;//SQL命令
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;//准备返回的结果,多样性结果,作者直接用Object保存了
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
//上面这个就是生成映射的字典的方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT://这个方法是最复杂的
//对于不同的返回类型,采取了不同的处理方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//把判断条件改写成方法名称,可读性真的会好很多
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//这些超级离谱的方法,都紧跟在这个函数后面,你可以看到他们的实现
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()//这个竟然能处理Optional包裹的单值返回
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
private Object rowCountResult(int rowCount) {
final Object result;
//可以看到增删改的方法可以映射成Integer,Long,Boolean或者void,甚至非包装类也可以
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}//你可能看不懂上面的判断,但是你绝对可以看懂下面的注释
//所以我们在编写框架的时候,那些对用户公开的部分,我们就要向处理前端参数一样验证
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//RowBounds是mybatis自带的分页参数,不需要在里面写limit就可使用
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
//数组和集合都是支持
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectCursor(command.getName(), param);
}
return result;
}
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
//现在我就明白了,原来对象工厂是根据对象的反射,然后创建这个对象的工厂
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
Class<?> arrayComponentType = method.getReturnType().getComponentType();
Object array = Array.newInstance(arrayComponentType, list.size());
if (arrayComponentType.isPrimitive()) {//判断是否为原始非包装类型
//有一说一,这个方法真的能猜出来
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
//但是你可以从这个地方学习,人家是怎么给原始类型赋值的
return array;
} else {
return list.toArray((E[]) array);
}
}
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
public static class ParamMap<V> extends HashMap<String, V> {
//这个泛型设计的挺好
private static final long serialVersionUID = -2212268410512043556L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
//这个available设计的真的好,如果你输入了错误的类型,说实话,作为一个程序员,我第一个想知道的就是,我到底有哪些选择?
}
return super.get(key);
}
}
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
public boolean hasRowBounds() {
return rowBoundsIndex != null;
}
public RowBounds extractRowBounds(Object[] args) {
return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
}
public boolean hasResultHandler() {
return resultHandlerIndex != null;
}
public ResultHandler extractResultHandler(Object[] args) {
return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
}
public Class<?> getReturnType() {
return returnType;
}
public boolean returnsMany() {
return returnsMany;
}
public boolean returnsMap() {
return returnsMap;
}
public boolean returnsVoid() {
return returnsVoid;
}
public boolean returnsCursor() {
return returnsCursor;
}
/**
* return whether return type is {@code java.util.Optional}.
*
* @return return {@code true}, if return type is {@code java.util.Optional}
* @since 3.5.0
*/
public boolean returnsOptional() {
return returnsOptional;
}
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
public String getMapKey() {
return mapKey;
}
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}
}
Integer.class和Integer.TYPE的区别
System.out.println(int.class==Integer.TYPE);//结果为true
System.out.println(int.class==Integer.class);//结果为false
为什么Mybatis只用mapper接口便可以运行
因为mapper的xml文件的命名空间对应的是这个接口的全限定名,而方法就是那条SQL的id,这样我们根据全限定名和方法名,就可以把它和代理对象绑定起来,然后采用命令模式,让这SQL运行起来
真正干活的就是执行器,负责java与数据库的交互,有三种执行器
执行器的创建
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//工厂模式
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
默认执行器 SimpleExecutor
public class SimpleExecutor extends BaseExecutor {
//依据configuration和事物来创建执行器
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
//MappedStatement 就是保存了几乎我们xml所有配置信息的类
Configuration configuration = ms.getConfiguration();//连configuration它都能获取到
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
return Collections.emptyList();
}
//SQL编译和参数的初始化
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//这里创建了连接,而且它把日志传了进去
Connection connection = getConnection(statementLog);
//SQL编译
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);//参数初始化
return stmt;
}
}
创建过程
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
与jdbc的对应关系
RoutingStatementHandler 是什么
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;//适配器模式
//但是这个适配器为我们做了统一的处理
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
//在构建的时候,根据上下文环境,选择用哪个处理器
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}
@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}
@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}
适配器和他都实现的同样的接口,在内部设计了一个属性了存放我们的目标对象,然后根据上下文环境决定用哪一个类,然后让适配器来执行这些方法,把他们统一起来
PreparedStatementHandler 最常见的
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;//JDBC原生类
ps.execute();
int rows = ps.getUpdateCount();//获取更新的数量
//下面这个两个方法是用来设置生成的主键
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);//通过resultSetHander处理
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleCursorResultSets(ps);
}
//SQL预编译
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();//得到SQL
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {//对主键的处理
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
//用parameterHandler初始化参数
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
}
sql预编译->参数初始化->设置参数->执行SQL->对返回结构进行处理
Execuor调用StatementHandler的perpare()方法预编译SQL,同时设置一些基本的运行参数,然后用parameterize()方法启用ParameterHandler 设置参数,完成预编译,然后执行查询,然后用ResultSetHandler来封装返回结果给调用者
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//这里面就是一个个参数的具体配置
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {//不要是输出参数,输出参数不参数SQL的预处理
Object value;
String propertyName = parameterMapping.getProperty();//得到属性名
if (boundSql.hasAdditionalParameter(propertyName)) {//有格外的参数吗
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {//传递的参数为null吗
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//这个参数有类型转换器吗?
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
//总的来说,上面就是获得这个value的值
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//经过上面的处理,我们拿到了参数的位置i,value的值,和jdbc类型
typeHandler.setParameter(ps, i + 1, value, jdbcType);
//这个函数值的设置,我们以前学习自定义的类型转换器的时候讲过,这个时候,你应该对整个流程都非常的清楚了!
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
可以说是mybatis里面最复杂的东西了,整整1000多行代码,贴出来分析一下
/**
* @author Clinton Begin
* @author Eduardo Macarron
* @author Iwao AVE!
* @author Kazuki Shimizu
*/
//上面几位确实牛逼,这个地方我不删作者,致敬!
public class DefaultResultSetHandler implements ResultSetHandler {
private static final Object DEFERRED = new Object();
private final Executor executor;//执行器
private final Configuration configuration;//全局上下文配置
private final MappedStatement mappedStatement;//mapper.xml配置
private final RowBounds rowBounds;//分页插件
private final ParameterHandler parameterHandler;//参数处理器
private final ResultHandler<?> resultHandler;//结果处理器
private final BoundSql boundSql;//SQL的核心
private final TypeHandlerRegistry typeHandlerRegistry;//类型转换器
private final ObjectFactory objectFactory;//对象工厂
private final ReflectorFactory reflectorFactory;//反射工程
//下面用了相当多的哈希表来完成我们复杂的参数映射过程
// nested resultmaps
private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
private final Map<String, Object> ancestorObjects = new HashMap<>();
private Object previousRowValue;
// multiple resultsets
private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
// Cached Automappings
private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
// temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
private boolean useConstructorMappings;
private static class PendingRelation {
public MetaObject metaObject;
public ResultMapping propertyMapping;
}
private static class UnMappedColumnAutoMapping {//未配置映射的列,自动映射
private final String column;//这些就是我们在xml里面指定的!
private final String property;
private final TypeHandler<?> typeHandler;
private final boolean primitive;//基本非包装类型
public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
this.column = column;
this.property = property;
this.typeHandler = typeHandler;
this.primitive = primitive;
}
}
public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql,
RowBounds rowBounds) {
this.executor = executor;
this.configuration = mappedStatement.getConfiguration();
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.parameterHandler = parameterHandler;
this.boundSql = boundSql;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
this.reflectorFactory = configuration.getReflectorFactory();
this.resultHandler = resultHandler;
}
//
// HANDLE OUTPUT PARAMETER
//
@Override
public void handleOutputParameters(CallableStatement cs) throws SQLException {
final Object parameterObject = parameterHandler.getParameterObject();
final MetaObject metaParam = configuration.newMetaObject(parameterObject);
final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0; i < parameterMappings.size(); i++) {
final ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
if (ResultSet.class.equals(parameterMapping.getJavaType())) {
handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
} else {
final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1));
}
}
}
}
private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam) throws SQLException {
if (rs == null) {
return;
}
try {
final String resultMapId = parameterMapping.getResultMapId();
final ResultMap resultMap = configuration.getResultMap(resultMapId);
final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
if (this.resultHandler == null) {
final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rs);
}
}
//
// HANDLE RESULT SETS
//
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
@Override
public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
if (resultMapCount != 1) {
throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
}
ResultMap resultMap = resultMaps.get(0);
return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
}
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
ResultSet rs = stmt.getResultSet();
while (rs == null) {
// move forward to get the first resultset in case the driver
// doesn't return the resultset as the first result (HSQLDB 2.1)
if (stmt.getMoreResults()) {
rs = stmt.getResultSet();
} else {
if (stmt.getUpdateCount() == -1) {
// no more results. Must be no resultset
break;
}
}
}
return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
private ResultSetWrapper getNextResultSet(Statement stmt) {
// Making this method tolerant of bad JDBC drivers
try {
if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
// Crazy Standard JDBC way of determining if there are more results
if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
ResultSet rs = stmt.getResultSet();
if (rs == null) {
return getNextResultSet(stmt);
} else {
return new ResultSetWrapper(rs, configuration);
}
}
}
} catch (Exception e) {
// Intentionally ignored.
}
return null;
}
private void closeResultSet(ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
// ignore
}
}
private void cleanUpAfterHandlingResultSet() {
nestedResultObjects.clear();
}
private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
if (rsw != null && resultMapCount < 1) {
throw new ExecutorException("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
+ "'. It's likely that neither a Result Type nor a Result Map was specified.");
}
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
@SuppressWarnings("unchecked")
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
//
// HANDLE ROWS FOR SIMPLE RESULTMAP
//
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void ensureNoRowBounds() {
if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
+ "Use safeRowBoundsEnabled=false setting to bypass this check.");
}
}
protected void checkResultHandler() {
if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
+ "Use safeResultHandlerEnabled=false setting to bypass this check "
+ "or ensure your statement returns ordered data and set resultOrdered=true on it.");
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
if (parentMapping != null) {
linkToParents(rs, parentMapping, rowValue);
} else {
callResultHandler(resultHandler, resultContext, rowValue);
}
}
@SuppressWarnings("unchecked" /* because ResultHandler> is always ResultHandler)
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
for (int i = 0; i < rowBounds.getOffset(); i++) {
if (!rs.next()) {
break;
}
}
}
}
//
// GET VALUE FROM ROW FOR SIMPLE RESULT MAP
//
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
//
// GET VALUE FROM ROW FOR NESTED RESULT MAP
//
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
final String resultMapId = resultMap.getId();
Object rowValue = partialObject;
if (rowValue != null) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
putAncestor(rowValue, resultMapId);
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
ancestorObjects.remove(resultMapId);
} else {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
putAncestor(rowValue, resultMapId);
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
private void putAncestor(Object resultObject, String resultMapId) {
ancestorObjects.put(resultMapId, resultObject);
}
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
if (resultMap.getAutoMapping() != null) {
return resultMap.getAutoMapping();
} else {
if (isNested) {
return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
} else {
return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
}
}
}
//
// PROPERTY MAPPINGS
//
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
final String mapKey = resultMap.getId() + ":" + columnPrefix;
List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
if (autoMapping == null) {
autoMapping = new ArrayList<>();
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if (columnPrefix != null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified,
// ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
if (property != null && metaObject.hasSetter(property)) {
if (resultMap.getMappedProperties().contains(property)) {
continue;
}
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, property, propertyType);
}
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
}
}
autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
// MULTIPLE RESULT SETS
private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn());
List<PendingRelation> parents = pendingRelations.get(parentKey);
if (parents != null) {
for (PendingRelation parent : parents) {
if (parent != null && rowValue != null) {
linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
}
}
}
}
private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) throws SQLException {
CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getColumn());
PendingRelation deferLoad = new PendingRelation();
deferLoad.metaObject = metaResultObject;
deferLoad.propertyMapping = parentMapping;
List<PendingRelation> relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>());
// issue #255
relations.add(deferLoad);
ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
if (previous == null) {
nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
} else {
if (!previous.equals(parentMapping)) {
throw new ExecutorException("Two different properties are mapped to the same resultSet");
}
}
}
private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns) throws SQLException {
CacheKey cacheKey = new CacheKey();
cacheKey.update(resultMapping);
if (columns != null && names != null) {
String[] columnsArray = columns.split(",");
String[] namesArray = names.split(",");
for (int i = 0; i < columnsArray.length; i++) {
Object value = rs.getString(columnsArray[i]);
if (value != null) {
cacheKey.update(namesArray[i]);
cacheKey.update(value);
}
}
}
return cacheKey;
}
//
// INSTANTIATION & CONSTRUCTOR MAPPING
//
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<>();
final List<Object> constructorArgs = new ArrayList<>();
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;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings,
List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix) {
boolean foundValues = false;
for (ResultMapping constructorMapping : constructorMappings) {
final Class<?> parameterType = constructorMapping.getJavaType();
final String column = constructorMapping.getColumn();
final Object value;
try {
if (constructorMapping.getNestedQueryId() != null) {
value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
} else if (constructorMapping.getNestedResultMapId() != null) {
final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
} else {
final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
}
} catch (ResultMapException | SQLException e) {
throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
}
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
if (defaultConstructor != null) {
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
} else {
for (Constructor<?> constructor : constructors) {
if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);
}
}
}
throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
}
private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
boolean foundValues = false;
for (int i = 0; i < constructor.getParameterTypes().length; i++) {
Class<?> parameterType = constructor.getParameterTypes()[i];
String columnName = rsw.getColumnNames().get(i);
TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
constructorArgTypes.add(parameterType);
constructorArgs.add(value);
foundValues = value != null || foundValues;
}
return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
private Constructor<?> findDefaultConstructor(final Constructor<?>[] constructors) {
if (constructors.length == 1) {
return constructors[0];
}
for (final Constructor<?> constructor : constructors) {
if (constructor.isAnnotationPresent(AutomapConstructor.class)) {
return constructor;
}
}
return null;
}
private boolean allowedConstructorUsingTypeHandlers(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
final Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length != jdbcTypes.size()) {
return false;
}
for (int i = 0; i < parameterTypes.length; i++) {
if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
return false;
}
}
return true;
}
private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final Class<?> resultType = resultMap.getType();
final String columnName;
if (!resultMap.getResultMappings().isEmpty()) {
final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
final ResultMapping mapping = resultMappingList.get(0);
columnName = prependPrefix(mapping.getColumn(), columnPrefix);
} else {
columnName = rsw.getColumnNames().get(0);
}
final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
return typeHandler.getResult(rsw.getResultSet(), columnName);
}
//
// NESTED QUERY
//
private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException {
final String nestedQueryId = constructorMapping.getNestedQueryId();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = constructorMapping.getJavaType();
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
value = resultLoader.loadResult();
}
return value;
}
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERRED;
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
if (propertyMapping.isLazy()) {
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERRED;
} else {
value = resultLoader.loadResult();
}
}
}
return value;
}
private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
if (resultMapping.isCompositeResult()) {
return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
} else {
return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
}
}
private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
final TypeHandler<?> typeHandler;
if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
} else {
typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
}
return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}
private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
final Object parameterObject = instantiateParameterObject(parameterType);
final MetaObject metaObject = configuration.newMetaObject(parameterObject);
boolean foundValues = false;
for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
// issue #353 & #560 do not execute nested query if key is null
if (propValue != null) {
metaObject.setValue(innerResultMapping.getProperty(), propValue);
foundValues = true;
}
}
return foundValues ? parameterObject : null;
}
private Object instantiateParameterObject(Class<?> parameterType) {
if (parameterType == null) {
return new HashMap<>();
} else if (ParamMap.class.equals(parameterType)) {
return new HashMap<>(); // issue #649
} else {
return objectFactory.create(parameterType);
}
}
//
// DISCRIMINATOR
//
public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException {
Set<String> pastDiscriminators = new HashSet<>();
Discriminator discriminator = resultMap.getDiscriminator();
while (discriminator != null) {
final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
if (configuration.hasResultMap(discriminatedMapId)) {
resultMap = configuration.getResultMap(discriminatedMapId);
Discriminator lastDiscriminator = discriminator;
discriminator = resultMap.getDiscriminator();
if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
break;
}
} else {
break;
}
}
return resultMap;
}
private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
final ResultMapping resultMapping = discriminator.getResultMapping();
final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}
private String prependPrefix(String columnName, String prefix) {
if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
return columnName;
}
return prefix + columnName;
}
//
// HANDLE NESTED RESULT MAPS
//
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
Object rowValue = previousRowValue;
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
Object partialObject = nestedResultObjects.get(rowKey);
// issue #577 && #542
if (mappedStatement.isResultOrdered()) {
if (partialObject == null && rowValue != null) {
nestedResultObjects.clear();
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else {
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
if (partialObject == null) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
}
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
previousRowValue = null;
} else if (rowValue != null) {
previousRowValue = rowValue;
}
}
//
// NESTED RESULT MAP (JOIN MAPPING)
//
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
boolean foundValues = false;
for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
final String nestedResultMapId = resultMapping.getNestedResultMapId();
if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
try {
final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
if (resultMapping.getColumnPrefix() == null) {
// try to fill circular reference only when columnPrefix
// is not specified for the nested result map (issue #215)
Object ancestorObject = ancestorObjects.get(nestedResultMapId);
if (ancestorObject != null) {
if (newObject) {
linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
}
continue;
}
}
final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
Object rowValue = nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null;
instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
if (rowValue != null && !knownValue) {
linkObjects(metaObject, resultMapping, rowValue);
foundValues = true;
}
}
} catch (SQLException e) {
throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
}
}
return foundValues;
}
private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
final StringBuilder columnPrefixBuilder = new StringBuilder();
if (parentPrefix != null) {
columnPrefixBuilder.append(parentPrefix);
}
if (resultMapping.getColumnPrefix() != null) {
columnPrefixBuilder.append(resultMapping.getColumnPrefix());
}
return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
}
private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw) throws SQLException {
Set<String> notNullColumns = resultMapping.getNotNullColumns();
if (notNullColumns != null && !notNullColumns.isEmpty()) {
ResultSet rs = rsw.getResultSet();
for (String column : notNullColumns) {
rs.getObject(prependPrefix(column, columnPrefix));
if (!rs.wasNull()) {
return true;
}
}
return false;
} else if (columnPrefix != null) {
for (String columnName : rsw.getColumnNames()) {
if (columnName.toUpperCase().startsWith(columnPrefix.toUpperCase())) {
return true;
}
}
return false;
}
return true;
}
private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix) throws SQLException {
ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
}
//
// UNIQUE RESULT KEY
//
private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
final CacheKey cacheKey = new CacheKey();
cacheKey.update(resultMap.getId());
List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
if (resultMappings.isEmpty()) {
if (Map.class.isAssignableFrom(resultMap.getType())) {
createRowKeyForMap(rsw, cacheKey);
} else {
createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
}
} else {
createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
}
if (cacheKey.getUpdateCount() < 2) {
return CacheKey.NULL_CACHE_KEY;
}
return cacheKey;
}
private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
CacheKey combinedKey;
try {
combinedKey = rowKey.clone();
} catch (CloneNotSupportedException e) {
throw new ExecutorException("Error cloning cache key. Cause: " + e, e);
}
combinedKey.update(parentRowKey);
return combinedKey;
}
return CacheKey.NULL_CACHE_KEY;
}
private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
if (resultMappings.isEmpty()) {
resultMappings = resultMap.getPropertyResultMappings();
}
return resultMappings;
}
private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.isSimple()) {
final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
final TypeHandler<?> th = resultMapping.getTypeHandler();
List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
// Issue #114
if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
final Object value = th.getResult(rsw.getResultSet(), column);
if (value != null || configuration.isReturnInstanceForEmptyRow()) {
cacheKey.update(column);
cacheKey.update(value);
}
}
}
}
}
private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, String columnPrefix) throws SQLException {
final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String column : unmappedColumnNames) {
String property = column;
if (columnPrefix != null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified, ignore columns without the prefix.
if (column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
property = column.substring(columnPrefix.length());
} else {
continue;
}
}
if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
String value = rsw.getResultSet().getString(column);
if (value != null) {
cacheKey.update(column);
cacheKey.update(value);
}
}
}
}
private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
List<String> columnNames = rsw.getColumnNames();
for (String columnName : columnNames) {
final String value = rsw.getResultSet().getString(columnName);
if (value != null) {
cacheKey.update(columnName);
cacheKey.update(value);
}
}
}
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (collectionProperty != null) {
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}
private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
final String propertyName = resultMapping.getProperty();
Object propertyValue = metaObject.getValue(propertyName);
if (propertyValue == null) {
Class<?> type = resultMapping.getJavaType();
if (type == null) {
type = metaObject.getSetterType(propertyName);
}
try {
if (objectFactory.isCollection(type)) {
propertyValue = objectFactory.create(type);
metaObject.setValue(propertyName, propertyValue);
return propertyValue;
}
} catch (Exception e) {
throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
} else if (objectFactory.isCollection(propertyValue.getClass())) {
return propertyValue;
}
return null;
}
private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
if (rsw.getColumnNames().size() == 1) {
return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
}
return typeHandlerRegistry.hasTypeHandler(resultType);
}
}
有时间我会注释上面的方法,一定会的(
从原理到个性化使用,需要什么,我想走走看
我发现这些插件其实还是框架提供给我们的,他们预留了接口,让我们在这些基础之上做调整,并不是让我们随意乱搞的
public interface Interceptor {//设计模式:template模式
//它会直接覆盖拦截对象原有的方法哦,Invocation可以使用反射调度原来对象的方法
Object intercept(Invocation invocation) throws Throwable;
//target是拦截对象,它的作用是给拦截对象生成一个代理对象,并且返回它
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
//容许插件在plugin元素中配置所需的参数,这个会在插件的初始化中被调用
default void setProperties(Properties properties) {
// NOP
}
}
我在这里又见到了MP框架,它实现了这个!
XMLConfigBuilder 实现了插件的初始化
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);//设置参数
configuration.addInterceptor(interceptorInstance);//加到了一个列表里面
}
}
}
除非我想看这个类在哪里定义,否则我要点那个方法,而不是那个类
然后你很容易就会找到,mybatis把这个存放在了一个列表里面,我们可以通过列表获取,插件用的是责任链模式,对SQL来说,是一层包一层的,而mybatis是由interceptorChain去定义的
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
注意看上面的代码,它在生存责任链的时候,把上一个获取的对象,传递了进去,这样就能形成层层包裹的责任链,说实话,我一下子没看懂这个实现,看来设计模式的书还是很有必要看,不过这样也好,能提前知道一些设计模式的用于,先用后学原理
mybatis 为我们提供的代理工具类
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
//自定义插件用了这个静态方法
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) { //如果存在方法签名,就用intercept方法执行
return interceptor.intercept(new Invocation(target, method, args));
}
//否则就当普通的方法执行
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
}
}
Invocation
public class Invocation {
//代理方法的执行涉及到了这个,让我们看看这个类做了什么
private final Object target;
private final Method method;
private final Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return target;
}
public Method getMethod() {
return method;
}
public Object[] getArgs() {
return args;
}
//注意看这个调用的顺序,先执行最后一个插件的方法,然后一直执行到第一个插件的方法,然后执行普通方法,然后执行第一个插件的方法,然后执行最后一个插件的方法
//也就是说,越靠前的插件,里真正的方法执行越近
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
MetaObject
public class MetaObject {
//这是mybatis为我们提供的工具类,它可以有效的读取和修改一些重要对象的属性
//我们已经在好多地方见过这个类了
//四大对象提供pulic方法修改参数很少,但是通过这个类提供的反射工具,我们就可以修改了
private final Object originalObject;
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) {
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
//用来包装对象,书上说现在用SystemMetaObject.forObject代替
public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
if (object == null) {
return SystemMetaObject.NULL_META_OBJECT;
} else {
return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
//你仔细看下面的方法,你会觉得,这些方法真的很有用
public String findProperty(String propName, boolean useCamelCaseMapping) {
return objectWrapper.findProperty(propName, useCamelCaseMapping);
}
public String[] getGetterNames() {
return objectWrapper.getGetterNames();
}
public String[] getSetterNames() {
return objectWrapper.getSetterNames();
}
public Class<?> getSetterType(String name) {
return objectWrapper.getSetterType(name);
}
public Class<?> getGetterType(String name) {
return objectWrapper.getGetterType(name);
}
public boolean hasSetter(String name) {
return objectWrapper.hasSetter(name);
}
public boolean hasGetter(String name) {
return objectWrapper.hasGetter(name);
}
//为什么这个方法这么复杂,因为这个方法竟然可以通过类.类.属性方式获取
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null) {
// don't instantiate child path if value is null
return;
} else {
metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory);
}
}
metaValue.setValue(prop.getChildren(), value);
} else {
objectWrapper.set(prop, value);
}
}
public MetaObject metaObjectForProperty(String name) {
Object value = getValue(name);
return MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
}
public ObjectWrapper getObjectWrapper() {
return objectWrapper;
}
public boolean isCollection() {
return objectWrapper.isCollection();
}
public void add(Object element) {
objectWrapper.add(element);
}
public <E> void addAll(List<E> list) {
objectWrapper.addAll(list);
}
}
具体代码实现
@Intercepts({
@Signature(
type = StatementHandler.class,//代理哪个类
method = "prepare",//哪个方法
args = {Connection.class,Integer.class}//方法对应的参数
)
})//这个注释你就里面上面为什么要判断Signature,所以要先看使用再看原理
@Slf4j
@Component//添加这个注解,spring boot会扫描,然把他注册到mybatis里面
public class MybatisPlugin implements Interceptor {
private Properties props=null;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
Object object=null;
//通过循环分离出最原始的目标类,因为目标类可能被多个拦截器拦截
while (metaStatementHandler.hasGetter("h")){
object=metaStatementHandler.getValue("h");
metaStatementHandler=SystemMetaObject.forObject(object);
}
statementHandler=(StatementHandler) object;
String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
//我们操作的语句就写在这个里面
log.info("执行的SQL"+sql);
log.info("参数"+parameterObject);
log.info("before...");
Object obj = invocation.proceed();
log.info("after...");
return obj;
}
@Override
public void setProperties(Properties properties) {
this.props=properties;
log.info(String.valueOf(this.props));
Interceptor.super.setProperties(properties);
}
}
已经可以当成模板使用了!
Intercepts怎么实现嵌套注释
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
/**
* Returns method signatures to intercept.
*
* @return method signatures
*/
Signature[] value();//Signature是一个数组,而且Signature也是一个注释
}
完结撒花!