文章目录
- 一、自动生成代码
- 1.1 安装插件
- 1.2 生成代码
- 二、Db 静态工具类
- 2.1 对 Db 静态工具类的认识
- 2.2 Db 静态工具类的使用案例
- 三、逻辑删除
- 四、枚举处理器
- 4.1 定义枚举常量
- 4.2 配置枚举处理器
- 4.3 测试枚举处理器的字段转换
- 五、JSON 处理器
- 5.1 定义实体
- 5.2 使用类型处理器
一、自动生成代码
在学习了 MyBatis Plus 的使用之后,我们发现了基础的 Mapper
、Service
、PO
等等代码基本上都是固定的,如果这样的话重复的编写代码就显得非常麻烦了。恰好,MyBatis Plus 官方就提供了代码生成器来根据数据库的表结构来自动为我们生成 Mapper
、Service
、PO
相关的代码。只不过代码生成器同样要编码使用,也很麻烦。这里推荐大家使用一款 Mybatis Plus 的插件,它可以基于图形化界面完成 Mybatis Plus 的代码生成,非常简单。
1.1 安装插件
在 IDEA 的 plugins 中搜索 “Mybatis Plus”,选择其中那个图标特别可爱的就是了:
安装成功之后,可以在 IDEA 的导航栏中发现看到一个 Orther
选项:
其中就包括了数据库的配置以及生成代码的选项了。
1.2 生成代码
此时正好我们有一个 address
表还没有编写对应的代码的,此时我们可以使用这个插件来自动生成 address
表相关的代码。
- 首先配置数据库,弹出的窗口中填写数据库连接的基本信息:
- 然后再次点击 IDEA 导航栏中的
other
,然后选择生成代码:
点击 “code generatro” ,就会自动生成代码到指定位置。
二、Db 静态工具类
2.1 对 Db 静态工具类的认识
有的时候不同的 Service 类之间会相互调用,为了避免出现循环依赖问题,Mybatis Plus 提供一个静态工具类:Db
,其中的一些静态方法与IService
中方法签名基本一致,也可以帮助我们实现 CRUD 的功能:
例如,下面的使用实例:
/** * 获取id为 1 的用户信息 */@Testvoid testDbGet() {User user = Db.getById(1L, User.class);System.out.println(user);}/** * 查询用户名中带 “o”, 并且balance >= 1000 的用户信息 */@Testvoid testDbList() {List<User> list = Db.lambdaQuery(User.class).like(User::getUsername, "o").ge(User::getBalance, 1000).list();System.out.println(list);}/** * 设置用户名为 Rose 的用户的 balance 为 2500 */@Testvoid testDbUpdate() {Db.lambdaUpdate(User.class).set(User::getBalance, 2500).eq(User::getUsername, "Rose").update();}
可以发现,对应 Db
静态类的使用和前面的使用方法都是类似的。
2.2 Db 静态工具类的使用案例
示例一:改造根据 id 用户查询的接口,查询用户的同时返回用户收货地址列表。
- 首先,我们要添加一个收货地址的 VO 对象:
@Data@ApiModel(description = "收货地址VO")public class AddressVO{@ApiModelProperty("id")private Long id;@ApiModelProperty("用户ID")private Long userId;@ApiModelProperty("省")private String province;@ApiModelProperty("市")private String city;@ApiModelProperty("县/区")private String town;@ApiModelProperty("手机")private String mobile;@ApiModelProperty("详细地址")private String street;@ApiModelProperty("联系人")private String contact;@ApiModelProperty("是否是默认 1默认 0否")private Boolean isDefault;@ApiModelProperty("备注")private String notes;}
- 然后,改造原来的
UserVO
类,在最后添加一个地址属性:
- 接下来,修改
UserController
中根据id
查询用户的业务接口:
@GetMapping("/{id}")@ApiOperation("根据id查询用户接口")public UserVO queryUserById(@PathVariable("id") Long id) {return userService.queryUserAndAddressById(id);}
此时,新增了一个queryUserAndAddressById
方法。
在
service
层实现queryUserAndAddressById
方法- 首先在
IUserService
中定义方法:
public interface IUserService extends IService<User> {UserVO queryUserAndAddressById(Long id);}
- 然后,在
UserServiceImpl
中实现该方法:
@Overridepublic UserVO queryUserAndAddressById(Long id) {// 1. 查询用户User user = getById(id);// 2. 使用 Db 根据用户查询地址类别List<Address> addresses = Db.lambdaQuery(Address.class).eq(Address::getUserId, user.getId()).list();// 3. 处理 VOUserVO userVO = BeanUtil.copyProperties(user, UserVO.class);userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));return userVO;}
- 首先在
在查询地址时,使用用了 Db
中的静态方法,因此避免了注入 AddressService
,减少了循环依赖的风险。
完成上上面的代码之后,通过 id
查询的用户信息中就有了地址信息了:
示例二:改造根据 id 批量查询用户的接口,要求查询出用户对应的所有地址
- 修改
controller
接口:
@GetMapping@ApiOperation("根据id批量查询用户接口")public List<UserVO> queryUserByIds(@ApiParam("用户id集合") @RequestParam("ids") List<Long> ids) {return userService.queryUserAndAddressByIds(ids);}
在
service
层实现queryUserAndAddressByIds
方法- 首先在
IUserService
中定义方法:
public interface IUserService extends IService<User> {List<UserVO> queryUserAndAddressByIds(Long id);}
- 然后,在
UserServiceImpl
中实现该方法:
@Overridepublic List<UserVO> queryUserAndAddressByIds(List<Long> ids) {// 1. 查询用户集合List<User> users = this.listByIds(ids);if (users.isEmpty()) {return Collections.emptyList();}// 2. 查询地址// 2.1 获取用户idList<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());// 2.2 查询地址List<Address> addresses = Db.lambdaQuery(Address.class).in(Address::getUserId, userIds).list();// 2.3 地址转换为 VOList<AddressVO> addressVOS = BeanUtil.copyToList(addresses, AddressVO.class);// 2.4 按照 userId 将地址 VO 进行分组Map<Long, List<AddressVO>> addressesMap = new HashMap<>();if (CollUtil.isNotEmpty(addresses)) {addressesMap = addressVOS.stream().collect(Collectors.groupingBy(AddressVO::getUserId));}// 3. 转换 VO 返回List<UserVO> list = new ArrayList<>(users.size());for (User user : users) {UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);// 填充地址userVO.setAddresses(addressesMap.get(user.getId()));list.add(userVO);}return list;}
- 首先在
注意事项:
在使用查询到的用户的id去查询地址信息的时候,要避免在循环中查询数据库。因此首先获取用户 id 集合,然后再根据这些 id 集合批量查询地址信息。
将查询出的地址信息按照用户 id 进行分类,然后设置进对应的
UserVO
对象中。
三、逻辑删除
对于一些比较重要的数据,我们往往会采用逻辑删除的方案,例如:
在表中添加一个字段标记数据是否被删除
当删除数据时把标记置为true
查询时过滤掉标记为true的数据
一旦采用了逻辑删除,所有的查询和删除逻辑都要跟着变化,那么对应数据库的操作就会变得非常麻烦。幸运的是,为了解决这个麻烦,MyBatis Plus 就提供了对逻辑删除的支持。
注意,只有 Mybatis Plus 生成的SQL语句才支持自动的逻辑删除,自定义 SQL 还是需要自己手动处理逻辑删除。
下面演示使用 MyBatis Plus 的逻辑删除功能:
- 在给
address
表添加一个逻辑删除字段:
alter table address add deleted bit default b'0' null comment '逻辑删除';
- 然后给
Address
实体添加deleted
字段:
- 接下来,在
application.yml
中配置逻辑删除字段
mybatis-plus:global-config:db-config:logic-delete-field: deleted # 逻辑删除字段logic-delete-value: 1 # 当 deleted 的值为 1,就逻辑删除了logic-not-delete-value: 0 # 当 deleted 的值为 0 ,没有逻辑删除
当完成了上面所有的准备工作之后,我们可以执行一个删除的测试方法:
@Testvoid testLogicDelete() {addressService.removeById(59L);}
运行这段代码:
发现将 id 为 59 的地址信息的 deleted
字段设置为了1,如果此时再查询这条数据:
发现此时就查询不到这条数据了,但是还在数据库中还仍然存在。
因此开启了逻辑删除功能以后,我们就可以像普通删除一样做 CRUD,基本不用考虑代码逻辑问题。还是非常方便的。但是使用逻辑删除也存在一定的问题,比如:
- 会导致数据库表垃圾数据越来越多,从而影响查询效率;
- SQL中全都需要对逻辑删除字段做判断,影响查询效率。
因此,不太推荐采用逻辑删除功能,如果数据不能删除,可以采用把数据迁移到其它表的办法。
四、枚举处理器
在 User
实体类中有一个用户状态字段:
像这种字段我们一般会定义一个枚举,做业务判断的时候就可以直接基于枚举做比较。但是我们数据库采用的是 int
类型,对应的 PO 也是Integer
。因此业务操作时必须手动把枚举与 Integer
转换,非常麻烦。因此,Mybatis Plus提供了一个处理枚举的类型转换器,可以帮我们把枚举类型与数据库类型自动转换。
4.1 定义枚举常量
首先,我们为用户表中的这个状态字段定义一个枚举常量:
@Getterpublic enum UserStatus {NORMAL(1, "正常"),FROZE(2, "冻结"),;private final int value;private final String desc;UserStatus(int value, String desc) {this.value = value;this.desc = desc;}}
然后把 User
类中的 status
字段改为 UserStatus
类型:
要让 Mybatis Plus处理枚举与数据库类型自动转换,我们必须告诉 Mybatis Plus,枚举中的哪个字段的值作为数据库值。Mybatis Plus 提供了 @EnumValue
注解来标记枚举属性:
4.2 配置枚举处理器
在application.yml
文件中添加以下配置,以开启枚举处理器的功能:
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler # 枚举处理器
4.3 测试枚举处理器的字段转换
例如,根据id查询某个用户:
此时,查询出的 User
类的 status
字段会是枚举类型。
同时,为了使页面查询结果也是枚举格式,我们需要修改 UserVO
中的status
属性:
并且,在UserStatus
枚举中通过@JsonValue
注解标记 JSON 序列化时展示的字段是 desc
:
最后,在页面查询,结果如下:
五、JSON 处理器
数据库的user
表中有一个info
字段,是 JSON 类型:
格式就像这样:
{"age": 20, "intro": "佛系青年", "gender": "male"}
但是目前User
实体类中却是String
类型,因为在 Java 中没有 JSON 这样的类型:
这样一来,我们要读取 info
中的属性时就非常不方便。如果要方便获取,info
的类型最好是一个 Map 或者实体类。
而一旦我们把info
改为对象类型,就需要在写入数据库时手动转为 String,再读取数据库时,手动转换为对象,这会非常麻烦。
因此 Mybatis Plus提供了很多特殊类型字段的类型处理器,解决特殊字段类型与数据库类型转换的问题。例如处理 JSON 就可以使用 JacksonTypeHandler
处理器。
接下来,我们就来看看这个处理器该如何使用。
5.1 定义实体
首先,我们定义一个单独实体类来与 info
字段的属性匹配:
@Data@NoArgsConstructor@AllArgsConstructorpublic class UserInfo {private Integer age;private String intro;private String gender;}
5.2 使用类型处理器
接下来,将 User
类的 info
字段修改为 UserInfo
类型,并声明类型处理器:
注意,需要设置autoResultMap
为 true
,才能生效。
测试可以发现,所有数据都正确封装到UserInfo
当中了:
同时,为了让页面返回的结果也以对象格式返回,我们要修改UserVO
中的info
字段的类型:
此时,在页面查询结果如下:
发现,此时显示的 info
就是 JSON 格式了。