一、概述 

  Mybatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变Mybatis的默认行为(诸如SQL重写之类的)

  Mybatis是通过动态代理的方式实现拦截的

  拦截器(Interceptor)在 Mybatis 中被当做插件(plugin)对待,官方文档提供了 Executor(拦截执行器的方法),ParameterHandler(拦截参数的处理),ResultSetHandler(拦截结果集的处理),StatementHandler(拦截Sql语法构建的处理) 共4种,并且提示“这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码”。

  拦截器的使用场景主要是更新数据库的通用字段,分库分表,加解密等的处理。

  MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。

  MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed):拦截执行器的方法

      Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。

    ParameterHandler (getParameterObject, setParameters):拦截参数的处理

      是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。

    ResultSetHandler (handleResultSets, handleOutputParameters):拦截结果集的处理

      是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。

    StatementHandler (prepare, parameterize, batch, update, query):拦截Sql语法构建的处理

      是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。

  

  Mybatis插件能够对这四大对象进行拦截,可以说包含到了Mybatis一次SQL执行的所有操作

二、 原理

  

  Mybatis的拦截器实现机制,使用的是JDK的InvocationHandler.

  当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,实际上使用的是Plugin这个代理类的对象,这个类实现了InvocationHandler接口

  接下来我们就知道了,在调用上述被代理类的方法的时候,就会执行Plugin的invoke方法.

  Plugin在invoke方法中根据@Intercepts的配置信息(方法名,参数等)动态判断是否需要拦截该方法.

  再然后使用需要拦截的方法Method封装成Invocation,并调用Interceptor的proceed方法.

  这样我们就达到了拦截目标方法的结果.

  例如Executor的执行大概是这样的流程:

  拦截器代理类对象->拦截器->目标方法

    Executor->Plugin->Interceptor->Invocation

    Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke

2.1、拦截器接口

  拦截器均需要实现该 org.apache.ibatis.plugin.Interceptor 接口

public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}

  有3个方法。 MyBatis默认没有一个拦截器接口的实现类

  setProperties方法是在Mybatis进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置。

  plugin方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin.wrap(target, this)。

    理解这个接口的定义,先要知道java动态代理机制。plugin接口即返回参数target对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。在调用对应对象的接口的时候,可以进行拦截并处理。

  intercept方法就是要进行拦截的时候要执行的方法。

2.2、拦截器四大接口实例

  上述的Executor、ParameterHandler、ResultSetHandler、StatementHandler具体实现

2.2.1、实例创建

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//确保ExecutorType不为空(defaultExecutorType有可能为空)
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;
} 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;
} public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
} public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

  查看源码可以发现, Mybatis框架在创建好这四大接口对象的实例后,都会调用InterceptorChain.pluginAll()方法。InterceptorChain对象是插件执行链对象,看源码就知道里面维护了Mybatis配置的所有插件(Interceptor)对象。

2.2.2、插件加载执行过程

1、plugin

  target --> Executor/ParameterHandler/ResultSetHander/StatementHandler

public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

  其实就是按顺序执行我们插件的plugin方法,一层一层返回我们原对象(Executor/ParameterHandler/ResultSetHander/StatementHandler)的代理对象。当我们调用四大接口的方法的时候,实际上是调用代理对象的相应方法,代理对象又会调用四大接口的实例。

  Plugin对象

  我们知道,官方推荐插件实现plugin方法为:Plugin.wrap(target, this);

2、wrap

public static Object wrap(Object target, Interceptor interceptor) {
  // 获取插件的Intercepts注解
  Map, Set> 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;
}

  这个方法其实是Mybatis简化我们插件实现的工具方法。其实就是根据当前拦截的对象创建了一个动态代理对象。代理对象的InvocationHandler处理器为新建的Plugin对象。

