刚才的案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能了。
1.条件构造器
除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id
作为where
条件以外,还支持更加复杂的where
条件。
Wrapper
的子类AbstractWrapper
提供了where中包含的所有条件构造方法:
而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:
而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:
接下来,我们就来看看如何利用Wrapper
实现复杂查询。
1.1QueryWrapper
无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件。接下来看一些例子: 查询:查询出名字中带o
的,存款大于等于1000元的人。代码如下:
@Test void testQueryWrapper() { // 1.构建查询条件 where name like "%o%" AND balance >= 1000 QueryWrapper<User> wrapper = new QueryWrapper<User>() .select("id", "username", "info", "balance") .like("username", "o") .ge("balance", 1000); // 2.查询数据 List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
更新:更新用户名为jack的用户的余额为2000,代码如下:
@Test void testUpdateByQueryWrapper() { // 1.构建查询条件 where name = "Jack" QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack"); // 2.更新数据,user中非null字段都会作为set语句 User user = new User(); user.setBalance(2000); userMapper.update(user, wrapper); }
1.2UpdateWrapper
基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。 例如:更新id为1,2,4
的用户的余额,扣200,对应的SQL应该是:
UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)
SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能了:
@Test void testUpdateWrapper() { List<Long> ids = List.of(1L, 2L, 4L); // 1.生成SQL UpdateWrapper<User> wrapper = new UpdateWrapper<User>() .setSql("balance = balance - 200") // SET balance = balance - 200 .in("id", ids); // WHERE id in (1, 2, 4) // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据, // 而是基于UpdateWrapper中的setSQL来更新 userMapper.update(null, wrapper); }
1.3LambdaQueryWrapper
无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,会出现字符串魔法值
。这在编程规范中显然是不推荐的。 那怎么样才能不写字段名,又能知道字段名呢?
其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:
- LambdaQueryWrapper
- LambdaUpdateWrapper
分别对应QueryWrapper和UpdateWrapper
其使用方式如下:
@Test void testLambdaQueryWrapper() { // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.lambda() .select(User::getId, User::getUsername, User::getInfo, User::getBalance) .like(User::getUsername, "o") .ge(User::getBalance, 1000); // 2.查询 List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
2.自定义SQL
在演示UpdateWrapper的案例中,我们在代码中编写了更新的SQL语句:
这种写法在某些企业也是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL。 这实在是太麻烦了。假如查询条件更复杂,动态SQL的编写也会更加复杂。
所以,MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL
2.1基本用法
以当前案例来说,我们可以这样写:
@Test void testCustomWrapper() { // 1.准备自定义查询条件 List<Long> ids = List.of(1L, 2L, 4L); QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids); // 2.调用mapper的自定义方法,直接传递Wrapper userMapper.deductBalanceByIds(200, wrapper); }
然后在UserMapper中自定义SQL:
public interface UserMapper extends BaseMapper<User> { @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}") void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper); }
这样就省去了编写复杂查询条件的烦恼了。
2.2多表关联
理论上来讲MyBatisPlus是不支持多表查询的,不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。 例如,我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL,大概是这样的:
<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User"> SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id WHERE u.id <foreach collection="ids" separator="," item="id" open="IN (" close=")"> #{id} </foreach> AND a.city = #{city} </select>
可以看出其中最复杂的就是WHERE条件的编写,如果业务复杂一些,这里的SQL会更变态。
但是基于自定义SQL结合Wrapper的玩法,我们就可以利用Wrapper来构建查询条件,然后手写SELECT及FROM部分,实现多表查询。
查询条件这样来构建:
@Test void testCustomJoinWrapper() { // 1.准备自定义查询条件 QueryWrapper<User> wrapper = new QueryWrapper<User>() .in("u.id", List.of(1L, 2L, 4L)) .eq("a.city", "北京"); // 2.调用mapper的自定义方法 List<User> users = userMapper.queryUserByWrapper(wrapper); users.forEach(System.out::println); }
然后在UserMapper中自定义方法:
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}") List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
当然,也可以在UserMapper.xml
中写SQL:
<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User"> SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment} </select>
3.Service接口
MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService
,默认实现为ServiceImpl
,其中封装的方法可以分为下几类:
save
:新增remove
:删除update
:更新get
:查询单个结果list
:查询集合结果
count
:计数page
:分页查询
3.1CRUD
我们先俩看下基本的CRUD接口。 新增:
save
是新增单个元素saveBatch
是批量新增saveOrUpdate
是根据id判断,如果数据存在就更新,不存在则新增saveOrUpdateBatch
是批量的新增或修改
删除:
removeById
:根据id删除removeByIds
:根据id批量删除removeByMap
:根据Map中的键值对为条件删除remove(Wrapper<T>)
:根据Wrapper条件删除
~~removeBatchByIds~~
:暂不支持
修改:
updateById
:根据id修改update(Wrapper<T>)
:根据UpdateWrapper
修改,Wrapper
中包含set
和where
部分update(T,Wrapper<T>)
:按照T
内的数据修改与Wrapper
匹配到的数据
updateBatchById
:根据id批量修改
Get:
getById
:根据id查询1条数据getOne(Wrapper<T>)
:根据Wrapper
查询1条数据getBaseMapper
:获取Service
内的BaseMapper
实现,某些时候需要直接调用Mapper
内的自定义SQL
时可以用这个方法获取到Mapper
List:
listByIds
:根据id批量查询list(Wrapper<T>)
:根据Wrapper条件查询多条数据list()
:查询所有
Count:
count()
:统计所有数量count(Wrapper<T>)
:统计符合Wrapper
条件的数据数量
getBaseMapper: 当我们在service中要调用Mapper中自定义SQL时,就必须获取service对应的Mapper,就可以通过这个方法: