MybatisPlus核心功能

1.条件构造器

之前学习的MybatisPlus只是简单的根据id(主键)去进行简单的增删改查,现在使用条件构造器可以自定义语句。

1.1 条件构造器方法介绍

条件构造器的具体用法可以查看MP的官方网站

MP条件构造器

1.2 案例

1.2.1 需求

查询出名字中带o的,存款大于等于1000元的人的id,username,info,balance字段(表的创建以及结构可以查看MP的基本使用

如果使用sql语句,应该是下面的sql

1
select id, username, info, balance from user where username like ? and balance >= ?

更新用户名为jack的用户余额为2000

如果使用sql语句,应该是下面的sql

1
update user set balance = 2000 where username = 'jack'

更新id为1,2,4的用户的余额扣除200

1
update user set balance = balance - 200 where id in (1,2,4)

1.2.2 使用MP构造器

1.查询出名字中带o的,存款大于等于1000元的人的id,username,info,balance字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
void testQueryWrapper(){
// 构件查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
// 选择要查询的内容
.select("id","username","info","balance")
// like即为模糊查询 即 "%0%"
.like("username","0")
// ge 即 >=
.ge("balance",1000);
// 查询
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

2.更新用户名为jack的用户余额为2000

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testUpdateUser(){
// 准备需要改变的实体类 设置Balance为2000
User user = new User();
user.setBalance(2000);
// 准备查询条件 username = 'jack'
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.eq("username","jack");
// update 需要传递两个参数 一个是用于赋值的实体类 一个是wrapper条件
int update = userMapper.update(user, wrapper);
System.out.println(update);
}

3.更新id为1,2,4的用户的余额扣除200

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testUpdateWrapper(){
// 准备更新的id列表 因为in()需要指定要更改的字段以及列表
List<Long> ids = new ArrayList<>();
// jdk9 可以使用 List<Long> ids = List.of(1L,2L,3L);
ids.add(1L);
ids.add(2L);
ids.add(3L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
// 手写sql setsql()方法
.setSql("balance = balance - 200")
// in() 两个参数 第一个参数是字段名 第二个参数是列表
.in("id",ids);
// 执行更新操作
userMapper.update(null,wrapper);
}

但是上述的写法术语硬编码,具有大量的魔法值,不推荐。因此在开发中经常使用lambda语法

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testLambdaWrapper(){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
// 使用User::method来获取属性名
.select(User::getId,User::getUsername,User::getInfo,User::getBalance)
.like(User::getUsername,"o")
.ge(User::getBalance,1000);
// 查询
List<User> users = userMapper.selectList(wrapper);
System.out.println(users);
}

1.2.3 总结

条件构造器的用法

  • QueryWrapper和LambdaQueryWrapper通常用来构建select,delete,update和where条件部分
  • UpdateWrapper和LambdaUpdateWrapper通常只在set语句比较特殊才使用
  • 尽量使用LambdaQueryWrapper和LamdaUpdateWrapper,避免硬编码,魔法值

2.自定义sql

我们可以利用MyBatisPlus的Wrapper来构建复杂的Where条件,然后自己定义SQL语句中剩下的部分。

2.1 需求

将id在指定范围的用户(例如1,2,4)的余额扣减指定值

如果自己写sql:

1
2
3
4
5
6
7
<update id="updateBalanceByIds">   
UPDATE user
SET balance = balance - #{amount}
WHERE id IN
<foreach collection="ids" separator="," item="id" open="(" close=")"> #{id} </foreach>
</update>

2.2 使用MP完成sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testUpdateWrapper(){
// 准备更新的id列表 因为in()需要指定要更改的字段以及列表
List<Long> ids = new ArrayList<>();
// jdk9 可以使用 List<Long> ids = List.of(1L,2L,3L);
ids.add(1L);
ids.add(2L);
ids.add(3L);
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
// 手写sql setsql()方法
.setSql("balance = balance - 200")
// in() 两个参数 第一个参数是字段名 第二个参数是列表
.in("id",ids);
// 执行更新操作
userMapper.update(null,wrapper);
}

MP更擅长的是where语句的编写

MP擅长where语句的编写,但是对于select部分,MP无法实现复杂的select,MP只能简单的查询某个或某些字段,但是无法实现查询count()或者起别名等操作,于是就出现了自定义sql。用户自己编写select部分,where部分交给MP完成

2.3 使用步骤

  • 基于Wrapper构建where条件
  • 在mapper方法参数中使用Param注解声明wrapper变量名称,必须是ew
  • 自定义sql,并使用Wrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testCustomSqlUpdate(){
// 准备更新的id列表 因为in()需要指定要更改的字段以及列表
List<Long> ids = new ArrayList<>();
ids.add(1L);
ids.add(2L);
ids.add(3L);
int amount = 200;
// 自定义条件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("id",ids);
// 调用自定义方法
userMapper.updateBalanceByIds(wrapper,amount);
}

在UserMapper中书写自定义方法

1
void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);

其中QueryWrapper前面必须加上注解@Param(“ew”)或者@Param(Constants.WRAPPER)

在xml文件中编写select部分,where部分使用${ew.customSqlsegment}代替

1
2
3
<update id="updateBalanceByIds">
update user set balance = balance - #{amount} ${ew.customSqlSegment}
</update>

3.IService接口

3.1 IService接口总结

IService接口总结

3.2 IService的使用

在使用Mybatis进行开发的过程中,通常是先创建UserService接口,书写方法,然后创建UserServiceImpl实现UserService接口,然后实现方法。但是现在使用MP的IService后,使用UserService接口继承IService接口,这样以来UserServiceImpl就需要实现IService的所有方法,如果不实现所有的方法会爆粗,因此我们需要使用UserServiceImpl去继承ServiceImp,因为ServiceImpl是IService的实现类。

定义接口,接口需要继承IService接口

1
2
public interface IUserService extends IService<User> {
}

定义接口实现类,实现类需要实现IUserService以及继承IService的实现类ServiceImpl,其中ServiceImpl需要指定两个泛型,第一个泛型是Service实现类所需要用到的Mapper,这里是UserMapper,第二个所需的是操作的实体类,这里是User

1
2
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

添加测试类

添加测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void addUser(){
User user = new User();
user.setUsername("jerry");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());

boolean save = iUserService.save(user);
System.out.println(save);
}

查询测试

1
2
3
4
5
@Test
public void testQueryUser(){
User byId = iUserService.getById(1);
System.out.println(byId);
}

3.3 总结

MP的Service接口的使用流程

  • 自定义Service接口继承IService接口
  • 自定义接口实现类实现自定义接口并继承ServiceImpl实现类
  • 指定实现类泛型<所使用到的Mapper,所操作的实体类>

4.IService的Lambda查询

4.1 需求

需求:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空
  • status:用户状态,可以为空
  • minBalance:最小余额,可以为空
  • maxBalance:最大余额,可以为空

如果使用mybatis原生的写法,则是下面的语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="queryUsers" resultType="com.itheima.mp.domain.po.User">    
SELECT *
FROM tb_user
<where>
<if test="name != null">
AND username LIKE #{name}
</if>
<if test="status != null">
AND `status` = #{status}
</if>
<if test="minBalance != null and maxBalance != null">
AND balance BETWEEN #{minBalance} AND #{maxBalance}
</if>
</where>
</select>

上述的sql还是进行了简化的,因为minBalance和maxBalance也可能一个为null,一个不为null

需求:改造根据id修改用户余额的接口,要求如下

  • 完成对用户状态的检验
  • 完成对用户余额的校验
  • 如果扣减后余额为0,则将用户status修改为冻结状态(2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void deductBalance(Long id, Integer money) {
// 查询用户
User user = userMapper.selectById(id);
// 校验用户状态
if (user == null || user.getStatus() == 2){
// 用户有问题
throw new RuntimeException("用户状态异常");
}
// 检验用户金额是否充足
if (user.getBalance() < money){
throw new RuntimeException("用户余额不足");
}
// 扣减金额
userMapper.deductBalance(id,money);
}

之前实现过这个方法,现在需要添加如果扣减后余额为0,则将用户status修改为冻结状态

4.2 MP实现

4.2.1 需求一实现

首先在IUserService中定义方法

1
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);

然后在UserService中实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 复杂sql的实现
* @param name
* @param status
* @param minBalance
* @param maxBalance
* @return
*/
@Override
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return 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)
// 查询一个就one() 集合就list() 数量count() 分页page()
.list();
}

