场景
假如说我有一个数据库表字段的数据类型为json。java对应实体类的属性类型为List集合类型。
问:我应该怎么把数据查出来映射给实体类属性?又应该怎么把实体类数据映射后存入数据库?
示例
数据库表
实体类
@Datapublic 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" }]
实现
依赖
<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> <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 对应 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@NoArgsConstructorpublic 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 对应 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 来实现。这样的话,往后扩展其他的类型处理器就不用再重写处理过程了,直接重写提供类型的方法即可。