日志(logImpl)
StdOutImpl
<setting name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
Slf4jImpl
<setting name="logImpl" value="org.apache.ibatis.logging.slf4j.Slf4jImpl"/>
引入maven文件
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>2.0.12</version>
</dependency>
编写配置文件
org.slf4j.simpleLogger.defaultLogLevel=debug
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
org.slf4j.simpleLogger.levelInBrackets=true
org.slf4j.simpleLogger.logFile=System.out
执行器
默认执行器(SimpleExecutor)
try(SqlSession sqlSession = sqlSessionFactory.openSession()) {// 使用SqlSession获取映射器实例FullCityMapper mapper = sqlSession.getMapper(FullCityMapper.class);// 使用映射器执行操作FullCity fullCity = mapper.selectByName("广东省");System.out.println("城市的名称:"+fullCity.getName());
}
重用执行器(ReuseExecutor)
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) {// 使用SqlSession获取映射器实例FullCityMapper mapper = sqlSession.getMapper(FullCityMapper.class);// 使用映射器执行操作FullCity fullCity = mapper.selectByName("广东省");System.out.println("城市的名称:"+fullCity.getName());
}
批量执行器(BatchExecutor)
try(SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {// 使用SqlSession获取映射器实例FullCityMapper mapper = sqlSession.getMapper(FullCityMapper.class);// 使用映射器执行操作FullCity fullCity = mapper.selectByName("广东省");System.out.println("城市的名称:"+fullCity.getName());}
起别名
配置文件
<typeAliases>
<!-- <typeAlias type="com.wyl.mybatis.entity.FullCity"/>--><package name="com.wyl.mybatis.entity"/></typeAliases>
mapper文件
<select id="selectByName" resultType="FullCity">select * from d_full_city where name = #{name}</select>
插件
注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {/*** Returns method signatures to intercept.** @return method signatures*/Signature[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {// Executor.classClass<?> type();// updateString method();// MappedStatement.class, Object.classClass<?>[] args();
}
主要用到了上面两个注解:@Intercepts和@Signature
方法名和参数的定义如下:
时间插件
作用:打印SQL语句执行的时间,分析慢查询原因(一般针对查询query来说)
package com.wyl.mybatis.intercept;import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;/*** @Description 时间插件* @Author WuYiLong* @Date 2024/2/29 9:51*/
@Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})
public class TimeIntercept implements Interceptor {private final static Logger log = LoggerFactory.getLogger(TimeIntercept.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement)args[0];MapperMethod.ParamMap pm = ( MapperMethod.ParamMap) args[1];Map<String, Object> paramsMap = new HashMap<>();pm.forEach((k,v)->{String key = String.valueOf(k);if(!key.contains("param")) {paramsMap.put(String.valueOf(k),v);}});BoundSql boundSql = ms.getBoundSql(ms.getParameterMap());// sql执行之前long startTime = System.currentTimeMillis();Object proceed = invocation.proceed();JSONArray jsonArray = JSONUtil.parseArray(proceed);long endTime = System.currentTimeMillis();log.info("----sql执行语句: {}",boundSql.getSql());log.info("----sql输入参数: {}", JSONUtil.toJsonStr(paramsMap));log.info("----sql输出结果数: {}", jsonArray.size());log.info("----sql执行花费时间: {}", (endTime-startTime)/1000);// sql执行之后return proceed;}
}
<plugins><plugin interceptor="com.wyl.mybatis.intercept.TimeIntercept"></plugin></plugins>
分页插件
FullCityMapper mapper = sqlSession.getMapper(FullCityMapper.class);
for (int i = 1; i <= 3 ; i++) {Page<FullCity> page = new Page<>(i, 10);List<FullCity> fullCities = mapper.selectFullCityPage(page);log.info("当前页:{}",page.getCurrentPage());log.info("页数:{}",page.getPageSize());log.info("总数:{}",page.getTotal());log.info("列表:{}", JSONUtil.toJsonStr(fullCities));
}
作用:mysql数据库物理分页,简化分页流程
package com.wyl.mybatis.page;import java.util.List;/*** @Description* @Author WuYiLong* @Date 2024/3/13 13:48*/
public class Page<T> {private Integer currentPage = 1;private Integer pageSize = 10;private Integer total;private List<T> records;public Page(Integer currentPage,Integer pageSize) {this.currentPage = currentPage;this.pageSize = pageSize;}public Page(){};public Integer getCurrentPage() {return currentPage;}public void setCurrentPage(Integer currentPage) {this.currentPage = currentPage;}public Integer getPageSize() {return pageSize;}public void setPageSize(Integer pageSize) {this.pageSize = pageSize;}public Integer getTotal() {return total;}public void setTotal(Integer total) {this.total = total;}public List<T> getRecords() {return records;}public void setRecords(List<T> records) {this.records = records;}
}
/*** 分页* @param page* @return*/@Select("select * from d_full_city")List<FullCity> selectFullCityPage(@Param("page") Page page);
package com.wyl.mybatis.intercept;import cn.hutool.db.sql.SqlBuilder;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.wyl.mybatis.config.SqlSessionFactoryConfig;
import com.wyl.mybatis.page.Page;
import com.wyl.mybatis.util.SqlParamUtil;
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 org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Map;/*** @Description* @Author WuYiLong* @Date 2024/3/13 12:06*/
@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 PageIntercept implements Interceptor {private final static Logger log = LoggerFactory.getLogger(PageIntercept.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {Executor executor = (Executor) invocation.getTarget();Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object params = args[1];RowBounds rowBounds = (RowBounds) args[2];BoundSql boundSql = ms.getBoundSql(params);String sql = boundSql.getSql();ResultHandler resultHandler = (ResultHandler) args[3];Page page = null;Map<String, Object> paramMap = SqlParamUtil.filter(params);for (Map.Entry<String, Object> mapEntry : paramMap.entrySet()) {Object v = mapEntry.getValue();if (v instanceof Page) {page = (Page) v;}}if(page != null) {String countSql = countSql(sql);int count = 0;SqlSessionFactory sqlSessionFactory = SqlSessionFactoryConfig.buildSqlSessionFactory();try (SqlSession sqlSession = sqlSessionFactory.openSession()) {Connection connection = sqlSession.getConnection();PreparedStatement preparedStatement = connection.prepareStatement(countSql);ResultSet resultSet = preparedStatement.executeQuery();while (resultSet.next()) {count = resultSet.getInt("count");}}page.setTotal(count);rowBounds = new RowBounds(page.getCurrentPage()-1,page.getPageSize());}CacheKey cacheKey;if (args.length == 4) {cacheKey = executor.createCacheKey(ms, params, rowBounds, boundSql);} else {cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}List<Object> query = executor.query(ms, params, rowBounds, resultHandler, cacheKey, boundSql);return query;}@Overridepublic Object plugin(Object target) {if (target instanceof Executor) {return Plugin.wrap(target, this);}return target;}private String countSql(String sql) {SqlBuilder sqlBuilder = new SqlBuilder();sqlBuilder.select("count(*) count");List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql);SQLSelectStatement sqlStatement = (SQLSelectStatement) sqlStatements.get(0);MySqlSelectQueryBlock queryBlock = (MySqlSelectQueryBlock) sqlStatement.getSelect().getQueryBlock();sqlBuilder.from(queryBlock.getFrom().toString());if (queryBlock.getWhere() != null) {sqlBuilder.where(queryBlock.getWhere().toString());}return sqlBuilder.build();}
}
<plugins><plugin interceptor="com.wyl.mybatis.intercept.PageIntercept"></plugin></plugins>
这里需要注意的是CacheKey缓存key,因为sql的变化直接影响了查询的输出,从上面可以看出分页参数,是不需要用户输入的,通过内置分页插件即可完成分页,所以说sql实质上是没有发生变化的,从而导致重新生成的缓存key都是一样的,如源码所示:
@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {if (closed) {throw new ExecutorException("Executor was closed.");}CacheKey cacheKey = new CacheKey();cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();// mimic DefaultParameterHandler logicfor (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}}if (configuration.getEnvironment() != null) {// issue #176cacheKey.update(configuration.getEnvironment().getId());}return cacheKey;}
看到了cacheKey的update方法,所以我们只需要每次请求改变其中之一即可,很明显,我们改变下RowBounds对象的参数就好,这个对象也是控制行数的,从它的名字就可以直接看出来。
项目地址
demo地址