这里需要额外提出的一点是实现类中能够直接使用IService提供的方法,因为UserService继承了ServiceImpl,因此能够使用IService的方法,比如list(),IService会通过调用BaseMapper去实现方法.

4.2.2 需求二实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public void deductBalance(Long id, Integer money) {
// 查询用户
User user = userMapper.selectById(id);
// 校验用户状态
if (user == null || user.getStatus() == 2){
// 用户有问题
throw new RuntimeException("用户状态异常");
}
// 检验用户金额是否充足
if (user.getBalance() < money){
throw new RuntimeException("用户余额不足");
}
// 计算剩余余额
int remainBalance = user.getBalance() - money;
// 扣减金额
lambdaUpdate()
.set(User::getBalance,remainBalance)
.set(remainBalance == 0,User::getStatus,2)
.eq(User::getId,id)
.update();
}

这里需要注意的是在最后需要添加update(),因为之前只是构建sql,只有添加了update()方法后才是执行sql

上述的方法可能会出现一定的线程安全问题,因此最好需要上锁

5.IService批量新增

5.1 需求

  • 普通for循环插入
  • IService的批量插入
  • 开启rewriteBatchedStatements=true参数

5.2 测试

5.2.1 普通for循环插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private User buildUsers(int i){
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (18688190000L + i));
user.setBalance(2000);
user.setInfo("test");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
}

