1. 前言
通常后台开发中与数据库打交道是必不可少的。Java中原生操作数据库是通过JDBC是处理的,但是步骤繁琐,冗余,性能较低。为解决这些问题,就出现了各种数据库持久层框架,其中比较灵活轻量的框架就是Mybatis了,也是大部分公司采用的技术。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级射映。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
我们经常需要编写Mapper接口以及对应的xml文件 ,那么问题来了,在xml文件中如何接收接口中的参数呢??尤其是复杂的Java对象如何取值,这是个值得底层开发人员必须知道的问题。
2. 具体场景
(1)Mapper参数类型
mapper接口中的参数如果是
a. 多个参数;
b. 参数是集合或者数组。
这两种情况下一定要写@Param(value = "XXX")
void deleteByIds(@Param(value = "ids") Set<String> ids);
对应的xml文件:
<delete id="delete" parameterType="java.util.Set">
DELETE FROM
<include refid="tableName" />
WHERE id in
<foreach collection="ids" item="item" open="(" close=")"
index="index" separator=",">
#{item,jdbcType=VARCHAR}
</foreach>
</delete>
注意collection="ids"与@Param(value = "ids")需要对应。
(2) 批量操作
对于上面的根据id 进行批量删除,采用foreach没有问题,但是如果是根据多个参数进行批量删除,比如:
<foreach open="(" close=")" separator=";"
<delete id="delete" parameterType="java.util.List">
delete from tableName where id=#{id} and parentId=#{paretnId}
</delete>
</foreach>
这种语句采用foreach将参数 填入则会出现错误。目前解决的方式是在service层对集合遍历然后调用对应的mapper,这中方式当数据量大的时候可能会阻塞程序,当然还可以采用case when的xml书写,但是比较复杂。
(3) TypeHandler的使用方式
通常,我们定义实体类对象包含各种数据,基本类型,时间,枚举类,布尔型,集合类等其他包装类型,那么在做resultMap映射时,除开基本类型和时间类型mybatis可以自动对应数据库中字段,其他类型都需要做个类型转换的帮助类,下面展示的是将常用类型转化为字符类型,以便存入数据库。由于便于集中处理,数据库中使用的字段全为varchar。
(a) set集合映射
如果在resultMap中有字段的映射,要跟上typeHandler;
<result column="ids" jdbcType="VARCHAR"
property="ids"
typeHandler="typeHandler.Set2CharTypeHandler" />
如果在增删改查的操作中,需要用到此字段,也必须加上typeHandler;
<if test="ids!=null">
ids=#{filterPolicyIds,jdbcType=VARCHAR,typeHandler=typeHandler.Set2CharTypeHandler},
</if>
我们要做的就是就是编写Set2CharTypeHandler类实现TypeHandler接口,重写三个方法。mybatis会去遍历所有的TypeHandler找到对应的类型,将set集合与string 做相应的互转。
public class Set2CharTypeHandler implements TypeHandler {
//存入数据的时候要调用的方法
@Override
public void setParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {
Set list = (Set)o;
String setStr = StringUtils.join(list,",");
preparedStatement.setString(i,setStr);
}
//下面两种方法是取数据时候需要调用
@Override
public Object getResult(ResultSet resultSet, String s) throws SQLException {
String result = resultSet.getString(s);
String [] arrRes = result.split(",");
return new HashSet<>(Arrays.asList(arrRes));
}
@Override
public Object getResult(CallableStatement callableStatement, int i) throws SQLException {
String result = callableStatement.getString(i);
String [] arrRes = result.split(",");
return new HashSet<>(Arrays.asList(arrRes));
}
}
(b) 布尔类的映射
-
resultMap的映射:
<result column="enable" jdbcType="VARCHAR" property="enable"
typeHandler="typeHandler.Bool2CharTypeHandler" />
-
增删改查的操作字段映射:
<if test="enable!='null'">
enable = #{enable,typeHandler=typeHandler.Bool2CharTypeHandler}
</if>
-
typeHandler代码示范:
注意@MappedJdbcTypes(JdbcType.VARCHAR) 和 @MappedTypes(Boolean.class) 不是必须的
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(Boolean.class)
public class Bool2CharTypeHandler implements TypeHandler {
@Override
public void setParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {
boolean enable = (boolean)o;
preparedStatement.setString(i,enable?"Y":"N");
}
@Override
public Boolean getResult(ResultSet resultSet, String s) throws SQLException {
String result = resultSet.getString(s);
return StringUtils.isEmpty(result)?false:result.equalsIgnoreCase("Y")?true:false;
}
@Override
public Boolean getResult(CallableStatement callableStatement, int i) throws SQLException {
String result = callableStatement.getString(i);
return StringUtils.isEmpty(result)?false:result.equalsIgnoreCase("Y")?true:false;
}
}
(c) 枚举类的映射
-
枚举类的定义
public enum RoleCategory {
USER("U"),RESOURCE("R");
@Getter
private String type;
private RoleCategory(String type) {
this.type=type;
}
public static RoleCategory fromType(String type) {
for(RoleCategory item:values()) {
if(item.type.equalsIgnoreCase(type)||item.toString().equalsIgnoreCase(type)) {
return item;
}
}
throw new CodedException(IErrorCodeService.NOT_SUPPORT_TYPE_OR_FUNCTION,"not support role category[\"+type+\"].");
}
}
-
typeHandler代码示范
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(RoleCategory.class)
public class RoleCategory2StringTypeHandler implements TypeHandler{
@Override
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
RoleCategory category=(RoleCategory)parameter;
ps.setString(i,category.getType());
}
@Override
public RoleCategory getResult(ResultSet rs, String columnName) throws SQLException {
String result = rs.getString(columnName);
return RoleCategory.fromType(result);
}
@Override
public RoleCategory getResult(CallableStatement cs, int columnIndex) throws SQLException {
String result = cs.getString(columnIndex);
return RoleCategory.fromType(result);
}
}
(d) 复杂包装类
-
类型定义:
包含自定义对象Target,List, Set,枚举类RoleCatetory
@Data
public class PolicySet{
private String description;
private Target target;
private List<Policy> policies=new ArrayList<>();
private RoleCategory role=RoleCategory.USER;
private Set<String> referencedPolicyId=new HashSet<>();
}
-
typeHandler代码, 这里使用了JSON解析:
public class PolicyModelTypeHandler implements TypeHandler {
@Override
public void setParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, JSON.toJSONString(o));
}
@Override
public PolicySet getResult(ResultSet resultSet, String s) throws SQLException {
String result = resultSet.getString(s);
return JSON.parseObject(result,PolicySet.class);
}
@Override
public PolicySet getResult(CallableStatement callableStatement, int i) throws SQLException {
String result = callableStatement.getString(i);
return JSON.parseObject(result,PolicySet.class);
}
}