场景
假如说我有一个数据库表字段的数据类型为json。java对应实体类的属性类型为List集合类型。
问:我应该怎么把数据查出来映射给实体类属性?又应该怎么把实体类数据映射后存入数据库?
示例
数据库表
实体类
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
//部门
private List<DepartmentInfo> department;
}
json数据格式
[
{
"departmentId": "string",
"departmentName": "string"
}
]
实现
依赖
<!--因为存的json数据,所以要用到fastjson转换-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
<dependency>
<!--mybatis plus-->
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
定义集合类型的处理类
@MappedJdbcTypes(JdbcType.VARBINARY)
@MappedTypes({List.class})
//继承BaseTypeHandler类,这个类是mybatis plus提供的基础类型处理类
public abstract class ListTypeHandler<T> extends BaseTypeHandler<List<T>> {
/**
* 保存数据前,会调用这个方法
*
* ps 就是我们jdbc的 ps
* i 对应参数保存sql中的位置,比如说我们以前用jdbc的时候,会写setString(i,value) ? -> value
* List<T> 对应 BaseTypeHandler 上的泛型,parameter 对应实体类属性名
其实这个参数就是获取到我们的实体类配了该处理器的属性的值,这里 parameter 为 department
* jdbcType 如其名
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
//如果参数有值,则用json转换器转换成json字符串,再把值set给ps
//相当于在ps执行sql前,将值进行转换处理再放回sql里面去
String content = parameter == null ? null : JSON.toJSONString(parameter);
ps.setString(i, content);
}
/**
* 查询数据后, 会调用这个方法
*
* rs 返回结果集,你懂jdbc的话应该不会陌生
* columnName 加了转换器的属性对应的列名
* rs.getString(columnName) 获取结果集中该列的值
*/
@Override
public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
//用JSON类的方法把json字符串转换成集合对象
return this.getListByJsonArrayString(rs.getString(columnName));
}
@Override
public List<T> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return this.getListByJsonArrayString(rs.getString(columnIndex));
}
@Override
public List<T> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return this.getListByJsonArrayString(cs.getString(columnIndex));
}
//自定义的一个方法,用于处理查询结果中加了转换器的值
private List<T> getListByJsonArrayString(String content) {
return content == null ? null : JSON.parseObject(content, this.specificType());
}
/**
* 具体类型,由子类提供
* @return 具体类型
*/
protected abstract TypeReference<List<T>> specificType();
}
定义提供具体类型的处理类
//具体类型为
public class DepartmentListTypeHandler extends ListTypeHandler<DepartmentInfo> {
@Override
protected TypeReference<List<DepartmentInfo>> specificType() {
return new TypeReference<List<DepartmentInfo>>() {
};
}
}
具体类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DepartmentInfo {
@JsonSerialize(using = ToStringSerializer.class)
private Long departmentId;//部门id
private String departmentName;//部门名称
public DepartmentInfo(DeptPO deptPO) {
this.departmentId = deptPO.getDeptId();
this.departmentName = deptPO.getDeptName();
}
}
使用
不用xml的查询与插入
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
//添加了自定义的类型处理器
@TableField(value = "department", typeHandler = DepartmentListTypeHandler.class)
private List<DepartmentInfo> department;
}
查询与插入的时候,因为有这个注解,所以会在查询和插入的sql执行前后,调用类型处理器对数据先做处理,再执行sql。
使用xml的查询与插入
xml文件
<resultMap id="xxx" type="xxx.xxx.xxx.xxx">
<result column="department" jdbcType="VARCHAR" property="department" typeHandler="xxx.xxx.xxx.DepartmentListTypeHandler"/>
</resultMap>
<select id="xxx" resultType="xxx" resultMap="xxx">
...
在resultMap标签里面指定一下即可
小结
类比JDBC查询
JDBC代码
ArrayList<User> us=new ArrayList<User>();//存储从数据库中取出来的数据
Connection conn=BaseConnection.getConnection();//获取数据库连接
//sql执行器对象
PreparedStatement ps=null;
//结果集对象
ResultSet rs=null;//查询出来的数据先放到rs中
try{
String sql="select * from user";
ps=conn.prepareStatement(sql);
rs=ps.executeQuery();//执行数据库查询的方法,放到rs中
while(rs.next()){//rs对象相当于一个指针,指向数据库的一横行数据
User user =new User();//封装数据
user.setId(rs.getLong("id"));//rs指针指向id一行获取id一行数据,存储到ne中
user.setTitle(rs.getString("name"));//rs指针指向title一行获取id一行数据,存储到ne中
//... 逐个字段封装数据
//轮到部门字段的时候做一定的处理
String departmentStr = rs.getString("department");
List<DepartmentInfo> department = JSON.parseObject(department, new TypeReference<List<DepartmentInfo>>());
us.add(user);
}
//...
ListTypeHandler代码
/**
* 查询数据后, 会调用这个方法
*
* rs 返回结果集,你懂jdbc的话应该不会陌生
* columnName 加了转换器的属性对应的列名
* rs.getString(columnName) 获取结果集中该列的值
*/
@Override
public List<T> getNullableResult(ResultSet rs, String columnName) throws SQLException {
//用JSON类的方法把json字符串转换成集合对象
//rs.getString("department");
return this.getListByJsonArrayString(rs.getString(columnName));
}
//...
//自定义的一个方法,用于处理查询结果中加了转换器的值
private List<T> getListByJsonArrayString(String content) {
return content == null ? null : JSON.parseObject(content, this.specificType());
}
类比JDBC插入
JDBC代码
Connection conn=BaseConnection .getConnection();
PreparedStatement ps=null;
String sql="insert into user(id,name,age,email,department) values(?,?,?,?,?)"; //占位符
//...
ps= conn.prepareStatement(sql);//把写好的sql语句传递到数据库,让数据库知道我们要干什么
ps.setLong(1,id);
ps.setString(2,name);
ps.setInt(3,age);
ps.setString(4,email);
//处理部门字段
String departmentStr = JSON.toJSONString(department);
ps.setString(5,departmentStr);
int a=ps.executeUpdate();//这个方法用于改变数据库数据,a代表改变数据库的条数
//...
ListTypeHandler代码
/**
* 保存数据前,会调用这个方法
*
* ps 就是我们jdbc的 ps
* i 对应参数保存sql中的位置,比如说我们以前用jdbc的时候,会写setString(i,value) ? -> value
* List<T> 对应 BaseTypeHandler 上的泛型,parameter 对应实体类属性名
其实这个参数就是获取到我们的实体类配了该处理器的属性的值,这里 parameter 为 department
* jdbcType 如其名
*/
@Override
public void setNonNullParameter(PreparedStatement ps, int i, List<T> parameter, JdbcType jdbcType) throws SQLException {
//如果参数有值,则用json转换器转换成json字符串,再把值set给ps
//相当于在ps执行sql前,将值进行转换处理再放回sql里面去
String content = parameter == null ? null : JSON.toJSONString(parameter);
ps.setString(i, content);
}
从上面不难看出,类型处理器的处理逻辑相当于是在执行sql之前做了一层拦截,把加了处理器的属性进行相应的处理后再放入sql或者转成具体类型封装给实体类。
不妨猜测mp在执行sql前,对实体类每个字段进行判断,如果有 TableField注解,且配置了相应的处理器,则处理后再返回结果。
DepartmentListTypeHandler
提供具体类型的处理类。
其实这里用了设计模式里面的模板方法。具体对字段属性要做什么处理,交给抽象类 ListTypeHandler 即可,具体要处理什么类型,由其子类 DepartmentListTypeHandler 来实现。这样的话,往后扩展其他的类型处理器就不用再重写处理过程了,直接重写提供类型的方法即可。