3、插件配置注解@Intercepts

  Mybatis的插件都要有Intercepts注解来指定要拦截哪个对象的哪个方法。我们知道,Plugin.warp方法会返回四大接口对象的代理对象(通过new Plugin()创建的IvocationHandler处理器),会拦截所有的执行方法。在代理对象执行对应方法的时候,会调用InvocationHandler处理器的invoke方法。Mybatis中利用了注解的方式配置指定拦截哪些方法。具体如下:

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)) {
      return interceptor.intercept(new Invocation(target, method, args));
    }
  return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}

  可以看到,只有通过Intercepts注解指定的方法才会执行我们自定义插件的intercept方法。未通过Intercepts注解指定的将不会执行我们的intercept方法。

4、官方插件开发方式

@Intercepts({@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class TestInterceptor implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    Object target = invocation.getTarget(); //被代理对象
    Method method = invocation.getMethod(); //代理方法
    Object[] args = invocation.getArgs(); //方法参数
    // do something ...... 方法拦截前执行代码块
    Object result = invocation.proceed();
    // do something .......方法拦截后执行代码块
    return result;
  }
  public Object plugin(Object target) {
  return Plugin.wrap(target, this);
  }
}

  以上就是Mybatis官方推荐的插件实现的方法,通过Plugin对象创建被代理对象的动态代理对象。可以发现,Mybatis的插件开发还是很简单的。

  自定义开发方式

  Mybatis的插件开发通过内部提供的Plugin对象可以很简单的开发。只有理解了插件实现原理,对应不采用Plugin对象我们一样可以自己实现插件的开发。下面是我个人理解之后的自己实现的一种方式。

5、实例;

public class TestInterceptor implements Interceptor {

public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理对象
Method method = invocation.getMethod(); //代理方法
Object[] args = invocation.getArgs(); //方法参数
// do something ...... 方法拦截前执行代码块
Object result = invocation.proceed();
// do something .......方法拦截后执行代码块
return result;
}
public Object plugin(final Object target) {
return Proxy.newProxyInstance(Interceptor.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return intercept(new Invocation(target, method, args));
}
});
}
public void setProperties(Properties properties) {
}
}

  当然,Mybatis插件的那这个时候Intercepts的注解起不到作用了。

6、小结

  如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

  因此,在编写插件时需注意以下几个原则:

    不编写不必要的插件;

    实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否者直接返回目标本身,这样可以减少目标被代理的次数。

    // 假如我们只要拦截Executor对象,那么我们应该这么做,默认是【return Plugin.wrap(target, this);】

    @Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}

  Mybatis插件很强大,可以对Mybatis框架进行很大的扩展。当然,如果你不理解Mybatis插件的原理,开发起来只能是模拟两可。在实际开发过程中,我们可以参考别人写的插件。下面是一个Mybatis分页的插件,可以为以后开发做参考。

  每一个拦截器对目标类都进行一次代理,原对象如果是X,那么第一个拦截器代理后为P(X),第二个代理后P(P(X))......最后返回这样的多重代理对象并执行。所以先配置的拦截器会后执行,因为先配置的先被包装成代理对象。

  最后在调用真实对象方法的时候,实际上是调用多重代理的invoke方法,当符合拦截条件的时候执行我们编写的interceptor.intercept,intercept方法最后必定是调用invocation.proceed,proceed也是一个method.invoke,促使拦截链往下进行,不符合拦截条件的时候直接调用method.invoke,即不执行我们的拦截方法,继续拦截链。

2.3、对象方法说明

2.3.1、Executor类说明

1、拦截器注解

@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})

  拦截器的使用需要查看每一个type所提供的方法参数。

  Signature 对应 Invocation 构造器,type 为 Invocation.Object,method 为 Invocation.Method,args 为 Invocation.Object[]。

  method 对应的 update 包括了最常用的 insert/update/delete 三种操作,因此 update 本身无法直接判断sql为何种执行过程。

  args 包含了其余所有的操作信息, 按数组进行存储, 不同的拦截方式有不同的参数顺序, 具体看type接口的方法签名, 然后根据签名解析。

