一、简介
MyBatis
允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor
执行增删改查操作ParameterHandler
设置预编译参数用的ResultSetHandler
处理结果集StatementHandler
处理SQL预编译,设置参数等相关工作
允许使用插件来拦截的四大对象在MyBatis的执行流程如下图所示:
官方文档 https://mybatis.org/mybatis-3/zh/configuration.html#plugins
二、加载
Executor 拦截器加载
流程
- 在解析Mybaits配置时,会将配置中所有拦截器信息存储到
configuration
的拦截器链(interceptorChain
)对象的一个List里面。 - 配置解析完毕,创建会话
SqlSession
时,会话的默认实现类DefaultSqlSession
有执行器executor
变量和运行时配置configuration
变量,在实例化时会给executor
和configuration
赋值。 configuration
在配置解析时已实例化完毕,通过configuration
的newExecutor
方法可获取配置的执行器,在此方法中获取到执行器之后,会调用拦截器链的pluginAll
方法,遍历所有拦截器,通过JDK动态代理对执行器进行循环增强。
由上可知,在创建执行器Executor对象时,通过JDK动态代理方式使用拦截器对执行器进行循环增强,其他三大核心对象也是在创建时加载。
三、准备工作
1.mybatis-config.xml
,用于连接数据库,以及关联我们的代码
<?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>
<plugins>
<plugin interceptor="com.study.mybatis.plugin.SelfPageInterceptor">
</plugin>
</plugins>
<environments default="mysql">
<environment id="mysql">
<!--配置事务的类型,使用本地事务策略-->
<transactionManager type="JDBC"></transactionManager>
<!--是否使用连接池 POOLED表示使用链接池,UNPOOLED表示不使用连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/step"/>
<property name="username" value="root"/>
<property name="password" value="2020"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</mappers>
</configuration>
2.pom.xml
文件准备
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitledpage</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--mybatis核心包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
四、分页代码
1.实体类User
package com.study.mybatis.entity;
/**
* @author step
* @date 2021年08月08日 11:51
*/
public class User {
private int id;
private String name;
//get,set 方法
//toString 方法
2.SelfPage
类
package com.study.mybatis.plugin;
/**
* 自定义分页对象
*/
public class SelfPage {
private int pageNo;
private int pageSize;
private int offset;
public int getOffset() {
this.offset = this.pageNo > 0 ? (this.pageNo - 1) * this.pageSize : 0;
return offset;
}
public SelfPage(int pageNo, int pageSize) {
this.pageNo = pageNo;
this.pageSize = pageSize;
}
//PageNo,getPageSize的get,set,toString方法
}
3.自动义分页辅助类SelfPageHelper
package com.study.mybatis.plugin;
/**
* @className: SelfPageHelper
* @description: 自定义分页辅助类
* @author: step
**/
public class SelfPageHelper {
private static final ThreadLocal<SelfPage> LOCAL_PAGE = new ThreadLocal<SelfPage>();
public static void startPage(int pageNo, int pageSize) {
SelfPage page = new SelfPage(pageNo, pageSize);
LOCAL_PAGE.set(page);
}
public static void removePage() {
LOCAL_PAGE.remove();
}
public static SelfPage getPage() {
return LOCAL_PAGE.get();
}
}
4.自定义分页插件SelfPageInterceptor
package com.study.mybatis.plugin;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
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;
/**
* @className: SelfPageInterceptor
* @description: 自定义分页插件
* @author: step
**/
@Intercepts(
{
@Signature(type = Executor.class,method ="query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class,method ="query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
}
)
public class SelfPageInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
try {
// 从 Invocation 中获取参数
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
// 获取分页参数
SelfPage page = SelfPageHelper.getPage();
if (page != null) {
boundSql = ms.getBoundSql(parameter);
String pageSql = getPageSql(boundSql,page);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler,cacheKey,pageBoundSql);
}
return invocation.proceed();
}finally {
SelfPageHelper.removePage();
}
}
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
public void setProperties(Properties properties) {
}
/**
* @param boundSql
* @param page
*/
private String getPageSql(BoundSql boundSql,SelfPage page){
String sql = boundSql.getSql();
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if(page.getOffset() > 0){
sqlBuilder.append("\n LIMIT ")
.append(page.getOffset()).append(",").append(page.getPageSize());
}else{
sqlBuilder.append("\n LIMIT ").append(page.getPageSize());
}
return sqlBuilder.toString();
}
}
5.UserMapper
package com.study.mybatis.mapper;
import com.study.mybatis.entity.User;
import java.util.List;
/**
* @author step
* @date 2021年08月08日 11:48
*/
public interface UserMapper {
List<User> selectAll();
6.mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.study.mybatis.mapper.UserMapper">
<resultMap id="resultListUser" type="com.study.mybatis.entity.User">
<id column="id" property="id" />
<result column="name" property="name"/>
</resultMap>
<select id="selectAll" resultType="com.study.mybatis.entity.User">
select * from user
</select>
</mapper>
6.测试类
package com.study;
import com.study.mybatis.entity.User;
import com.study.mybatis.mapper.UserMapper;
import com.study.mybatis.plugin.SelfPageHelper;
//import com.study.mybatis.plugin.StepPagePlugin;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @author step
* @date 2021年08月08日 16:37
*/
public class Teest {
@Test
public void study() throws IOException {
//加载主配置文件,目的是为了构建SqlSessionFactory对象
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//通过SqlSessionFactory工厂对象创建SqlSesssion对象
SqlSession session = factory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
SelfPageHelper.startPage(1,3);
//调用查询所有的方法
List<User> list = mapper.selectAll();
//遍历集合
for (User user : list) {
System.out.println(user);
}
//释放资源
session.close();
in.close();
}
}
五、日志代码
1.mybatis-config.xml
中加入
<plugin interceptor="com.study.mybatis.plugin.SqlLogInterceptor">
</plugin>
2.SqlLogInterceptor
package com.study.mybatis.plugin;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
/**
* 类名称:SqlLogInterceptor<br>
* 类描述:<br>
* @author step
* @date 2021年08月09日 16:37
*/
@Intercepts({
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
public class SqlLogInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
//获取sql语句
String sql =showSql(configuration, boundSql);
//获取数据源
PooledDataSource db = (PooledDataSource) configuration.getEnvironment().getDataSource();
//sql=sql.replaceAll("\'","").replace("\"", "");
Object result = invocation.proceed();//proceed只能使用一次,proceed是反射调用原方法,不能直接作为非事物方法多次调用
// LOGGER.info("url: {} , sql: {} ,result: {}",db.getUrl(),sql,result.toString());
System.out.println("url:"+ db.getUrl());
System.out.println("sql: "+sql);
System.out.println("result: "+result.toString());
//执行结果
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
public void setProperties(Properties properties) {
}
private String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(obj) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
public String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
}