TypeHandler
MyBatis已经实现的TypeHandler
注册
mybatis默认定义了一批TypeHandler,正常情况下这些TypeHandler就可以满足我们的使用了.mybatis通过TypeHandlerRegister来管理TypeHandler
配置
一种是通过typeHandler 标签(先),
一种通过package标签(后)。package标签没有了JavaType与jdbcType属性。但1.可以在TypeHandler的实现类上标注注释即可确定@MappedJdbcTypes(JdbcType.VARCHAR):要处理的jdbc类型、 @MappedTypes(Encrypt.class) :要处理的Java类型。2.在Mapper.xml 的 resultMap标签下 result 标签内配置typehander属性
<typeHandlers>
<!-- <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="tk.mybatis.simple.type.Enable"/> -->
<typeHandler handler="tk.mybatis.simple.type.EnableTypeHandler" javaType="tk.mybatis.simple.type.Enable"/>
<!-- <typeHandler handler="tk.mybatis.simple.type.EncryptStringTypeHandler"/>-->
<!-- <typeHandler handler="tk.mybatis.simple.type.EncryptTypeHandler"/>-->
<!--使用该注解可以指定(package 应放在 typeHandler的后面)-->
<package name="tk.mybatis.simple.type"/>
</typeHandlers>
使用案例 EnumOrdinalTypeHandler
SQL
create table sys_role
(
id bigint auto_increment comment '角色ID'
primary key,
role_name varchar(50) null comment '角色名',
enabled int null comment '有效标志',
create_by bigint null comment '创建人',
create_time datetime null comment '创建时间'
)
comment '角色表' charset = utf8;
枚举类
public enum EnumOrdinal {
disable, // 0
enable; // 1
}
实体类
/**
* 有效标志
*/
private EnumOrdinal enabled;
public EnumOrdinal getEnabled() {
return enabled;
}
public void setEnabled(EnumOrdinal enabled) {
this.enabled = enabled;
}
mybatis-config.xml
<typeHandlers>
<!-- 因为EnumOrdinalTypeHandler类未标明注解@MappedTypes
所以这里一定要配置javaType ,否则TypeHandlerRegistry.register()会提示 Unable to find a usable constructor for ***-->
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="tk.mybatis.simple.type.EnumOrdinal"/>
</typeHandlers>
执行效果
内置枚举 TypeHandler 的不足与补充
mybatis 提供的Enum有许多局限性 Enum(String name, int ordinal) 这是因为 EnumOrdinal-TypeHandler 源码三个地方限制了TypeHandler 的类型。我们可以通过改造 构造方法、设置值方法(setNonNullParameter)、获取值方法(getNullableResult的三个重载方法)
通用枚举转换的实现 BaseTypeHandler、@MappedTypes背后的执行逻辑-ibatis
http://t.csdn.cn/kXXeI
TypeHandler的拓展
实现数据加密解密
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import java.nio.charset.StandardCharsets;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
// 识别不了嵌套的association; BaseTypeHandler 与 TypeHandler 的区别 :BaseTypeHandler配置的时候不需要指定 javaType
// 加密思路就是 字符串(包括中文)不能直接使用AES算法解密,因为可能出现中文乱码的情况实现先转为 HEX 再进行AES_ENCRYPT
// 解密则是将顺序倒置
// 此方法不能进行模糊查询
public class EncryptStringTypeHandler extends BaseTypeHandler<String> {
private static final byte[] KEYS = "12345678abcdefgh".getBytes(StandardCharsets.UTF_8);
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null){
ps.setString(i,null);
}
AES aes = SecureUtil.aes(KEYS);
String s = aes.encryptHex(parameter);
ps.setString(i,s);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String string = rs.getString(columnName);
return SecureUtil.aes(KEYS).decryptStr(string);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String string = rs.getString(columnIndex);
return SecureUtil.aes(KEYS).decryptStr(string);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String string = cs.getString(columnIndex);
return SecureUtil.aes(KEYS).decryptStr(string);
}
}
简单实现MySQL加密数据模糊查询
create table user_info
(
id varchar(64) not null
primary key,
name_decrypt varchar(64) null comment '加密后的名字',
user_name varchar(64) null comment '加密前的名字'
)
charset = utf8;
-- 添加数据
INTO mybatis.user_info (id, name_decrypt, user_name) VALUES ('2', 'AEF5650465C8A91F11086D72A6C54039', 'Jay');
-- 模糊查询
SELECT * FROM user_info WHERE AES_DECRYPT(UNHEX(name_decrypt),'1024') LIKE CONCAT('%','a','%') LIMIT 1;
Java工具类
import org.apache.commons.codec.binary.Hex;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
/*<dependency>
<groupId>org.apache.directory.studio</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>*/
/**
* @author Jay
*/
public class AESUtil {
/**
* secretKey 的最长长度
*/
public static final int LENGTH = 16;
/**
* 编码格式
*/
private static final String CHARSET_NAME = "UTF-8";
/**
* 算法/模式/填充
*/
public static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
public static final String ALGORITHM = TRANSFORMATION.split("/")[0];
/**
* 加密的Key-可自行设置 长度应小于16
*/
private static final String AES_KEY = "1024";
public static void main(String[] args) throws Exception {
String str = "Jay";
String byteStr = "AEF5650465C8A91F11086D72A6C54039";
System.out.println("要加密的数据:" + str + ",加密后:" + encryptThenToHex(str));
System.out.println("要解密的数据:" + byteStr + ",解密后:" + hexThenDecrypt(byteStr));
// SELECT HEX(AES_ENCRYPT('Jay','1024')) FROM DUAL;
// SELECT AES_DECRYPT(UNHEX('AEF5650465C8A91F11086D72A6C54039'),'1024') FROM DUAL;
}
/**
* AES 加密
*/
public static String encrypt(String parameter) {
try {
return hexThenDecrypt(parameter);
} catch (Exception e) {
System.out.println("AES加密失败");
return parameter;
}
}
/**
* AES 解密
*/
public static String decrypt(String parameter) {
try {
return encryptThenToHex(parameter);
} catch (Exception e) {
System.out.println("AES解密失败");
return parameter;
}
}
/**
* 字符串经 AES 加密后用 Hex encodeHex
*
* @param str 要加密的字符串
* @return char[]转 String后的数据
*/
private static String encryptThenToHex(String str) throws Exception {
final Cipher encryptCipher = Cipher.getInstance(TRANSFORMATION);
encryptCipher.init(Cipher.ENCRYPT_MODE, generateKeyStr());
/* SELECT HEX(AES_ENCRYPT( str , key_str )) FROM DUAL; -- 加密
AES_ENCRYPT(str,key_str[,init_vector][,kdf_name][,salt][,info | iterations])
HEX( str )
*/
char[] bytes = Hex.encodeHex(encryptCipher.doFinal(str.getBytes(CHARSET_NAME)));
return new String(bytes).toUpperCase(Locale.ROOT);
}
/**
* 字符串经Hex decode 再 AES解密
*
* @param byteStr 待解密的数据
* @return 解密字符串-result
* @throws Exception 传递篡改后的字符串可能会解析失败,自行捕获异常
*/
private static String hexThenDecrypt(String byteStr) throws Exception {
final Cipher decryptCipher = Cipher.getInstance(TRANSFORMATION);
decryptCipher.init(Cipher.DECRYPT_MODE, generateKeyStr());
/* SELECT AES_DECRYPT(UNHEX( str ), key_str ) FROM DUAL;
UNHEX(str)
AES_ENCRYPT(str,key_str[,init_vector][,kdf_name][,salt][,info | iterations]) */
byte[] bytes;
try {
bytes = decryptCipher.doFinal(Hex.decodeHex(byteStr.toCharArray()));
} catch (Exception e) {
System.out.println(" - Error decrypting: " + e.getMessage());
return "";
}
return new String(bytes);
}
/**
* 模拟生成 MySQL 密钥字符串key_str,并生成密钥 <br/>
*
* @return SecretKeySpec 对称密钥(SecretKey)
*/
private static SecretKeySpec generateKeyStr() {
if (AESUtil.AES_KEY.length() > LENGTH) {
throw new RuntimeException("Specify a key of length less than 16.");
}
try {
final byte[] secretKey = new byte[LENGTH];
int i = 0;
for (byte b : AESUtil.AES_KEY.getBytes(CHARSET_NAME)) {
secretKey[i % LENGTH] = b;
i++;
}
// 通过 key 生成的 secretKey 如果 key 长度小于16 则会使用 0 填充, 如果 key 长度大于16 则会再从前往后覆盖设置
return new SecretKeySpec(secretKey, ALGORITHM);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Failed to generate a key.", e);
}
}
}
TypeHandler
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import tk.mybatis.simple.util.AESUtil;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 泛型 String 可以配置成 其他的自定义类型
* @author Jay
*/
public class AESTypeHandler extends BaseTypeHandler<String> {
public AESTypeHandler() {
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
try {
ps.setString(i, AESUtil.encrypt(parameter));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String value = rs.getString(columnName);
return value == null ? null : AESUtil.decrypt(value);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String value = rs.getString(columnIndex);
return value == null ? null : AESUtil.decrypt(value);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String value = cs.getString(columnIndex);
return value == null ? null : AESUtil.decrypt(value);
}
}
使用方法
@MapKey("id")
List<Map> getUserNameByDecrypt(@Param("decrypt") String decrypt);
<select id="getUserNameByDecrypt" resultType="map">
SELECT * FROM user_info WHERE user_name = #{decrypt,typeHandler=tk.mybatis.simple.plugin.typehandler.AESTypeHandler}
</select>
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
List<Map> j = userInfoMapper.getUserNameByDecrypt("AEF5650465C8A91F11086D72A6C54039");
System.out.println(j);
MyBatis缓存配置
一级缓存
@Test
public void testL1Cache() {
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
SysUser user1;
try {
//获取 SysUserMapper 接口
SysUserMapper SysUserMapper = sqlSession.getMapper(SysUserMapper.class);
//调用 selectByPrimaryKey 方法,查询 id = 1 的用户
user1 = SysUserMapper.selectByPrimaryKey(1L);
//对当前获取的对象重新赋值
user1.setUserName("New Name");
//再次查询获取 id 相同的用户
SysUser user2 = SysUserMapper.selectByPrimaryKey(1L);
//虽然我们没有更新数据库,但是这个用户名和我们 user1 重新赋值的名字相同了
Assert.assertEquals("New Name", user2.getUserName());
//不仅如何,user2 和 user1 完全就是同一个实例
Assert.assertEquals(user1, user2);
} finally {
//关闭当前的 sqlSession
sqlSession.close();
}
System.out.println("开启新的 sqlSession");
//开始另一个新的 session
sqlSession = getSqlSession();
try {
//获取 SysUserMapper 接口
SysUserMapper SysUserMapper = sqlSession.getMapper(SysUserMapper.class);
//调用 selectByPrimaryKey 方法,查询 id = 1 的用户
SysUser user2 = SysUserMapper.selectByPrimaryKey(1L);
//第二个 session 获取的用户名仍然是 admin
Assert.assertNotEquals("New Name", user2.getUserName());
//这里的 user2 和 前一个 session 查询的结果是两个不同的实例
Assert.assertNotEquals(user1, user2);
//执行删除操作 (执行了任何的“增删改”操作,无论这些“增删改”操作是否影响到了缓存的数据)
SysUserMapper.deleteByPrimaryKey(2L);
//获取 user3
SysUser user3 = SysUserMapper.selectByPrimaryKey(1L);
//这里的 user2 和 user3 是两个不同的实例
Assert.assertNotEquals(user2, user3);
} finally {
//关闭 sqlSession
sqlSession.close();
}
}
解决思路
<select id="selectRolesByUserId" flushCache="true" resultType="tk.mybatis.simple.model.SysRole">
二级缓存
<!--org.apache.ibatis.session.Configuration-->
<settings>
<!--二级缓存在SqlSessionFactory 默认是打开的-->
<setting name="cacheEnabled" value="true"/>
</settings>
读取命名空间的配置
<cache type="org.mybatis.caches.redis.RedisCache"
eviction="LRU" flushInterval="0" size="0" readOnly="false" blocking="false" />
org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
分布式系统缓存&自定义缓存
自定义缓存会使得部分参数不可用
cache type="cache.RedisCache" size="不可用" flushInterval="不可用" eviction="不可以" readOnly="不可用" blocking="不可用"/>
查询语句标注了 useCache="false" flushCache="true" 使用了 ResultHandler 的情况需要注意
/**
* 参照 org.apache.ibatis.cache.impl.ScheduledCache、 PerpetualCache<br/>
* 间隔 60s 刷新一次,使用自定义的 Cache 不支持解析参数
* <cache type="cache.RedisCache" size="" flushInterval="" eviction="" readOnly="" blocking=""/><br/>
* org.apache.ibatis.mapping.CacheBuilder#build() L92
*
* @author Jay
*/
public class RedisCache implements Cache {
/**
* 当前 Mapper的命名空间
*/
private final String id;
/**
* 清除 field 的时间间隔
*/
private long clearInterval = 60 * 1000;
/**
* 最后一次清除的时间
*/
private long lastClear;
private HadesClient hadesClient;
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
this.lastClear = System.currentTimeMillis();
}
@Override
public String getId() {
return this.id;
}
@Override
public synchronized void putObject(Object key, Object value) {
clearWhenStale();
if (ObjectUtils.isEmpty(key) || ObjectUtils.isEmpty(value)) {
return;
}
hadesClient.hset(id.getBytes(StandardCharsets.UTF_8),
key.toString().getBytes(StandardCharsets.UTF_8),
SerializeUtil.serialize(value));
}
@Override
public synchronized Object getObject(Object key) {
if (clearWhenStale()) {
return null;
}
byte[] bytes = hadesClient.hget(id.getBytes(StandardCharsets.UTF_8), key.toString().getBytes(StandardCharsets.UTF_8));
return SerializeUtil.unSerialize(bytes);
}
@Override
public synchronized Object removeObject(Object key) {
clearWhenStale();
return hadesClient.hdel(id.getBytes(StandardCharsets.UTF_8), key.toString().getBytes(StandardCharsets.UTF_8));
}
@Override
public synchronized void clear() {
getIfNecessary();
lastClear = System.currentTimeMillis();
// 删除过期的缓存
hadesClient.del(id.getBytes(StandardCharsets.UTF_8));
}
@Override
public int getSize() {
clearWhenStale();
return hadesClient.hkeys(id.getBytes(StandardCharsets.UTF_8)).size();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
/**
* 检查 hadesClient
*/
private void getIfNecessary() {
if (hadesClient == null) {
this.hadesClient = SpringContextUtil.getBean(HadesClient.class);
}
}
/**
* 过期时清除
* @return
*/
private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
}
二级缓存适用场景
Mybatis 默认实现了哪些缓存,都可以设置什么参数

MyBatis配置文件哪些属性由谁加载
org.apache.ibatis.session.Configuration
MyBatis 插件开发
MyBatis 的执行流程
My Batis 允许在己映射语句执行过程中的某一点进行拦截调用。默认情况下, MyBatis 允许 使用插件来拦截的接口和方法包括以下几个。

Executor
拦截执行器的方法
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
public abstract class MyExecutor implements Executor {
/**
* 该方法会在所有的 INSERT UPDATE DELET 执行时被调用,
*/
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
return 0;
}
/**
* 该方法不可被拦截
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException {
return null;
}
/**
* 该方法会在所有 SELECT 查询方法执行时被调用
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
return null;
}
/**
* 该方法只有在查询 的返回值类型为 Cursor 时被调用
*/
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
return null;
}
/**
* 该方法只在通过 SqlSession 方法调用 flushStatements 方法或执行的接口方法中带有@Flush 注解时才被调用
*/
@Override
public List<BatchResult> flushStatements() throws SQLException {
return null;
}
/**
* 只在通过 SqlSession 方法调用 commit 方法时才被调用
*/
@Override
public void commit(boolean required) throws SQLException {
}
/**
* 过 SqlSession口方法调用 rollback 方法时才被调用
*/
@Override
public void rollback(boolean required) throws SQLException {
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return null;
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return false;
}
@Override
public void clearLocalCache() {
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
}
/**
* 在通过 SqlSession 方法获取数据库连接时才被调用,
*/
@Override
public Transaction getTransaction() {
return null;
}
/**
* 只在延迟加载获取新的 Executor 后才会被执行
*/
@Override
public void close(boolean forceRollback) {
}
/**
* 该方法只在延迟加载执行查询方法前被执行
*/
@Override
public boolean isClosed() {
return false;
}
@Override
public void setExecutorWrapper(Executor executor) {
}
}
ParameterHandler
拦截参数的处理
import org.apache.ibatis.executor.parameter.ParameterHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public abstract class MyParameterHandler implements ParameterHandler {
/**
* 法只在执行存储过程处理出参的时候被调用。
*/
@Override
public Object getParameterObject() {
return null;
}
/**
* 所有数据库方法设置 SQL 参数时被调用。
*/
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
}
}
ResultSetHandler
拦截结果集的处理
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import java.sql.CallableStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
public abstract class MyResultSetHandler implements ResultSetHandler {
/**
* 在 除 存储过程及返回值类型为 Cursor<T> 的查询方法中被调用(3.4.0 版本中新增)
*/
@Override
public <E> List<E> handleResultSets(Statement stmt) throws SQLException {
return null;
}
@Override
public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
return null;
}
/**
* 只在使用存储过程处理出参时被调用
*/
@Override
public void handleOutputParameters(CallableStatement cs) throws SQLException {
}
}
StatementHandler
拦截Sql语法构建的处理
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
public abstract class MyStatementHandler implements StatementHandler {
/**
* 该方法会在数据库执行前被调用 优先于当前接口中的其他方法而被执行
*/
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return null;
}
/**
* 该方法在 prepare 方法之后执行,用于处理参数信息
*/
@Override
public void parameterize(Statement statement) throws SQLException {
}
/**
* 在全局设置配置 defaultExecutorType BATCH 时,执行数据操作才会调用该方
*/
@Override
public void batch(Statement statement) throws SQLException {
}
@Override
public int update(Statement statement) throws SQLException {
return 0;
}
/**
* 执行 SELECT 方法时调用
*/
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return null;
}
/**
* 只会在返回值类型为 Cursor<T >的查询中被调用
*/
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return null;
}
@Override
public BoundSql getBoundSql() {
return null;
}
@Override
public ParameterHandler getParameterHandler() {
return null;
}
}
拦截器接口
package org.apache.ibatis.plugin;
import java.util.Properties;
public interface Interceptor {
// 要执行的拦截方法
Object intercept(Invocation invocation) throws Throwable;
// 在创建被拦截的接口实现类时被调用 主要的方法实现是 return Plugin.wrap(target, this) ;
Object plugin(Object target);
// 传递插件的参数,可以通过参数来改变插件的行为。
void setProperties(Properties properties);
}
拦截顺序
拦截器签名
可以仅配置一个 @Signature;或者是一个 Signature集合 (@Intercepts({@Signature(***), @Signature(***), ........}))
实现案例
对返回结果为Map类型的进行小驼峰转换
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* 就是循环判断结果。如果是 Map 类型的结果,就对 Map
* 进行处理 处理时为了避免把己经是驼峰的值转换为纯小写,因此通过首字母是否为大写或是
* 否包含下画线来判断(实际应用中要根据实际情况修改)。如果符合其中 个条件就转换为驼峰
* 形式,删除对应的 key ,使用新的 key 来代替 。
*/
@SuppressWarnings({"unchecked","rawtypes"})
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}))
public class CameHumpInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 先执行结果,再对结果进行处理
List<Object> result = (List<Object>) invocation.proceed();
for (Object object : result) {
// 如果结果是 Map 类型的,就对 Map 的 key 进行转换
if (object instanceof Map)
processMap((Map) object);
else
break;
}
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/**
* 处理 Map 类型
*
* @param map
*/
private void processMap(Map<String, Object> map) {
HashSet<String> keySet = new HashSet<>(map.keySet());
for (String key : keySet) {
// 将以大写开头的字符串转换为小写,如果包含下划线也会处理为驼峰
// 此处只通过这两个简单的标识来判断是否进行转换
if (key.charAt(0) >= 'A' && key.charAt(0) <= 'Z' && key.contains("_")) {
Object value = map.get(key);
map.remove(key);
map.put(underlineToCamelHump(key), value);
}
}
}
/**
* 将下划线风格替换为驼峰风格
*/
private String underlineToCamelHump(String key) {
StringBuilder stringBuilder = new StringBuilder();
boolean nextUpperCase = false;
for (int i = 0; i < key.length(); i++) {
char c = key.charAt(i);
if (c == '_') {
if (stringBuilder.length() > 0)
nextUpperCase = true;
} else {
if (nextUpperCase) {
stringBuilder.append(Character.toUpperCase(c));
nextUpperCase = false;
} else {
stringBuilder.append(Character.toLowerCase(c));
}
}
}
return stringBuilder.toString();
}
}
Mybatis_PageHelper: Mybatis分页插件 (gitee.com)
文章参考: MyBatis从入门到精通