2、Object 对象类型

  args 参数列表中,Object.class 是特殊的对象类型。如果有数据库统一的实体 Entity 类,即包含表公共字段,比如创建、更新操作对象和时间的基类等,在编写代码时尽量依据该对象来操作,会简单很多。该对象的判断使用

Object parameter = invocation.getArgs()[1];
if (parameter instanceof BaseEntity) {
BaseEntity entity = (BaseEntity) parameter;
}

  即可,根据语句执行类型选择对应字段的赋值。

  如果参数不是实体,而且具体的参数,那么 Mybatis 也做了一些处理,比如 @Param("name") String name 类型的参数,会被包装成 Map 接口的实现来处理,即使是原始的 Map 也是如此。使用

Object parameter = invocation.getArgs()[1];
if (parameter instanceof Map) {
Map map = (Map) parameter;
}

  即可,对具体统一的参数进行赋值。

3、SqlCommandType 命令类型

Executor 提供的方法中,update 包含了 新增,修改和删除类型,无法直接区分,需要借助 MappedStatement 类的属性 SqlCommandType 来进行判断,该类包含了所有的操作类型

public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}

毕竟新增和修改的场景,有些参数是有区别的,比如创建时间和更新时间,update 时是无需兼顾创建时间字段的

MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
SqlCommandType commandType = ms.getSqlCommandType(); 

三、示例

3.1、基础使用示例

增加拦截器类

