一、介绍
mybatis我们都用过,但很多都只是局限于利用xml来配置statement来进行后端CRUD,对于XML的配置和优化其实可以更深一层地挖掘。(团队开发中,需要搭建项目的微服务权限架构,为了后端用户和角色权限的更好维护,使用到了转化器)
预备知识:
-----了解mybatis是通过扫描Mapper接口文件和对应的XML资源,在查询时候以mapper接口文件中的方法名,来匹配xml中的statement对象,进行查询。
-----了解mybais的CRUD在通过configuration加载配置,启动sqlSessionFacory()后,通过这个工厂来创建sqlSession会话进行沟通
-----了解mybais的预加载是在创建sqlSession后通过获取mapper的方式定位到xml中对应的方法。
-----了解到mybatis在进行定位后开始通过BaseExcetor与cacheExcetor执行(动态SQL解析和查询结果的缓存维护)。
-----了解到mybatis的动态解析的需要经过[参数映射,SQL解析,SQL执行,结果映射]
(图来自博友)
说明:对于一些特殊参数处理,我们可以在参数映射阶段对特殊的参数的隐射方式进行优化和改造。mybatis中提供了如下的一个对象,我们可以通过继承并重写入参和出参的处理方式,从而实现特殊字段的参数隐射和结果映射。
BaseTypeHandler<T>
二、转化器解析
概述:这一部分主要是讲解BaseTypeHandler<T>怎么用以及理解!
说明:类型转化主要是用于当你的逻辑层的数据类型与数据库表中对应的字段的数据类型不一致的场景。比如常见的性别类型,在数据库为数字参数,查询到逻辑层时自动通过字段转化器匹配转化为中文【结果映射】,中文名称在存入数据库时自动通过转化器将中文转化为数字代码【参数映射】。比如数据类型不一致的转化,集合处理等。我们知道某些存储的数据类型类似[java.util.Set]在数据库是没有对应的数据类型,虽然一般情况下我们不会做这种操作,但如果必须要我们通常是选择存储为[Vachar]类型。这个时候也可以通过转化器实现通用的转化管理。处理思路依旧是【参数映射】,【结果映射】分别做出入管理。
顶层接口:
public interface TypeHandler<T> {
void setParameter(PreparedStatement var1, int var2, T var3, JdbcType var4) throws SQLException;
T getResult(ResultSet var1, String var2) throws SQLException;
T getResult(ResultSet var1, int var2) throws SQLException;
T getResult(CallableStatement var1, int var2) throws SQLException;
}
抽象接口:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
protected Configuration configuration;
public BaseTypeHandler() {
}
}
说明:以上为类型转化器的顶层接口与抽象接口,我们要继承的便是BaseTypeHandler<T>。其中<T>类型传入的是我们逻辑层的数据类型。在实现继承后,我们将重写如下的四个方法。
(因为电脑截图范围限制,文章为了描述,注释与备注并没有做统一规范。)
说明1:在重写完这个方法后,如果xml的statement中存在配置,那么在进行【参数映射】的时候,【预处理权柄】将会自动走入此类。逻辑层调用所指定的参数将会通过如下方法进行处理,处理的结果作为最终数据库的入参,交给数据库执行:
/**
* CYQ:插入时候将数据转化为字符串 [xxx,xxxx,xxxxx] 存入数据库
* @param preparedStatement
* @param i
* @param integers
* @param jdbcType
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Set<Integer> integers, JdbcType jdbcType) throws SQLException {
StringBuffer result = new StringBuffer();
result.append("[");
if (!CollectionUtils.isEmpty(integers)){
for (Integer integerParam : integers){
result.append(integerParam).append(",");
}
result.deleteCharAt(result.length()-1);
}
result.append("]");
preparedStatement.setString(i,result.toString());
}
说明2:当我们数据库存入数据后,查询出来的数据便是我们数据库的数据格式,获取之后转化为我们需要的参数【结果映射】,通常我们获取数据的方式,有通过数据库字段名去匹配获取,通过索引字段去获取,通过存储过程去获取。因此我们需要重写获取的这三种类型的映射方法。
/**
* CYQ:通过字段名查询后转化
* @param resultSet
* @param s
* @return
* @throws SQLException
*/
@Override public HashSet<Integer> getNullableResult(ResultSet resultSet, String s) throws SQLException {
return getStringToSet(resultSet.getString(s));
}
/**
* CYQ:通过字段索引查询后转化
* @param resultSet
* @param i
* @return
* @throws SQLException
*/
@Override public Set<Integer> getNullableResult(ResultSet resultSet, int i) throws SQLException {
return getStringToSet(resultSet.getString(i));
}
/**
* CYQ:通过存储过程查询后转化
* @param callableStatement
* @param i
* @return
* @throws SQLException
*/
@Override public Set<Integer> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return getStringToSet(callableStatement.getString(i));
}
/**
* CYQ:将数据库中字符串类型转化为Set<Integer>集合(兼具备去重的责任)
* @param columnValue
* @return
*/
public HashSet<Integer> getStringToSet(String columnValue) {
if (StringUtils.isBlank(columnValue)){
return null;
}
HashSet<Integer> integerHashSet = new HashSet<Integer>();
Integer[] integers = toObject(columnValue);
if (integers.length == 0){
return null;
}
for (Integer s: integers){
integerHashSet.add(s);
}
return integerHashSet;
}
//将数据库字段转化为数组
public Integer[] toObject(String content) {
if (content != null && !content.isEmpty()) {
try {
return (Integer[]) mapper.readValue(content, Integer[].class);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return null;
}
}
到此处我们的转化器便处理完成了,那么应该如何使用!
三、配置和使用
说明: XML文件的配置因为Mybaits的用法很多,本次只说明基本的配置方式,如图:(免注册)
方式一:<resultMap></resultMap>标签
<resultMap id="BaseResultMap" type="com.xxxxx.xxxx.comom.vo.SysAdminInfo">
<id column="y_pk_id" jdbcType="INTEGER" javaType="java.lang.Integer" />
<result column="user_name" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="pass_word" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="last_login_ip" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="last_login_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
<result column="head_picture" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="role_ids" jdbcType="VARCHAR" property="roleIds" javaType="java.util.Set" typeHandler="com.xxxx.xxxx.framework.common.mybatis.StringSetTypeHandler"/>
<result column="create_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
<result column="update_time" jdbcType="TIMESTAMP" javaType="java.util.Date" />
<result column="create_by" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="update_by" jdbcType="VARCHAR" javaType="java.lang.String" />
<result column="del_flag" jdbcType="BIT" javaType="java.lang.Boolean" />
</resultMap>
说明:此处的配置我们以role_ids字段,role_ids本身在实体类中为Set类型,因为数据库限制,我们只能需要以字符串的形式维护进去。因此,使用了 【StringSetTypeHandler】做了转化。配置如下:
<result column="role_ids" jdbcType="VARCHAR" property="roleIds" javaType="java.util.Set" typeHandler="com.xxxx.xxxx.framework.common.mybatis.StringSetTypeHandler"/>
注意:mybatis不会通过数据库信息去窥探数据类型和逻辑层实体类的类型是否一致,只会在对应不上的时候报异常。因此我们需要明确告知其 对应的实体类类型【javaType】与数据库表数据类型【jdbcType】;此处使用property是因为数据库字段与表字段不一致,我们需要进行协调。(使用property必须使用resultMap标签中的result参数来作为标记)
<if test="roleIds != null" >
#{roleIds,jdbcType=VARCHAR,javaType=java.util.Set,typeHandler=com.xxxx.xxx.framework.common.mybatis.StringSetTypeHandler},
</if>
说明:在XML中实行了转化器的插入或者查询引用方式如上。
补充说明:如果需要使用property则不能使用xml里面的构造器。