@Test
public void testSaveOneByOne(){
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
userService.save(buildUsers(i));
}
long end = System.currentTimeMillis();
System.out.println("耗时 " + (end - start));
}

消耗的时间为:210005

5.2.2 IService的批量插入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testSaveBatch(){
// 我们每次批量插入1000条数据 插入100次即10万条数据
// 准备一个容量为1000的集合
List<User> list = new ArrayList<>(1000);
long start = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
// 添加一个user
list.add(buildUsers(i));
// 每1000条批量插入一次
if (i % 1000 == 0){
userService.saveBatch(list);
list.clear();
}
}
}

消耗的时间为26258,效率提升接近十倍.

虽然使用了IService的批处理,但是实际上还是一条一条插入的,因此需要在application.yaml的url中添加参数:rewriteBatchedStatements=true

最后消耗的时间为:6200,效率极大的提升,原因如下:

testSaveOneByOne()方法中,通过循环调用userService.save()方法逐条插入数据。每次插入都会执行一次SQL语句和数据库操作,这会带来较大的性能开销。

而在testSaveBatch()方法中,我们使用了saveBatch()方法,该方法是MyBatis-Plus提供的批量插入数据的方法。通过将多个实体对象一次性传入,可以减少与数据库的交互次数,从而提高性能。具体实现原理是,MyBatis-Plus会根据底层数据库的不同,选择合适的方式进行批量插入,如使用MySQL的INSERT INTO ... VALUES (),(),...语法进行批量插入。

总结起来,使用IService接口的saveBatch()方法能够提高效率的原因是减少了与数据库的交互次数,通过一次性插入多条数据来降低开销.

5.3 总结

  • 普通for循环逐条插入速度极差,不推荐
  • MP的批量新增,基于预编译的批处理,性能不错
  • 配置jdbc参数,开rewriteBatchedStatements,性能最好
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights ©本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处! yang
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信