mybatis的执行的大概过程:首先需要有sqlSessionFactroy,然后通过sqlSessionFactory拿到sqlSession,然后通过sqlSession调用getMapper拿到代理的接口,然后拿到代理的接口的信息mapperInterface,从而找到需要执行的具体的方法中的sql方法,,如果执行过,同时没有发生改变的话,则直接返回结果,否则会进行更新,同时如果执行过的话,会直接返回结果,此时会看到methodCache中有我们执行过的方法。
mybatis中,如果想进行sql的拦截,需要对其基于interceptor做拦截。因为mybatis的执行中,我们需要获取它的boundSql,而获取boundSql,需要获取MappedStatement,而MappedStatement可以在StatementHandler语句处理器中找到,因此可以在此基础上获取,通过反射的方式获取,此时可以用到插件模块中的invocation对象获取,然后对其进行增强。
基于interceptor可以实现sql的完整打印,除了实现打印之外。其实还可以实现分页和排序,下面的分页和排序基于aop+mybatis的interceptor实现。其本质还是对mappedStament的boundSql进行增强。
下面的项目来源于github,通过这个我们可以很好的学习mybatis中插件interceptor的使用。
首先定义分页元注解:
/**
* 自定义分页注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Limit {
/**
* 当前页面
*
* @return
*/
int page() default 0;
/**
* 每页显示数量
*
* @return
*/
int pageSize() default 10;
}
定义排序元注解:
/**
* 自定义排序注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OrderBy {
/**
* 表的别名
*/
String tableAlias() default "";
/**
* 排序字段
*/
String orderColumn() default "";
/**
* ASC/DESC 默认倒序
* @return
*/
boolean isAsc() default false;
}
定义基础切面处理类:
/**
* @ClassName BaseAspectAbstract
* @Description 基础切面处理类,每一个Spring的切面都需要去继承此抽象类
* @Date
**/
public abstract class BaseAspectAbstract {
private static TreeMap<Integer, SQLLanguage> CONTAINERS = new TreeMap<>();
// 放入sql 切点、sql类型、sql
public void putSQL(JoinPoint point, SQLEnums sqlEnums, SQLLanguage sqlLanguage) {
CONTAINERS.put(sqlEnums.getId(), sqlLanguage);
// 获取方法里的参数
Object parmas = point.getArgs()[0];
Map map = (Map)parmas;
map.put("SQL", getSQL());
}
public TreeMap<Integer, SQLLanguage> getSQL() {
return CONTAINERS;
}
}
进行分页切面:
@Component
@Aspect
@Order(3) //拼接sql时的顺序
public class LimitAspect extends BaseAspectAbstract {
@Pointcut("@annotation(com.mybatis.interceptor.annotation.Limit)")
public void limitCut() {}
@Before("limitCut()")
public void limit(JoinPoint point) {
StringBuilder limitBuilder = new StringBuilder(" LIMIT ");
MethodSignature methodSignature = (MethodSignature)point.getSignature();
// 获得对应注解
Limit limit = methodSignature.getMethod().getAnnotation(Limit.class);
if (!StringUtils.isEmpty(limit)) {
limitBuilder.append(limit.page()).append(",").append(limit.pageSize());
putSQL(point, LIMIT, new SQLLanguage(limitBuilder.toString()));
}
}
}
进行排序切面:
@Component
@Aspect
@Order(2)
public class OrderByAspect extends BaseAspectAbstract {
// 切点:对注解中的特定注解进行拦截,进行增强
@Pointcut("@annotation(com.mybatis.interceptor.annotation.OrderBy)")
public void orderByCut() {}
// 执行切点操作,将其进行增强,放入排序
@Before("orderByCut()")
public void orderBy(JoinPoint point) {
StringBuilder orderByBuilder = new StringBuilder(" ORDER BY ");
MethodSignature methodSignature = (MethodSignature)point.getSignature();
// 获得对应注解
OrderBy orderBy = methodSignature.getMethod().getAnnotation(OrderBy.class);
if (!StringUtils.isEmpty(orderBy)) {
String sort = orderBy.isAsc() ? " asc " : " desc";
orderByBuilder.append(orderBy.orderColumn()).append(sort);
putSQL(point, ORDERBY, new SQLLanguage(orderByBuilder.toString()));
}
}
}
枚举sql顺序:
/**
* sql类型枚举
*/
public enum SQLEnums {
/**
* 数字越靠前 则拼接SQL语句越靠前执行,目前拼接顺序为
* SELECT * FROM table GROUP BY ORDER BY xxx LIMIT 0, 10
*/
LIKE(1, "LIKE"), GROUPBY(2, "GROUP BY"), ORDERBY(3, "ORDER BY"), LIMIT(4, "LIMIT");
private int id;
private String condition;
SQLEnums(int id, String condition) {
this.id = id;
this.condition = condition;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
}
执行sql增强的注解放置在serviceImpl里面:
@RequestMapping("/nba")
public class PlayController {
@Autowired
private PlayerService playerService;
@RequestMapping("/player")
public List<Player> getList(Map<String, Object> params) {
List<Player> players = playerService.getList(params);
return players;
}
}
执行sql增强:
@Service
public class PlayerServiceImpl implements PlayerService {
@Autowired
private PlayerMapper playerMapper;
// 加入自定义注解,方便切点进行增强
@OrderBy(orderColumn = "height")
@Limit()
@Override
public List<Player> getList(Map<String, Object> params) {
return playerMapper.getList(params);
}
}
执行到这里会执行动态代理,然后执行sql拦截。
执行sql拦截:
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}))
@Component
public class DataFilterInterceptor extends AbstractSqlParserHandler implements Interceptor {
// 拦截器
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// SQLLanguage 解析
sqlParser(metaObject);
// 非查询操作
MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
return invocation.proceed();
}
// 取出原始SQL 取出参数
BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
String dataSql = boundSql.getSql();
Object paramObj = boundSql.getParameterObject();
Map map = (Map)paramObj;
String sqlLanguage = getSQLLanguage(map);
String sql = dataSql + sqlLanguage;
// 重写sql
metaObject.setValue("delegate.boundSql.sql", sql);
return invocation.proceed();
}
// 插件
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
// 获取sql语句
private String getSQLLanguage(Map<String, Object> map) {
TreeMap<Integer, SQLLanguage> sqlMap = (TreeMap)map.get("SQL");
StringBuilder sqlBuilder = new StringBuilder();
for (Map.Entry treeMap : sqlMap.entrySet()) {
SQLLanguage sql = (SQLLanguage)treeMap.getValue();
if (null != sql) {
sqlBuilder.append(sql);
}
}
return sqlBuilder.toString();
}
}