MyBatis架构图
程序启动时 org.mybatis.spring.SqlSessionFactoryBean 会通过 org.apache.ibatis.builder.xml.XMLMapperBuilder 解析Mapper XML 文件,生成 MappedStatement 对象 并存放在 org.apache.ibatis.session.Configuration#mappedStatements (类型为 Map<String, MappedStatement>)
MappedStatement
MappedStatement 属性中包含了 ParameterMap 与 List<ResultMap>, 前者是处理预编译SQL,即查询参数的数据类型。后者是为查询结果转换数据类型使用的。且两者都会结合TypeHandler 对数据进行再加工。
ParameterHandler & BaseTypeHandler#setParameter & JdbcType
Mybatis的执行流程 如上调用栈,其通过 MapperProxy 代理 获取到 SqlSession 经 Executor 创建 StatementHandler ,StatementHandler 完善预编译SQL (PreparedStatement),ParameterHandler(调用栈的第三行) 在此期间完成对参数的设置,如果没 ParameterMap 没有指定 TypeHandler 则会匹配 UnknownTypeHandler 通过 JdbcType 尝试查找出一个 TypeHandler 为其设置值.
JdbcType 官方解释
在这个表格之后的所支持的 JDBC 类型列表中的类型。JDBC 类型是仅 仅需要对插入,更新和删除操作可能为空的列进行处理。这是 JDBC jdbcType 的需要,而不是 MyBatis 的。如果你直接使用 JDBC 编程,你需要指定 这个类型-但仅仅对可能为空的值。
ResultSetHandler & JavaType
转换结果的调用栈,经过StatementHandler, 进入 ResultHandler, 由 ResultMap 的 propertyMapping 确定 TypeHandler, 最后进行结果转换
JavaType 会在解析 MapperStatement 的时候根据编写的 JavaType 去设置合适的 TypeHandler
JavaType 官方解释
一个 Java 类的完全限定名,或一个类型别名(参考上面内建类型别名 的列表) 。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。 然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证所需的行为。
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);
CBC模式加密
public static void main(String[] args) throws Exception {
// 加密密钥 以及 Iv 算法参数 的 16进制表示方式
String keyStr = "1234567890123456";
String inputStr = "PassWord";
// CBC 需要结合 Iv 算法
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(keyStr.getBytes(StandardCharsets.UTF_8), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(keyStr.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] bytes = cipher.doFinal(inputStr.getBytes(StandardCharsets.UTF_8));
// 16进制表示
String result = Hex.encodeHexString(bytes);
System.out.println("加密结果:" + result);
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
byte[] bytes1 = cipher.doFinal(Hex.decodeHex(result));
System.out.println("解密结果:" + new String(bytes1));
/*-- https://dev.mysql.com/doc/refman/8.0/en/encryption-functions.html#function_aes-encrypt
-- mysql AES 默认使用 aes-128-ebc, 如果要设置 iv 向量则需要修改默认值
SET block_encryption_mode = 'aes-128-cbc';
SELECT HEX(AES_ENCRYPT('PassWord', '1234567890123456', '1234567890123456'));*/
}
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 插件开发
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从入门到精通