如何在SpringBoot中如何引入MyBatis-Plus如下:
https://blog.csdn.net/iwjijksw/article/details/142063661?spm=1001.2014.3001.5501
1.MyBatis-Plus是怎么自己编写sql的
public interface UserMapper extends BaseMapper<User> {
}
@Data
public class User {
private Long id;
private String username;
private String password;
private String phone;
private String info;
private Integer status;
private Integer balance;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
由上面,我们可以看到继承的BaseMapper有个泛型是User类型的,而MyBatis-Plus就是通过反射这个User得到与数据库相对应的属性值,其中类名就是表名,id值就是主键,而映射的数据库属性名由如下规则:
- 类名驼峰转下划线作为表名
- 名weiid的字段为主键
- 变量名驼峰转下划线作为表的字段名
(由此可以看出平时的编码习惯很重要)
但是只有这些规则显然不能满足一些特殊的情况,于是MyBatis-Plus就提供了一些注解。
1-1 常用注解
@TableName:用来指定表名
@TableId:用来指定表中的主键值字段信息
@TableField:用来指定表中的普通字段信息
@TableName("tb_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long ID;
@TableField("username")
private String userName;
//如果是布尔类型,是is开头的,要自己手动转换
@TableField("is_married")
private Boolean isMarried;
//变量名和数据库关键字冲突的
@TableField("`order`")
private Integer order;
//数据库中不存在的
@TableField(exist = false)
private String address;
}
MyBatis-Plus其他注解————>注解
1-2 常用配置
mybatis-plus:
type-aliases-package: com.aaa.bbb.ccc.po #别名扫描包
mapper-locations: "classpath*:mapper/**/*.xml" #mapper.xml文件地址
configuration:
map-underscore-to-camel-case: true #是否开启下划线和驼峰的映射
cache-enabled: false #是否开启二级缓存
global-config:
db-config:
id-type: assign_id #雪花算法
update-strategy: not_null #更新策略:只更新非空字段
2.核心功能
2-1 条件构造器
-
2-1-1 QueryWrapper
@Test
void testUserByQueryWrapper() {
//构建条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.ge("balance",1000)
.like("username", "o");
List<User> users = userMapper.selectList(wrapper);
//遍历集合
users.forEach(System.out::println);
}
QueryWrapper比起它的父类,扩展了select方法,可以查询指定字段
-
2-1-2 UpdateWrapper
//需求:更新id为1,2,4的用户的余额,扣200
@Test
void testUserByUpdateWrapper() {
//方法一:
QueryWrapper<User> wrapper1 = new QueryWrapper<User>()
.in("id", 1, 2, 4);
List<User> users = userMapper.selectList(wrapper1);
users.forEach(user -> {
user.setBalance(user.getBalance() - 200);
userMapper.updateById(user);
});
//方法二:
UpdateWrapper<User> wrapper2 = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", 1, 2, 4);
userMapper.update(null, wrapper2);
}
UpdateWrapper也有区别于父类的扩展方法,就是setSql方法,可以自定义sql语句,上面两个方法的对于扣200余额这个需求来说,明显第二个更加简单方便。
-
2-1-3 LambdaQueryWrapper
但是我们编程最好不要硬编码,所以有了Lambda形式,让其动态的获取字段值
@Test
void testUserByQueryWrapper() {
//构建条件
LambdaQueryWrapper<User> wrapper1 = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername, User::getInfo, User::getBalance)
.ge(User::getBalance, 1000)
.like(User::getUsername, "o");
List<User> users1 = userMapper.selectList(wrapper1);
//遍历集合
users1.forEach(System.out::println);
}
LambdaUpdateWrapper也是一样的
2-2 自定义sql
在开发过程中,大多数的单表的增删改查我们都可以用MyBatis-plus来做,但是也有一些特殊的情况,如下
UpdateWrapper<User> wrapper2 = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", 1, 2, 4);
一般这种mp的代码,我们都在业务层去写,但是企业规范中是不允许sql语句写道业务层的,违反了开发规范,所以又出现了自定义的sql。基于wrapper构建条件,自己写剩下的sql,然后拼装。
2-2-1 自定义sql步骤如下:
1.基于wrapper构建条件
List<Long> ids = List.of(1L,2L,4L);
int amount = 200;
// 1.构建条件
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.in(User::getId,ids);
// 2. 自定义SQL方法调用
userMapper.updateBlanceByIds(wrapper,amount);
2.在mapper方法中,用@Param声明wrapper的名称,固定ew
void updateBalanceByIds (@Param("ew") LambdaQueryWrapper <User> Wrapper,
@Param("amount") int amount);
3.自定义SQL,并使用wrapper条件
<update id="updateBalanceByIds">
update user set blance = blance - #{amount} ${ew.customSqlSegment}
</update>
2-3 service接口
2-3-1 IService提供的相关方法
2-3-2 service的实现类继承关系:
在service层,mp也提供了相关方法实现增删改查,更加的方便,只不过要我们的接口去继承IService接口,并且实现类继承ServiceImpl类,因为IService内的方法非常多,ServiceImpl是官方提供的实现类。
//service层接口实现IService
public interface UserService extends IService<User> {
}
//service层实现类继承ServiceImpl,再实现我们自己的接口
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}
用法举例:
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
//根据id查询用户
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id){
return userService.getById(id);
}
}
由此可以看出,这是一个controller的类,以往我们还得再service层,mapper层写代码,对于这种简单的增删改查,多了很多没必要的事情,但是现在只需要一行代码,就完成简单的查询用户操作,是不是很方便呢
2-3-3 LambdaQuery方法
对于有些复杂的条件查询,我们不能把他们全部写在controller层,这样就显得controller层的代码非常臃肿,所以在service层调用自己就可以。如下
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
//条件:名字中带有name的,状态为status的,大于最小金额以及小于最大金额,且这四个可能为空
@Override
public List<User> getUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return this.lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list();
}
}
2-3-4 LambdaUpdate方法
LambdaUpdate和LambdaQuery基本一样,只不过最后要调用update方法
this.lambdaUpdate()
.set(User::getUsername, name)
.set(minBalance == 0, User::getStatus, status)
.update();
2-3-5 逐条插入数据与批处理插入的性能对比
首先我在User类中构建了一个静态方法用于创建User对象并插入属性数据
public static User sUserBuilder(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123456");
user.setPhone("18909800000" + i);
user.setInfo("{\"age\":24,\"intro\":\"编程老师\",\"gender\":\"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
}
然后插入数据
@Test
void createUser() {
long originTime= System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
userService.save(User.sUserBuilder(i));
}
long finallyTime = System.currentTimeMillis();
System.out.println("10万条数据的创建时间是" + (finallyTime - originTime));
}
可以看出时间挺长的,有3.5分钟
然后批处理,一次插入100条,共100次,看看效果怎么样,前提给MySQL数据库传入批处理重写为一条的配置属性rewriteBatchedStatements=true
@Test
void insertUser(){
long originTime = System.currentTimeMillis();
//准备一个容量为1000的集合
List<User> list = new ArrayList<>(1000);
for (int i = 1; i <= 100000; i++) {
list.add(User.sUserBuilder(i));
if (i%1000 == 0){
userService.saveBatch(list);
//清空集合
list.clear();
}
}
long finallyTime = System.currentTimeMillis();
System.out.println("10万条数据的创建时间是" + (finallyTime - originTime));
}
可以看出时间大大缩短,只有不到8秒,性能提高了27倍,这就是批处理的好处
3.扩展功能
3-1 Db工具类
我们在开发过程的,会遇到一种情况,就是一个类中需要用到其他不同的类,比如用户service层可能会用到地址的mapper层或者地址的service层,用户类和地址类按照开发规范应该彼此间尽可能的少调用,降低类与类之间的耦合性,提高项目后期可维护性与提高阅读性,于是Db可能在某些情况会解决这一问题。
Db是一个静态工具类,方法中大多需要传入映射的类的类型,如下
Db类和IService提供的方法基本上一样,就是多了一个参数,类型参数
举例:
@Override
public UserVO getUserAndAddressById(Long id) {
//先查用户
User user = this.getById(id);
if (user == null || user.getStatus() == 2){
throw new RuntimeException("用户不存在或者用户已被冻结");
}
//再查询用户的地址
List<Address> list = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, id)
.list();
//封装VO
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
if (CollUtil.isNotEmpty(list)){
userVO.setAddressVOS(BeanUtil.copyToList(list, AddressVO.class));
}
return userVO;
}
(注意!!!Db静态工具类是MyBatis-Plus较高版本才有的功能,老版本没有的)
3-2 逻辑删除
在开发中,订单模块很常见,对于一个用户而言,用户可以选择删除这个订单,但是在商家这里,每一个订单都是非常重要的,不能删除,所以就有了逻辑删除,我们可以给一个表一个字段为deleted,如果deleted是0表示未删除,1表示逻辑删除,我们不能对已经逻辑删除的信息进行修改,于是在每一个接口中,都需要加一个where deleted = 0这样的条件;mybatis-plus解决了这个麻烦的操作,只需要做相关配置。
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted #全局逻辑删除实体字段名,字段类型可以是boolean integer
logic-delete-value: 1 #逻辑已删除值(默认为1)
logic-not-delete-value: 0 #逻辑未删除值(默认为0)
3-3 枚举类型与数据库字段名之间的转换
1.引入配置
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
2.加入@EnumValue注解在枚举类中与数据库像转换的枚举项上
@Getter
public enum StatusEnum {
NORMAL(1,"正常"),
FREEZE(2,"冻结")
;
@EnumValue
private final int value;
// mvc默认在枚举转换中返回给前端的是枚举项名称,也就是NORMAL,加入这个注解后,返回的是desc代表的字符串
@JsonValue
private final String desc;
StatusEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
3-4 MyBatis-Plus提供的分页插件
首先对Mybatis-plus进行配置
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//1.初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//2.添加分页插件
PaginationInnerInterceptor pageInterceptor
= new PaginationInnerInterceptor(DbType.MYSQL);
pageInterceptor.setMaxLimit(1000L); //设置分页上限
interceptor.addInnerInterceptor(pageInterceptor);
return interceptor;
}
}
举例:
// 1.查询
int pageNumber = 1, pageSize = 5;
// 2.分页参数
Page<User> page = Page.of(pageNumber, pageSize);
// 3.排序参数,如果第一个满足了,那就再用第二个进行排序
page.addOrder(new OrderItem("username", false));
page.addOrder(new OrderItem("amount", true));
// 4.分页查询
Page<User> p = userService.page(page,
new LambdaQueryWrapper<User>().like(User::getUsername, "张"));
// 5.查看总条数和总页数
System.out.println("总条数是" + p.getTotal() + "\r\n" + "总页数是" + p.getPages());
// 6. 分页数据
List<User> records = p.getRecords();
records.forEach(System.out::println);