源码分析完了,其实还有很多附加的插件没有分析了,因为这些给开发者实现用的,所以还是先学会用再看源码了。
今天主要两个点:
- TypeHandler入参出参实现使用
- Interceptor插件实现使用
1. TypeHandler
顾名思义,类型处理器,看下该接口的类
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
如果不想关心出参和入参数据的判断,可以直接使用BaseTypeHandler
1.1 为什么要有类型处理器
因为mysql是对N种开发语言的,mysql中的类型需要转义到对应语言的实现,我们来看下java中是怎么映射的,ArrayTypeHandler 类中
STANDARD_MAPPING = new ConcurrentHashMap<>();
STANDARD_MAPPING.put(BigDecimal.class, JdbcType.NUMERIC.name());
STANDARD_MAPPING.put(BigInteger.class, JdbcType.BIGINT.name());
STANDARD_MAPPING.put(boolean.class, JdbcType.BOOLEAN.name());
STANDARD_MAPPING.put(Boolean.class, JdbcType.BOOLEAN.name());
STANDARD_MAPPING.put(byte[].class, JdbcType.VARBINARY.name());
STANDARD_MAPPING.put(byte.class, JdbcType.TINYINT.name());
STANDARD_MAPPING.put(Byte.class, JdbcType.TINYINT.name());
STANDARD_MAPPING.put(Calendar.class, JdbcType.TIMESTAMP.name());
STANDARD_MAPPING.put(java.sql.Date.class, JdbcType.DATE.name());
STANDARD_MAPPING.put(java.util.Date.class, JdbcType.TIMESTAMP.name());
STANDARD_MAPPING.put(double.class, JdbcType.DOUBLE.name());
STANDARD_MAPPING.put(Double.class, JdbcType.DOUBLE.name());
STANDARD_MAPPING.put(float.class, JdbcType.REAL.name());
STANDARD_MAPPING.put(Float.class, JdbcType.REAL.name());
STANDARD_MAPPING.put(int.class, JdbcType.INTEGER.name());
STANDARD_MAPPING.put(Integer.class, JdbcType.INTEGER.name());
STANDARD_MAPPING.put(LocalDate.class, JdbcType.DATE.name());
STANDARD_MAPPING.put(LocalDateTime.class, JdbcType.TIMESTAMP.name());
STANDARD_MAPPING.put(LocalTime.class, JdbcType.TIME.name());
STANDARD_MAPPING.put(long.class, JdbcType.BIGINT.name());
STANDARD_MAPPING.put(Long.class, JdbcType.BIGINT.name());
STANDARD_MAPPING.put(OffsetDateTime.class, JdbcType.TIMESTAMP_WITH_TIMEZONE.name());
STANDARD_MAPPING.put(OffsetTime.class, JdbcType.TIME_WITH_TIMEZONE.name());
STANDARD_MAPPING.put(Short.class, JdbcType.SMALLINT.name());
STANDARD_MAPPING.put(String.class, JdbcType.VARCHAR.name());
STANDARD_MAPPING.put(Time.class, JdbcType.TIME.name());
STANDARD_MAPPING.put(Timestamp.class, JdbcType.TIMESTAMP.name());
STANDARD_MAPPING.put(URL.class, JdbcType.DATALINK.name());
从上述映射的内容中,基本上能看到我们日常所用的基本上都有映射了。 但是如果是映射内容之外的呢?
1.2 java中使用TypeHandler
使用由来,在一个小说的系统中,需要记录浏览小说名称的先后顺序,可以供后期大数据分析使用。 表字段以及数据:
CREATE TABLE `user_see_books` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NULL DEFAULT NULL COMMENT '名称',
`age` INT(11) NULL DEFAULT NULL COMMENT '年龄',
`status` INT(11) NULL DEFAULT '1' COMMENT '状态',
`book_names` VARCHAR(50) NULL DEFAULT NULL COMMENT '阅读书名集合',
`create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
INSERT INTO `user_see_books` (`name`, `age`, `status`, `book_names`, `create_time`, `update_time`) VALUES ('bkid_1646031712018', 19, 1, '斗罗大陆, 遮天, 斗破苍穹', '2022-02-17 16:45:01', '2022-03-07 17:33:12');
但是再bean中需要返回list数组,于是自定义了一个TypeHandler实现类
@Slf4j
@Component
public class MyTypeHandler implements TypeHandler<List<String>> {
@Override
public void setParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
log.info("set ========= " + parameter);
ps.setString(i,parameter.toString().replaceAll("\\[|\\]",""));
}
@Override
public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
log.info("getResult == String");
String string = rs.getString(columnName);
List<String> list = Arrays.asList(string.split(","));
return list;
}
@Override
public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
log.info("getResult == columnIndex");
return null;
}
@Override
public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
log.info("getResult == columnIndex");
return null;
}
}
1.3 测试代码
基于springboot 2.5.6版本,maven就不贴了,查询类
@GetMapping("/data/select")
public Object select() {
Map<String, Object> map = new HashMap<>();
map.put("bookNames", Arrays.asList(1, 2, 3));
List<Map<String, Object>> mapList = testMapper.select(map);
return mapList;
}
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.example.demo.mapper.test.TestMapper">
<resultMap id="userMap" type="Map">
<result column="book_names" property="bookNames" typeHandler="com.example.demo.plugin.MyTypeHandler"/>
</resultMap>
<select id="select" parameterType="Map" resultMap="userMap">
select * from user_see_books
<where>
<if test="bookNames != null">
and book_names= #{bookNames,typeHandler=com.example.demo.plugin.MyTypeHandler}
</if>
</where>
</select>
</mapper>
调用/data/select,查看打印日志和输出结果
Preparing: select * from user_see_books WHERE book_names = ?
set ========= [斗罗大陆, 遮天, 斗破苍穹]
Parameters: 斗罗大陆, 遮天, 斗破苍穹(String)
[{
"update_time": "2022-03-07T17:42:54",
"create_time": "2022-02-17T16:45:01",
"bookNames": ["斗罗大陆", " 遮天", " 斗破苍穹"],
"name": "bkid_1646031712018",
"id": 1,
"age": 19,
"status": 1
}]
需要注意的点 xml中入参写法别忘记,#{bookNames,typeHandler=com.example.demo.plugin.MyTypeHandler} xml中出参映射resultMap不能忘,<result column="book_names" property="bookNames" typeHandler="com.example.demo.plugin.MyTypeHandler"/>
上面仅为写文章搭建出来的,真实情况根据需求来实现
2. Interceptor拦截插件
在很多业务中,我们可能需要去拦截执行的sql,达到不修改原有的代码业务去处理一些东西。例如:分页操作,数据权限,sql执行次数和时长等待,这时就可以用到这个Interceptor拦截器了
2.1 再次回顾一下Mybaits的核心对象
Configuration 初始化基础配置,一些重要的类型对象,例如:插件,映射器,factory工厂,typeHandler对象等等。该类贯穿全局 SqlSessionFactory SqlSession工厂 SqlSession 顶层工作API,和数据库交互,完成CRUD功能 Executor 执行器,是mybatis调度的核心,负责SQL语句的生成和查询缓存的维护 StatementHandler 封装了JDBC Statement操作,负责对应操作,例如:设置参数,结果集转出 ParameterHandler 参数转换处理 MapperedStatement 对执行sql节点的封装 SqlSource 动态生成sql语句,并封装到BoundSql中 BoundSql 动态生成的sql和参数的封装 ResultSetHandler 结果集处理,将JDBC类型转换成Java数据类型 TypeHandler 类型转换器,可自定义实现
2.2 拦截器的作用
Mybatis支持对Executor、StatementHandler、ParmeterHandler和ResultSetHandler接口进行拦截,也就是会对这几个接口进行代理。
2.3 java中实现拦截器
本文主要分析执行器Executor#query拦截
@Slf4j
@Component
@Intercepts(value = {@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyPagePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("进入插件 ================");
Object target = invocation.getTarget();
log.info("原始类来源:{}", target.getClass().getName());
Object[] args = invocation.getArgs();
MappedStatement st = (MappedStatement) args[0];
BoundSql sql = st.getBoundSql(args[1]);
log.info("执行SQL:" + sql.getSql());
Object arg = args[1];
log.info("传入参数:{}", arg);
return invocation.proceed();
}
}
2.4 拦截器测试
还是上面的调用代码,查看一下打印日志
进入插件 ================
原始类来源:org.apache.ibatis.executor.CachingExecutor
执行SQL:select * from user_see_books WHERE book_names = ?
传入参数:{bookNames=[斗罗大陆, 遮天, 斗破苍穹]}
以后再有sql报错找不到sql的,自己也可以实现了。 其他的功能就不多分析了,后面会有专门的插件拦截器pagehelper 使用以及源码分析 别人已经有了这些支持了,那就好好的去把它用起来,不要造重复的轮子。
以上就是本文的全部内容了
上一篇:mybatis第四话 - 让我们一层一层来剥开mybatis的心,源码分析 下一篇:mybatis第六话 - mybatis插件篇之pagehelper的使用
盛年不重来,一日难再晨
|