Mybatis之对象工厂和拦截器
这节我们讲两个关于Mybatsi常用知识点,欢迎持续关注,洁癖会推出一系类相关Mybatis面试以及经常在工作中使用的知识点,废话不多说,来开始我们的讲解吧!
MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现,先看一下源码
public class DefaultObjectFactory implements ObjectFactory, Serializable {
private static final long serialVersionUID = -8855120656740914948L;
public DefaultObjectFactory() {
}
//调用无参构造函数实例化
public <T> T create(Class<T> type) {
return this.create(type, (List)null, (List)null);
}
//调用有参构造函数
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
Class<?> classToCreate = this.resolveInterface(type);
return this.instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
//setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法
public void setProperties(Properties properties) {
}
}
看完源码,我就简单使用一下ObjectFactory,自定义我们自己的对象工厂.
先说一下我们要干的事情,我们是要在从数据库中获取到一个学生对象,但是数据库student表没有地址这个字段,因此我们要使用自定义工厂实现对地址(address)进行赋值.
1.student表
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL,
`pass` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
2.Student实体类
public class Student {
private int id;
private String name;
private boolean pass;
private String address;
}
第一步:继承默认的DefaultObjectFactory
public class MyObjectFactory extends DefaultObjectFactory {
@Override
public Object create(Class type) {
Student student = null;
if (Student.class == type) {//当时学生对象是进行封装实例化填充地址值
student = (Student) super.create(type);
student.setAddress("深圳");
return student;
}
return super.create(type);
}
}
第二步:配置mybatis配置文件mybatis-config.xml
<objectFactory type="com.jiepi.util.MyObjectFactory"/>
第三步:测试我们的的对象工厂
@Test
public void find() {
StudentMapper dao = session.getMapper(StudentMapper.class);
List<Student> student = dao.find();
student.stream().map(it->it.toString()).forEach(System.out::println);
System.out.println(student);
}
运行结果
[DEBUG] Opening JDBC Connection
[DEBUG] Created connection 673068808.
[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
[DEBUG] ==> Preparing: SELECT * FROM student
[DEBUG] ==> Parameters:
String string
[DEBUG] <== Total: 1
洁癖查询之后
id: 1 name: jiepi pass: trueaddress: 深圳
[id: 1 name: jiepi pass: trueaddress: 深圳]
以上就是我们自定义的对象工厂工厂,下面我们再看一下另外一个知识点拦截器插件
拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑.打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor,ReuseExecutor,SimpleExecutor和CachingExecutor。这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,之后可以选择是否继续执行原来的query方法。
我们在这里就对Executor这个接口进行实验,先看一下他的源码.
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement var1, Object var2) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean var1) throws SQLException;
void rollback(boolean var1) throws SQLException;
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
boolean isCached(MappedStatement var1, CacheKey var2);
void clearLocalCache();
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
}
我们针对他的query做一些事情,看看我们自定义的拦截器,使用很简单只要是实现Interceptor接口
第一步:实现Interceptor
package com.jiepi.util;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;
/**
* @Intercepts:表明那个类是拦截器
* @@Signature:表明那类接口那个方法要拦截 type:要拦截的接口 只允许拦截Executor ParameterHandler ResultSetHandler StatementHandler
* method:拦截的方法
* args:区分拦重载方法
*/
@Intercepts({
@Signature(method = "query", type = Executor.class, args = {
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
})
})
public class MyInterceptorPlugin implements Interceptor {
/**
* 处理代理类方法的执行
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("洁癖查询之前");
Object result = invocation.proceed();
System.out.println("洁癖查询之后");
return result;
}
/**
* 确定是够要进行封装对象返回一个代理对象
*
* @param obj
* @return
*/
@Override
public Object plugin(Object obj) {
System.out.println("拦截洁癖");
return Plugin.wrap(obj, this);
}
/**
* 设置参数获取plugin元素里面的properties的值
* @param properties
*/
@Override
public void setProperties(Properties properties) {
String name = properties.getProperty("name");
System.out.println(name);
}
}
1.intercept 方法的参数Invocation是类方法的封装,里面有target 调用的代理对象,method 调用的方法,args调用的参数
2.plugin方法,obj要拦截的对象,Plugin.wrap(obj, this)判断是否要返回一个代理对象。看一下源码
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
首先是获取注解需要拦截的那些接口,在判断是否都实现了要拦截的接口,如果有,则返回一个代理对象Proxy.newProxyInstance,否则返回本身对象target,最后,如果返回代理对象,就会执行invoke,这里面判断要当前执行的方法是否是要拦截的方法,如果是,就调用interceptor.intercept(),否则调用本身方法method.invoke,当然在Invocatuon中要继续调用方法本身方,就可以使用proceed();
3.setProperties,就是获取plugin元素下定义的属性值
第二步:配置mybatis配置文件
<plugins>
<plugin interceptor="com.jiepi.util.MyInterceptorPlugin">
<property name="name" value="jiepi"/>
</plugin>
</plugins>
第三步:运行结果
@Test
public void find() {
StudentMapper dao = session.getMapper(StudentMapper.class);
List<Student> student = dao.find();
student.stream().map(it->it.toString()).forEach(System.out::println);
System.out.println(student);
}
运行结果:
拦截洁癖
洁癖查询之前
拦截洁癖
拦截洁癖
拦截洁癖
[DEBUG] Opening JDBC Connection
[DEBUG] Created connection 673068808.
[DEBUG] Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@281e3708]
[DEBUG] ==> Preparing: SELECT * FROM student
[DEBUG] ==> Parameters:
String string
[DEBUG] <== Total: 1
洁癖查询之后
id: 1 name: jiepi pass: trueaddress: 深圳
[id: 1 name: jiepi pass: trueaddress: 深圳]
注意,有的朋友发现"拦截洁癖"打印了四次,那是因为MyBatis 允许使用插件来拦截的方法调用包括
上面四个接口都会调用plugin,但是此例子仅声明了对Executor进行拦截。建议跟着文章自己写一遍理解更加深刻.
官方声明:除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会严重影响 MyBatis 的行为,务请慎之又慎.