摘要
MyBatis拦截器像一位守护神,它在Dao和DB之间守护着,为我们解决了许多重复的查询和操作。比如分页、权限、日志等,让我们的开发更加高效。
正文
MyBatis拦截器
一、阻拦目标和插口完成实例
MyBatis拦截器的功效是取决于Dao到DB正中间开展附加的解决。绝大多数状况下根据mybatis的xml配备sql都能够做到要想的DB实际操作实际效果,殊不知存有一些相近或是同样的查询条件或是查看规定,这种能够根据拦截器的完成能够提高开发设计高效率,例如:分页查询、插进和更新/人、数据权限、SQL监管日志等。
-
Mybatis适用四种目标阻拦Executor、StatementHandler、PameterHandler和ResultSetHandler
-
Executor:阻拦电动执行机构的方式。
-
StatementHandler:阻拦Sql英语的语法搭建的解决。
-
ParameterHandler:阻拦主要参数的解决。
-
ResultHandler:阻拦結果集的解决。
1 public interface Executor { 2 ResultHandler NO_RESULT_HANDLER = null; 3 int update(MappedStatement var1, Object var2) throws SQLException; 4 <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException; 5 <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException; 6 <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException; 7 List<BatchResult> flushStatements() throws SQLException; 8 void commit(boolean var1) throws SQLException; 9 void rollback(boolean var1) throws SQLException; 10 CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4); 11 boolean isCached(MappedStatement var1, CacheKey var2); 12 void clearLocalCache(); 13 void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5); 14 Transaction getTransaction(); 15 void close(boolean var1); 16 boolean isClosed(); 17 void setExecutorWrapper(Executor var1); 18 } 19 public interface StatementHandler { 20 Statement prepare(Connection var1, Integer var2) throws SQLException; 21 void parameterize(Statement var1) throws SQLException; 22 void batch(Statement var1) throws SQLException; 23 int update(Statement var1) throws SQLException; 24 <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException; 25 <E> Cursor<E> queryCursor(Statement var1) throws SQLException; 26 BoundSql getBoundSql(); 27 ParameterHandler getParameterHandler(); 28 } 29 public interface ParameterHandler { 30 Object getParameterObject(); 31 void setParameters(PreparedStatement var1) throws SQLException; 32 } 33 public interface ResultHandler<T> { 34 void handleResult(ResultContext<? extends T> var1); 35 }
阻拦的实行次序是Executor->StatementHandler->ParameterHandler->ResultHandler
-
MyBatis给予的拦截器插口:
1 public interface Interceptor { 2 Object intercept(Invocation var1) throws Throwable; 3 default Object plugin(Object target) { 4 return Plugin.wrap(target, this); 5 } 6 default void setProperties(Properties properties) {} 7 }
Object intercept方式用以拦截器的完成;
Object plugin方式用以分辨实行拦截器的种类;
void setProperties方式用以获得配备项的特性。
-
阻拦目标和拦截器插口的融合,自定的拦截器类必须完成拦截器插口,并根据注释@Intercepts和主要参数@Signature来申明要阻拦的目标。
@Signature主要参数type是阻拦目标,method是阻拦的方式,即上边的四个类相匹配的方式,args是阻拦方式相匹配的主要参数(方式存有轻载因而必须指出主要参数数量和种类)
@Intercepts能够有好几个@Signature,即一个拦截器完成类能够与此同时阻拦好几个目标及方式,实例以下:
-
Executor->intercept
-
StatementHandler->intercept
-
ParameterHandler->intercept
-
ResultHandler->intercept
1 @Intercepts({ 2 @Signature( 3 type = Executor.class, 4 method = "query", 5 args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} 6 ) 7 }) 8 public class SelectPlugin implements Interceptor { 9 @Override 10 public Object intercept(Invocation invocation) throws Throwable { 11 if (invocation.getTarget() instanceof Executor) { 12 System.out.println("SelectPlugin"); 13 } 14 return invocation.proceed(); 15 } 16 @Override 17 public Object plugin(Object target) { 18 if (target instanceof Executor) { 19 return Plugin.wrap(target, this); 20 } 21 return target; 22 } 23 @Override 24 public void setProperties(Properties properties) {} 25 } 26 @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) 27 public class StatementPlugin implements Interceptor { 28 @Override 29 public Object intercept(Invocation invocation) throws Throwable { 30 if (invocation.getTarget() instanceof StatementHandler) { 31 System.out.println("StatementPlugin"); 32 } 33 return invocation.proceed(); 34 } 35 @Override 36 public Object plugin(Object target) { 37 if (target instanceof StatementHandler) { 38 return Plugin.wrap(target, this); 39 } 40 return target; 41 } 42 @Override 43 public void setProperties(Properties properties) {} 44 } 45 @Intercepts({@Signature(type = ParameterHandler.class,method = "setParameters",args = {PreparedStatement.class})}) 46 public class ParameterPlugin implements Interceptor { 47 @Override 48 public Object intercept(Invocation invocation) throws Throwable { 49 if (invocation.getTarget() instanceof ParameterHandler) { 50 System.out.println("ParameterPlugin"); 51 } 52 return invocation.proceed(); 53 } 54 @Override 55 public Object plugin(Object target) { 56 if (target instanceof ParameterHandler) { 57 return Plugin.wrap(target, this); 58 } 59 return target; 60 } 61 @Override 62 public void setProperties(Properties properties) {} 63 } 64 @Intercepts({@Signature(type = ResultHandler.class,method = "handleResult",args = {ResultContext.class})}) 65 public class ResultPlugin implements Interceptor { 66 @Override 67 public Object intercept(Invocation invocation) throws Throwable { 68 if (invocation.getTarget() instanceof ResultHandler) { 69 System.out.println("ResultPlugin"); 70 } 71 return invocation.proceed(); 72 } 73 @Override 74 public Object plugin(Object target) { 75 if (target instanceof ResultHandler) { 76 return Plugin.wrap(target, this); 77 } 78 return target; 79 } 80 @Override 81 public void setProperties(Properties properties) {} 82 }
二、拦截器申请注册的三种方法
前边详细介绍了Mybatis的阻拦目标以及插口的完成方法,那麼在新项目中如何注册拦截器呢?文中中得出三种申请注册方法。
1.XML申请注册
xml申请注册是最基本上的方法,是根据在Mybatis环境变量中plugins元素来开展申请注册的。一个plugin相匹配着一个拦截器,在plugin元素能够特定property子原素,在申请注册界定拦截器时把相匹配拦截器的全部property根据Interceptor的setProperties方式引入给拦截器。因而拦截器申请注册xml方法以下:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6 <!-- ...... --> 7 <plugins> 8 <plugin interceptor="com.tiantian.mybatis.interceptor.MyInterceptor"> 9 <property name="prop1" value="prop1"/> 10 <property name="prop2" value="prop2"/> 11 </plugin> 12 </plugins> 13 <!-- ...... --> 14 </configuration>
2.配备类申请注册
配备类申请注册就是指根据Mybatis的配备类中申明申请注册拦截器,配备类申请注册还可以根据Properties类给Interceptor的setProperties方式引入主要参数。实际参照以下:
1 @Configuration 2 public class MyBatisConfig { 3 @Bean 4 public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) { 5 UpdatePlugin executorInterceptor = new UpdatePlugin(); 6 Properties properties = new Properties(); 7 properties.setProperty("prop1", "value1"); 8 // 给拦截器加上自定主要参数 9 executorInterceptor.setProperties(properties); 10 sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor); 11 sqlSessionFactory.getConfiguration().addInterceptor(new StatementPlugin()); 12 sqlSessionFactory.getConfiguration().addInterceptor(new ResultPlugin()); 13 sqlSessionFactory.getConfiguration().addInterceptor(new ParameterPlugin()); 14 // sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin()); 15 return "interceptor"; 16 } 17 18 // 与sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());实际效果一致 19 @Bean 20 public SelectPlugin SelectInterceptor() { 21 SelectPlugin interceptor = new SelectPlugin(); 22 Properties properties = new Properties(); 23 // 启用properties.setProperty方式给拦截器设定自定主要参数 24 interceptor.setProperties(properties); 25 return interceptor; 26 } 27 }
3.注释方法
根据@Component注释方法是非常简单的方法,在不用转寄自定主要参数时能够应用,省时省力。
@Component @Intercepts({ @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) }) public class SelectPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { if (invocation.getTarget() instanceof Executor) { System.out.println("SelectPlugin"); } return invocation.proceed(); } @Override public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties properties) { } }
三、ParameterHandler主要参数改变-修改时间和改动人统一插进
对于实际的拦截器完成开展叙述。日常编号要求中会遇到改动时必须插进改动的時间和工作人员,假如要用xml的方法去写十分不便,而根据拦截器的方法能够迅速完成全局性的插进修改时间和工作人员。首先看编码:
1 @Component 2 @Intercepts({ 3 @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}), 4 }) 5 public class MyBatisInterceptor implements Interceptor { 6 @Override 7 public Object intercept(Invocation invocation) throws Throwable { 8 // 主要参数代理商 9 if (invocation.getTarget() instanceof ParameterHandler) { 10 System.out.println("ParameterHandler"); 11 // 全自动加上操作工信息内容 12 autoAddOperatorInfo(invocation); 13 } 14 return invocation.proceed(); 15 } 16 17 @Override 18 public Object plugin(Object target) { 19 return Plugin.wrap(target, this); 20 } 21 22 @Override 23 public void setProperties(Properties properties) { 24 25 } 26 27 /** 28 * 全自动加上操作工信息内容 29 * 30 * @param invocation 代理商目标 31 * @throws Throwable 出现异常 32 */ 33 private void autoAddOperatorInfo(Invocation invocation) throws Throwable { 34 System.out.println("autoInsertCreatorInfo"); 35 // 获得代理商的主要参数目标ParameterHandler 36 ParameterHandler ph = (ParameterHandler) invocation.getTarget(); 37 // 根据MetaObject获得ParameterHandler的反射面內容 38 MetaObject metaObject = MetaObject.forObject(ph, 39 SystemMetaObject.DEFAULT_OBJECT_FACTORY, 40 SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, 41 new DefaultReflectorFactory()); 42 // 根据MetaObject反射面的內容获得MappedStatement 43 MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement"); 44 // 当sql种类为INSERT或UPDATE时,全自动插进操作工信息内容 45 if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT || 46 mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) { 47 // 获得主要参数目标 48 Object obj = ph.getParameterObject(); 49 if (null != obj) { 50 // 根据反射面获得主要参数目标的特性 51 Field[] fields = obj.getClass().getDeclaredFields(); 52 // 解析xml主要参数目标的特性 53 for (Field f : fields) { 54 // 假如sql是INSERT,且存有createdAt特性 55 if ("createdAt".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) { 56 // 设定容许浏览反射面特性 57 f.setAccessible(true); 58 // 要是没有设定createdAt特性,则全自动为createdAt特性加上当今的時间 59 if (null == f.get(obj)) { 60 // 设定createdAt特性为获取当前时间 61 f.set(obj, LocalDateTime.now()); 62 } 63 } 64 // 假如sql是INSERT,且存有createdBy特性 65 if ("createdBy".equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) { 66 // 设定容许浏览反射面特性 67 f.setAccessible(true); 68 // 要是没有设定createdBy特性,则全自动为createdBy特性加上当今登陆的工作人员 69 if (null == f.get(obj)) { 70 // 设定createdBy特性为当今登陆的工作人员 71 f.set(obj, 0); 72 } 73 } 74 // sql为INSERT或UPDATE时均必须设定updatedAt特性 75 if ("updatedAt".equals(f.getName())) { 76 f.setAccessible(true); 77 if (null == f.get(obj)) { 78 f.set(obj, LocalDateTime.now()); 79 } 80 } 81 // sql为INSERT或UPDATE时均必须设定updatedBy特性 82 if ("updatedBy".equals(f.getName())) { 83 f.setAccessible(true); 84 if (null == f.get(obj)) { 85 f.set(obj, 0); 86 } 87 } 88 } 89 90 // 根据反射面获得ParameterHandler的parameterObject特性 91 Field parameterObject = ph.getClass().getDeclaredField("parameterObject"); 92 // 设定容许浏览parameterObject特性 93 parameterObject.setAccessible(true); 94 // 将上边设定的新主要参数目标设定到ParameterHandler的parameterObject特性 95 parameterObject.set(ph, obj); 96 } 97 } 98 } 99 }
拦截器的插口完成参照前文,这儿主要详细介绍autoAddOperatorInfo方式里的有关类。
1.ParameterHandler
插口源代码:
1 public interface ParameterHandler { 2 Object getParameterObject(); 3 void setParameters(PreparedStatement var1) throws SQLException; 4 }
给予2个方式:
getParameterObject是获得主要参数目标,很有可能存有null,必须留意null表针。
setParameters是操纵怎么设置SQL主要参数,即sql语句中配备的java目标和jdbc种类相匹配的关联,比如#{id,jdbcType=INTEGER},id默认设置种类是javaType=class java.lang.Integer。
该插口有一个默认设置的完成类,源代码以下:
1 public class DefaultParameterHandler implements ParameterHandler { 2 private final TypeHandlerRegistry typeHandlerRegistry; 3 private final MappedStatement mappedStatement; 4 private final Object parameterObject; 5 private final BoundSql boundSql; 6 private final Configuration configuration; 7 8 public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 9 this.mappedStatement = mappedStatement; 10 this.configuration = mappedStatement.getConfiguration(); 11 this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); 12 this.parameterObject = parameterObject; 13 this.boundSql = boundSql; 14 } 15 16 public Object getParameterObject() { 17 return this.parameterObject; 18 } 19 20 public void setParameters(PreparedStatement ps) { 21 ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId()); 22 List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings(); 23 if (parameterMappings != null) { 24 for(int i = 0; i < parameterMappings.size(); i) { 25 ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i); 26 if (parameterMapping.getMode() != ParameterMode.OUT) { 27 String propertyName = parameterMapping.getProperty(); 28 Object value; 29 if (this.boundSql.hasAdditionalParameter(propertyName)) { 30 value = this.boundSql.getAdditionalParameter(propertyName); 31 } else if (this.parameterObject == null) { 32 value = null; 33 } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) { 34 value = this.parameterObject; 35 } else { 36 MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject); 37 value = metaObject.getValue(propertyName); 38 } 39 40 TypeHandler typeHandler = parameterMapping.getTypeHandler(); 41 JdbcType jdbcType = parameterMapping.getJdbcType(); 42 if (value == null && jdbcType == null) { 43 jdbcType = this.configuration.getJdbcTypeForNull(); 44 } 45 46 try { 47 typeHandler.setParameter(ps, i 1, value, jdbcType); 48 } catch (SQLException | TypeException var10) { 49 throw new TypeException("Could not set parameters for mapping: " parameterMapping ". Cause: " var10, var10); 50 } 51 } 52 } 53 } 54 55 } 56 }
根据DefaultParameterHandler完成类我们知道根据ParameterHandler能够获得到什么特性和方式,在其中包含大家下边一个关键的类MappedStatement。
2.MappedStatement
MyBatis的mapper文件中的每一个select/update/insert/delete标识会被在线解析分析成一个相匹配的MappedStatement目标,也就是一个MappedStatement目标叙述一条SQL句子。MappedStatement目标特性以下:
1 // mapper配备文件夹名称 2 private String resource; 3 // mybatis的全局性信息内容,如jdbc 4 private Configuration configuration; 5 // 连接点的id特性加类名,如:com.example.mybatis.dao.UserMapper.selectByExample 6 private String id; 7 private Integer fetchSize; 8 private Integer timeout; 9 private StatementType statementType; 10 private ResultSetType resultSetType; 11 private SqlSource sqlSource; 12 private Cache cache; 13 private ParameterMap parameterMap; 14 private List<ResultMap> resultMaps; 15 private boolean flushCacheRequired; 16 private boolean useCache; 17 private boolean resultOrdered; 18 // sql语句的种类:select、update、delete、insert 19 private SqlCommandType sqlCommandType; 20 private KeyGenerator keyGenerator; 21 private String[] keyProperties; 22 private String[] keyColumns; 23 private boolean hasNestedResultMaps; 24 private String databaseId; 25 private Log statementLog; 26 private LanguageDriver lang; 27 private String[] resultSets;
在本例中根据MappedStatement目标的sqlCommandType来分辨当今的sql种类是insert、update来开展下一步的实际操作。
四、根据StatementHandler改变SQL
StatementHandler是用以封裝JDBC Statement实际操作,承担对JDBC Statement的实际操作,如设定主要参数,并将Statement結果集转化成List结合。
完成编码以下:
删掉注释标识
@Target({ElementType.METHOD}) //表明注释的应用范畴 @Retention(RetentionPolicy.RUNTIME) //注释的储存時间 @Documented //文本文档表明 public @interface DeletedAt { boolean has() default true; }
Dao层加上删掉注释,为false时不加上删掉标示
1 @Mapper 2 public interface AdminProjectDao { 3 @DeletedAt(has = false) 4 List<AdminProjectPo> selectProjects(AdminProjectPo po); 5 }
拦截器根据删掉注释标识分辨是不是加上删掉标示
1 @Component 2 @Intercepts({ 3 @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}), 4 }) 5 public class MyBatisInterceptor implements Interceptor { 6 @Override 7 public Object intercept(Invocation invocation) throws Throwable { 8 if (invocation.getTarget() instanceof StatementHandler) { 9 System.out.println("StatementHandler"); 10 checkHasDeletedAtField(invocation); 11 } 12 return invocation.proceed(); 13 } 14 15 @Override 16 public Object plugin(Object target) { 17 return Plugin.wrap(target, this); 18 } 19 20 @Override 21 public void setProperties(Properties properties) { 22 23 } 24 25 /** 26 * 查验查看是不是必须加上删掉标示字段名 27 * 28 * @param invocation 代理商目标 29 * @throws Throwable 出现异常 30 */ 31 private void checkHasDeletedAtField(Invocation invocation) throws Throwable { 32 System.out.println("checkHasDeletedAtField"); 33 StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 34 // 根据MetaObject浏览目标的特性 35 MetaObject metaObject = MetaObject.forObject( 36 statementHandler, 37 SystemMetaObject.DEFAULT_OBJECT_FACTORY, 38 SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, 39 new DefaultReflectorFactory()); 40 // 获得成员函数mappedStatement 41 MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); 42 // 假如sql种类是查看 43 if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) { 44 // 获得删掉注释标示 45 DeletedAt annotation = null; 46 String id = mappedStatement.getId(); 47 String className = id.substring(0, id.lastIndexOf(".")); 48 String methodName = id.substring(id.lastIndexOf(".") 1); 49 Class<?> aClass = Class.forName(className); 50 Method[] declaredMethods = aClass.getDeclaredMethods(); 51 for (Method declaredMethod : declaredMethods) { 52 declaredMethod.setAccessible(true); 53 //方式名同样,而且注释是DeletedAt 54 if (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(DeletedAt.class)) { 55 annotation = declaredMethod.getAnnotation(DeletedAt.class); 56 } 57 } 58 // 假如注释不会有或是注释为true(默认设置为true) 则为mysql语句提升删掉标示 59 if (annotation == null || annotation.has()) { 60 BoundSql boundSql = statementHandler.getBoundSql(); 61 //获得到初始sql语句 62 String sql = boundSql.getSql(); 63 //根据反射面改动sql语句 64 Field field = boundSql.getClass().getDeclaredField("sql"); 65 field.setAccessible(true); 66 String newSql = sql.replaceAll("9=9", "9=9 and deleted_at is null "); 67 field.set(boundSql, newSql); 68 } 69 } 70 } 71 }
在SQL句子更换上必须能鉴别到要被更换的內容,因而在xml的sql语句中添加独特标示”9=9″,该标示不危害原先SQL的实行結果,不一样的过虑标准能够设定不一样的标示,是一个较为恰当的更换方法。
关注不迷路
扫码下方二维码,关注宇凡盒子公众号,免费获取最新技术内幕!
评论0