@Intercepts({@Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}

全局xml配置

<plugins>
<plugin interceptor="com.github.bjlhx15.mybatis.interceptor.ExamplePlugin"></plugin>
</plugins>

  说明:这个拦截器拦截Executor接口的update方法(其实也就是SqlSession的新增,删除,修改操作),所有执行executor的update方法都会被该拦截器拦截到。

2.2、统一给数据库字段属性赋值

1、普通实体类

public class BaseEntity {
private int id;
private int creator;
private int updater;
private Long createTime;
private Long updateTime;
}

2、dao 操作使用了实体和参数的方式,

方式一、使用实体

int add(BaseEntity entity);

方式二、使用参数

int update(@Param("id") int id,@Param("creator") int creator, @Param("updateTime") long updateTime); 

3、拦截器开发

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;import java.util.Map;
import java.util.Properties; /**
* 全局拦截数据库创建和更新
* <p>
* Signature 对应 Invocation 构造器, type 为 Invocation.Object, method 为 Invocation.Method, args 为 Invocation.Object[]
* method 对应的 update 包括了最常用的 insert/update/delete 三种操作, 因此 update 本身无法直接判断sql为何种执行过程
* args 包含了其余多有的操作信息, 按数组进行存储, 不同的拦截方式有不同的参数顺序, 具体看type接口的方法签名, 然后根据签名解析, 参见官网
*
* @link http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins 插件
* <p>
* MappedStatement 包括了SQL具体操作类型, 需要通过该类型判断当前sql执行过程
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class NormalPlugin implements Interceptor { @Override
@SuppressWarnings("unchecked")
public Object intercept(Invocation invocation) throws Throwable {
// 根据签名指定的args顺序获取具体的实现类
// 1. 获取MappedStatement实例, 并获取当前SQL命令类型
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
SqlCommandType commandType = ms.getSqlCommandType(); // 2. 获取当前正在被操作的类, 有可能是Java Bean, 也可能是普通的操作对象, 比如普通的参数传递
// 普通参数, 即是 @Param 包装或者原始 Map 对象, 普通参数会被 Mybatis 包装成 Map 对象
// 即是 org.apache.ibatis.binding.MapperMethod$ParamMap
Object parameter = invocation.getArgs()[1];
// 获取拦截器指定的方法类型, 通常需要拦截 update
String methodName = invocation.getMethod().getName();
log.info("NormalPlugin, methodName; {}, commandType: {}", methodName, commandType); // 3. 获取当前用户信息
UserEntity userEntity = UserUtil.getCurrentUser(); if (parameter instanceof BaseEntity) {
// 4. 实体类
BaseEntity entity = (BaseEntity) parameter; if (methodName.equals("update")) {
if (commandType.equals(SqlCommandType.INSERT)) {
entity.setCreator(userEntity.getUserName());
entity.setCreateTime(new Date());
} else if (commandType.equals(SqlCommandType.UPDATE)) {
entity.setUpdater(userEntity.getUserName());
entity.setUpdateTime(new Date());
}
}
} else if (parameter instanceof Map) {
// 5. @Param 等包装类
// 更新时指定某些字段的最新数据值
if (commandType.equals(SqlCommandType.UPDATE)) {
// 遍历参数类型, 检查目标参数值是否存在对象中, 该方式需要应用编写有一些统一的规范
// 否则均统一为实体对象, 就免去该重复操作
Map map = (Map) parameter;
if (map.containsKey("creator")) {
map.put("creator", creator);
}
if (map.containsKey("updateTime")) {
map.put("updateTime", DateUtil.getTimeStamp());
}
}
}
// 6. 均不是需要被拦截的类型, 不做操作
return invocation.proceed();
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
}
}

xml配置

2.3、实现Mybatis官方提供的拦截器,用于记录SQL语句的执行时间

package com.github.bjlhx15.mybatis;

/**
* @author lihongxu
* @since 2018/11/15 下午4:02
*/ import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.sql.Statement;
import java.util.Properties; /**
* Sql执行时间记录拦截器
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = { Statement.class })})
public class SqlCostInterceptor implements Interceptor {
Logger logger = LoggerFactory.getLogger(SqlCostInterceptor.class); @Override
public Object intercept(Invocation invocation) throws Throwable { long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
final BoundSql boundSql = statementHandler.getBoundSql();
try {
return invocation.proceed();
} finally { final StringBuilder build = new StringBuilder(7);
build.append("\n");
build.append("------------sql执行耗时计算开始---------------");
build.append("\n");
build.append("SQL:");
build.append(boundSql.getSql());
build.append("\n");
build.append("MYBATIS-SQL执行耗时:[");
build.append((System.currentTimeMillis() - startTime));
build.append("ms]");
build.append("\n");
build.append("------------sql执行耗时计算结束---------------");
logger.warn(build.toString());
}
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) { }
}

注:Interceptor接口是Mybatis官方提供的拦截接口,创建一个类实现该接口并重写其三个方法并将该类配置在Mybatis的配置文件中,即可拦截SQL语句的执行过程

手动编写执行的配置文件

<?xml version="1.0"  encoding="UTF-8"  ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings>
<setting name="cacheEnabled" value="false" />
<setting name="lazyLoadingEnabled" value="false" />
<setting name="multipleResultSetsEnabled" value="true" />
<setting name="useColumnLabel" value="true" />
<setting name="useGeneratedKeys" value="false" />
<setting name="defaultExecutorType" value="SIMPLE" />
<setting name="defaultStatementTimeout" value="30" />
<!--<setting name="logImpl" value="LOG4J2" />-->
</settings>
<plugins>
<!-- 拦截器配置 -->
<plugin interceptor="com.github.bjlhx15.mybatis.SqlCostInterceptor" />
</plugins> </configuration>

java-mybaits-011-mybatis-拦截器计算耗时的更多相关文章

  1. Mybatis拦截器介绍

    拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法.Mybatis拦截器设计的一个初 ...

  2. Mybatis拦截器实现分页

    本文介绍使用Mybatis拦截器,实现分页:并且在dao层,直接返回自定义的分页对象. 最终dao层结果: public interface ModelMapper { Page<Model&g ...

  3. 数据权限管理中心 - 基于mybatis拦截器实现

    数据权限管理中心 由于公司大部分项目都是使用mybatis,也是使用mybatis的拦截器进行分页处理,所以技术上也直接选择从拦截器入手 需求场景 第一种场景:行级数据处理 原sql: select ...

  4. Mybatis拦截器介绍及分页插件

    1.1    目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2     前言 拦截器的一 ...

  5. Mybatis拦截器 mysql load data local 内存流处理

    Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所lo ...

  6. 基于Spring和Mybatis拦截器实现数据库操作读写分离

    首先需要配置好数据库的主从同步: 上一篇文章中有写到:https://www.cnblogs.com/xuyiqing/p/10647133.html 为什么要进行读写分离呢? 通常的Web应用大多数 ...

  7. 通过spring抽象路由数据源+MyBatis拦截器实现数据库自动读写分离

    前言 之前使用的读写分离的方案是在mybatis中配置两个数据源,然后生成两个不同的SqlSessionTemplate然后手动去识别执行sql语句是操作主库还是从库.如下图所示: 好处是,你可以人为 ...

  8. spring boot 实现mybatis拦截器

    spring boot 实现mybatis拦截器 项目是个报表系统,服务端是简单的Java web架构,直接在请求参数里面加了个query id参数,就是mybatis mapper的query id ...

  9. Mybatis拦截器实现SQL性能监控

    Mybatis拦截器只能拦截四类对象,分别为:Executor.ParameterHandler.StatementHandler.ResultSetHandler,而SQL数据库的操作都是从Exec ...

  10. Mybatis拦截器执行过程解析

    上一篇文章 Mybatis拦截器之数据加密解密 介绍了 Mybatis 拦截器的简单使用,这篇文章将透彻的分析 Mybatis 是怎样发现拦截器以及调用拦截器的 intercept 方法的 小伙伴先按 ...

随机推荐

  1. UVALive - 3507 Keep the Customer Satisfied

    题意:收到n个订单,每个订单有q,d分别代表做这个的时间,和最晚的完成时间,问你最多能接受几个订单 思路:贪心,我们显然要按最早的完成时间排序,那么接下来,我们用(6,8)和(4,9)做为例子,按照我 ...

  2. Android studio Unable to start the daemon process

    Unable to start the daemon process.This problem might be caused by incorrect configuration of the da ...

  3. Android.mk (2) 函数进阶教程 - 分支、循环、子程序

    https://www.jianshu.com/p/674dc7d7b4b0 函数进阶教程 - 分支.循环.子程序 按照面向过程程序设计的标准流程,我们讲完了顺序结构,就要讲分支.循环和子程序.下面我 ...

  4. sencha touch list(列表) item(单行)单击事件触发顺序

    测试代码如下 Ext.define('app.view.new.List', { alternateClassName: 'newList', extend: 'app.view.util.MyLis ...

  5. sysbench 压力测试工具

    一.sysbench压力测试工具简介: sysbench是一个开源的.模块化的.跨平台的多线程性能测试工具,可以用来进行CPU.内存.磁盘I/O.线程.数据库的性能测试.目前支持的数据库有MySQL. ...

  6. 在Linux下面的某一个文件的查找命令

    借鉴文章:https://www.kafan.cn/edu/60044166.html Linux查找包含特定字符串的文件名的方法:http://www.jbxue.com/LINUXjishu/97 ...

  7. linux下MySQL安装及设置(二)

    MySQL二进制分发包安装 去MySql官网下MySQL classic版mysql-5.6.30-osx10.11-x86_64.tar.gz  http://dev.mysql.com/downl ...

  8. nodejs事件的监听与事件的触发

    nodejs事件(Events) 一.事件机制的实现 Node.js中大部分的模块,都继承自Event模块(http://nodejs.org/docs/latest/api/events.html  ...

  9. 微信 获取wx.config 参数 基类

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  10. TFS二次开发10——分组(Group)和成员(Member)

    TFS SDK 10 ——分组(Group)和成员(Member) 这篇来介绍怎样读取TFS服务器上的用户信息 首先TFS默认有如下分组(Group): SharePoint Web Applicat ...