不知道你有没有用过mybatis/mybatis-plus?应该都用过吧(碎碎念:没用过我也就很无奈了),那么在用这个半orm框架的时候不知道你们都遇到了哪些坑,今天我就来说说我遇到的坑以及解决方案。
因为在设计数据库的时候使用到了json类型结构要对应Entity的List<Entity>类型,就需要考虑如何完成这个蜜汁操作??
对mybatis有些了解的应该知道实体映射只会自动映射基本类型,像list或者map这种类型是没办法来进行自动映射的,所以很多人在处理的时候常采用以下方案:
将Entity字段类型更改成String类型,让mybatis/mybatis-plus来帮我们映射,映射后的字段值再进行二次处理
不得不说,这是一种方案,但是这种方案相对来说比较麻烦,需要有二次操作。进一步调研发现(相信你们在写的时候也或多或少搞过,就是指定类型,ResultMap中指定字段的typehandler–类型处理器)。不知道你们的sql是写在xml中还是直接使用的wrapper接口来进行的,所以我不会推荐你们去使用xml的方式进行,因为我有尝试网上的各种方法,都为能生效合理,所以我下面主要来说一下注解使用wrapper方式
华丽分隔符之后讲述解决方案,前期准备
数据库表结构如上图,sql脚本如下:
CREATE TABLE `message` (
`id` varchar(24) NOT NULL,
`content` json DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在执行过程中你可能会爆粗口,奶奶的,为啥不行呢,为啥我的没有json类型呢?因为json这种类型是5.7版本(也成7版本)之后才有的,自己重新建mysql实例吧。
前期准备有了,那么看一下实体的结构吧:
@Data
@EqualsAndHashCode(callSuper = false)
public class Message extends Model<Message> {
private String id;
private List<User> content;
}
@Data
public class User {
private String id;
private String name;
private String nameEn;
private String avatar;
}
首先我们第一步需要对实体进行修改,修改效果如下:
@Data
@EqualsAndHashCode(callSuper = false)
@TableName(value = "message",autoResultMap = true)
public class Message extends Model<Message> implements Serializable {
private String id;
@TableField(value = "content",typeHandler = FastjsonTypeHandler.class)
private List<User> content;
}
这里尤其注意autoResultMap注解,默认是false,点进入看到如下:
/**
* 是否自动构建 resultMap 并使用,
* 只生效与 mp 自动注入的 method,
* 如果设置 resultMap 则不会进行 resultMap 的自动构建并注入,
* 只适合个别字段 设置了 typeHandler 或 jdbcType 的情况
*
* @since 3.1.2
*/
boolean autoResultMap() default false;
这里如果你开启的自动匹配类型,就必须要给特殊字段设置他的类型转换器,否则就会抛出异常,如下:
大概的意思就是让你给这个字段设置一个类型转换器,因为mybatis/mybatis-plus不识别list这种类型,当都设置好了之后就可以测试一下了,测试代码如下:
@RestController
@RequestMapping("/message")
public class MessageController {
private final MessageService messageService;
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
@GetMapping("get")
public ResponseEntity<?> get(String id) {
return CommonResponse.success(messageService.get(id));
}
@GetMapping("set")
public ResponseEntity<?> set() {
return CommonResponse.success(messageService.set());
}
}
@Service
public class MessageService {
private final MessageMapper messageMapper;
public MessageService(MessageMapper messageMapper) {
this.messageMapper = messageMapper;
}
public Message get(String id) {
return messageMapper.selectById(id);
}
public int set() {
Message message = new Message();
message.setId(new ObjectId().toHexString());
List<User> users = new ArrayList<>();
User user = new User();
user.setId(new ObjectId().toHexString());
user.setName("张三");
user.setName("zhangsan");
user.setAvatar("http://www.baidu.com");
users.add(user);
message.setContent(users);
return messageMapper.insert(message);
}
}
@Mapper
@Repository
public interface MessageMapper extends BaseMapper<Message> {
}
效果如下:
{
"code": 200,
"message": "ok",
"data": {
"id": "60434fba268a283401628323",
"content": [
{
"name": "张三",
"id": "60434fba268a283401628324",
"avatar": "http://www.baidu.com",
"nameEn": "zhangsan"
}
]
}
}
大概的处理流程就是上边,xml方式我曾有过尝试,不知道是因为版本的原因还是因为我操作的原因,不能生效,只有这种方式最直接有效,typehandler也是有很多类型,可以使用fastjson,也可以使用其他的,如果你们还有其他好的方式,可以整理一下甩个链接给我…
坑:
当你使用了某个类型转换器,就必须添加上与这个类型转换器相关的依赖,比如你使用了FastjsonTypeHandler就必须添加上FastJson的依赖,否则不会生效。
另外一个坑就是如果使用FastjsonTypeHandler这种类型转换器,返回的时候上述例子中内层嵌套的List<User>就不再是User对象了,而是被转换成JSONObject对象。如下:
对直接返回给前端的数据友好,但是如果你还要对查询到的数据当成对象来处理就只能采用:1、自定义类型转换器 2、将JSONObject再转换成实体对象