建设网站报告,重庆建筑信息网官网,网页版传奇世界什么组合最好,wordpress 教垜MybatisPlus 快速入门 常见注解 配置_软工菜鸡的博客-CSDN博客 2.核心功能 刚才的案例中都是以id为条件的简单CRUD#xff0c;一些复杂条件的SQL语句就要用到一些更高级的功能了。 2.1.条件构造器 除了新增以外#xff0c;修改、删除、查询的SQL语句都需要指定where条件。因此… MybatisPlus 快速入门 常见注解 配置_软工菜鸡的博客-CSDN博客 2.核心功能 刚才的案例中都是以id为条件的简单CRUD一些复杂条件的SQL语句就要用到一些更高级的功能了。 2.1.条件构造器 除了新增以外修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外还支持更加复杂的where条件。 参数中的Wrapper就是条件构造的抽象类其下有很多默认实现继承关系如图 Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法 而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法允许指定查询字段 而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法允许指定SQL中的SET部分 接下来我们就来看看如何利用Wrapper实现复杂查询。 2.1.1.QueryWrapper 无论是修改、删除、查询都可以使用QueryWrapper来构建查询条件。接下来看一些例子 查询查询出名字中带o的存款大于等于1000元的人。代码如下 Test
void testQueryWrapper() {// 1.构建查询条件 where name like %o% AND balance 1000QueryWrapperUser wrapper new QueryWrapperUser().select(id, username, info, balance).like(username, o).ge(balance, 1000);// 2.查询数据ListUser users userMapper.selectList(wrapper);users.forEach(System.out::println);
} 更新更新用户名为jack的用户的余额为2000代码如下 Test
void testUpdateByQueryWrapper() {// 1.构建查询条件 where name JackQueryWrapperUser wrapper new QueryWrapperUser().eq(username, Jack);// 2.更新数据user中非null字段都会作为set语句User user new User();user.setBalance(2000);userMapper.update(user, wrapper);
} 2.1.2.UpdateWrapper 基于BaseMapper中的update方法更新时只能直接赋值对于一些复杂的需求就难以实现。 例如更新id为1,2,4的用户的余额扣200对应的SQL应该是 UPDATE user SET balance balance - 200 WHERE id in (1, 2, 4) SET的赋值结果是基于字段现有值的这个时候就要利用UpdateWrapper中的setSql功能 来-200了 Test
void testUpdateWrapper() {ListLong ids List.of(1L, 2L, 4L);// 1.生成SQLUpdateWrapperUser wrapper new UpdateWrapperUser().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);
} 2.1.3.LambdaQueryWrapper 无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称会出现字符串魔法值。这在编程规范中显然是不推荐的。 那怎么样才能不写字段名又能知道字段名呢 其中一种办法是基于变量的gettter方法结合反射技术。因此我们只要将条件对应的字段的getter方法传递给MybatisPlus它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper包含两个 LambdaQueryWrapperLambdaUpdateWrapper 分别对应QueryWrapper和UpdateWrapper 其使用方式如下 Test
void testLambdaQueryWrapper() {// 1.构建条件 WHERE username LIKE %o% AND balance 1000QueryWrapperUser wrapper new QueryWrapper();wrapper.lambda().select(User::getId, User::getUsername, User::getInfo, User::getBalance).like(User::getUsername, o).ge(User::getBalance, 1000);// 2.查询ListUser users userMapper.selectList(wrapper);users.forEach(System.out::println);
} 2.2.自定义SQL 在演示UpdateWrapper的案例中我们在代码中编写了更新的SQL语句 这种写法在某些企业也是不允许的因为SQL语句最好都维护在持久层而不是业务层。就当前案例来说由于条件是in语句只能将SQL写在Mapper.xml文件利用foreach来生成动态SQL。 这实在是太麻烦了。假如查询条件更复杂动态SQL的编写也会更加复杂。 所以MybatisPlus提供了自定义SQL功能可以让我们利用Wrapper生成查询条件再结合Mapper.xml编写SQL 2.2.1.基本用法 以当前案例来说我们可以这样写 Test
void testCustomWrapper() {// 1.准备自定义查询条件ListLong ids List.of(1L, 2L, 4L);QueryWrapperUser wrapper new QueryWrapperUser().in(id, ids);// 2.调用mapper的自定义方法直接传递WrapperuserMapper.deductBalanceByIds(200, wrapper);
} 然后在UserMapper中自定义SQL public interface UserMapper extends BaseMapperUser {Select(UPDATE user SET balance balance - #{money} ${ew.customSqlSegment})void deductBalanceByIds(Param(money) int money, Param(ew) QueryWrapperUser wrapper);
} 这样就省去了编写复杂查询条件的烦恼了。 2.2.2.多表关联 理论上来将MyBatisPlus是不支持多表查询的不过我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。 例如我们要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL大概是这样的 select idqueryUserByIdAndAddr resultTypecom.itheima.mp.domain.po.UserSELECT *FROM user uINNER JOIN address a ON u.id a.user_idWHERE u.idforeach collectionids separator, itemid openIN ( close)#{id}/foreachAND a.city #{city}/select 可以看出其中最复杂的就是WHERE条件的编写如果业务复杂一些这里的SQL会更变态。但是基于自定义SQL结合Wrapper的玩法我们就可以利用Wrapper来构建查询条件然后手写SELECT及FROM部分实现多表查询。 查询条件这样来构建 Test
void testCustomJoinWrapper() {// 1.准备自定义查询条件QueryWrapperUser wrapper new QueryWrapperUser().in(u.id, List.of(1L, 2L, 4L)).eq(a.city, 北京);// 2.调用mapper的自定义方法ListUser 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})
ListUser queryUserByWrapper(Param(ew)QueryWrapperUser wrapper); 当然也可以在UserMapper.xml中写SQL select idqueryUserByIdAndAddr resultTypecom.itheima.mp.domain.po.UserSELECT * FROM user u INNER JOIN address a ON u.id a.user_id ${ew.customSqlSegment}
/select 2.3.Service接口 关于mybatis-plus中Service和Mapper的分析 MybatisPlus不仅提供了BaseMapper还提供了通用的Service接口及默认实现封装了一些常用的service模板方法。 通用接口为IService默认实现为ServiceImpl其中封装的方法可以分为几类 save新增remove删除update更新get查询单个结果list查询集合结果count计数page分页查询 2.3.1.CRUD 我们先俩看下基本的CRUD接口。 新增 save是新增单个元素saveBatch是批量新增saveOrUpdate是根据id判断如果数据存在就更新不存在则新增saveOrUpdateBatch是批量的新增或修改 删除 removeById根据id删除removeByIds根据id批量删除removeByMap根据Map中的键值对为条件删除remove(WrapperT)根据Wrapper条件删除~~removeBatchByIds~~暂不支持 修改 updateById根据id修改update(WrapperT)根据UpdateWrapper修改Wrapper中包含set和where部分update(TWrapperT)按照T内的数据修改与Wrapper匹配到的数据updateBatchById根据id批量修改 Get getById根据id查询1条数据getOne(WrapperT)根据Wrapper查询1条数据getBaseMapper获取Service内的BaseMapper实现某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper List listByIds根据id批量查询list(WrapperT)根据Wrapper条件查询多条数据list()查询所有 Count count()统计所有数量count(WrapperT)统计符合Wrapper条件的数据数量 getBaseMapper 当我们在service中要调用Mapper中自定义SQL时就必须获取service对应的Mapper就可以通过这个方法 2.3.2.基本用法 由于Service中经常需要定义与业务有关的自定义方法因此我们不能直接使用IService而是自定义Service接口然后继承IService以拓展方法。同时让自定义的Service实现类继承ServiceImpl这样就不用自己实现IService中的接口了。 首先定义UserService继承IService package com.itheima.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;public interface UserService extends IServiceUser {// 拓展自定义方法
} 然后编写UserServiceImpl类继承ServiceImpl实现UserService package com.itheima.mp.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.UserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;Service
public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService {
} 项目结构如下 最后编写一个测试类测试一下 package com.itheima.mp.service;import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.util.List;import static org.junit.jupiter.api.Assertions.*;SpringBootTest
class UserServiceTest {AutowiredUserService userService;Testvoid testService() {ListUser list userService.list();list.forEach(System.out::println);}
} 2.3.3.批量新增 IService中的批量新增功能使用起来非常方便但有一点注意事项我们来测试一下。 首先我们测试逐条插入数据 Test
void testSaveOneByOne() {long b System.currentTimeMillis();for (int i 1; i 100000; i) {userService.save(buildUser(i));}long e System.currentTimeMillis();System.out.println(耗时 (e - b));
}private User buildUser(int i) {User user new User();user.setUsername(user_ i);user.setPassword(123);user.setPhone( (18688190000L i));user.setBalance(2000);user.setInfo({\age\: 24, \intro\: \英文老师\, \gender\: \female\});user.setCreateTime(LocalDateTime.now());user.setUpdateTime(user.getCreateTime());return user;
} 执行结果如下 可以看到速度非常慢。 然后再试试MybatisPlus的批处理 Test
void testSaveBatch() {// 准备10万条数据ListUser list new ArrayList(1000);long b System.currentTimeMillis();for (int i 1; i 100000; i) {list.add(buildUser(i));// 每1000条批量插入一次if (i % 1000 0) {userService.saveBatch(list);list.clear();}}long e System.currentTimeMillis();System.out.println(耗时 (e - b));
} 执行最终耗时如下 可以看到使用了批处理以后比逐条新增效率提高了10倍左右性能还是不错的。 不过我们简单查看一下MybatisPlus源码 Transactional(rollbackFor Exception.class)
Override
public boolean saveBatch(CollectionT entityList, int batchSize) {String sqlStatement getSqlStatement(SqlMethod.INSERT_ONE);return executeBatch(entityList, batchSize, (sqlSession, entity) - sqlSession.insert(sqlStatement, entity));
}
// ...SqlHelper
public static E boolean executeBatch(Class? entityClass, Log log, CollectionE list, int batchSize, BiConsumerSqlSession, E consumer) {Assert.isFalse(batchSize 1, batchSize must not be less than one);return !CollectionUtils.isEmpty(list) executeBatch(entityClass, log, sqlSession - {int size list.size();int idxLimit Math.min(batchSize, size);int i 1;for (E element : list) {consumer.accept(sqlSession, element);if (i idxLimit) {sqlSession.flushStatements();idxLimit Math.min(idxLimit batchSize, size);}i;}});
} 可以发现其实MybatisPlus的批处理是基于PrepareStatement的预编译模式然后批量提交最终在数据库执行时还是有多条insert语句逐条插入数据。SQL类似这样 Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
Parameters: user_1, 123, 18688190001, , 2000, 2023-07-01, 2023-07-01
Parameters: user_2, 123, 18688190002, , 2000, 2023-07-01, 2023-07-01
Parameters: user_3, 123, 18688190003, , 2000, 2023-07-01, 2023-07-01 而如果想要得到最佳性能最好是将多条SQL合并为一条像这样 INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES
(user_1, 123, 18688190001, , 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, , 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, , 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, , 2000, 2023-07-01, 2023-07-01); 该怎么做呢 MySQL的客户端连接参数中有这样的一个参数rewriteBatchedStatements。顾名思义就是重写批处理的statement语句。参考文档 cj-conn-prop_rewriteBatchedStatements 这个参数的默认值是false我们需要修改连接参数将其配置为true 修改项目中的application.yml文件在jdbc的url后面添加参数rewriteBatchedStatementstrue: spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicodetruecharacterEncodingUTF-8autoReconnecttrueserverTimezoneAsia/ShanghairewriteBatchedStatementstruedriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123 再次测试插入10万条数据可以发现速度有非常明显的提升 在ClientPreparedStatement的executeBatchInternal中有判断rewriteBatchedStatements值是否为true并重写SQL的功能 最终SQL被重写了 2.3.4.Lambda Service中对LambdaQueryWrapper和LambdaUpdateWrapper的用法进一步做了简化。我们无需自己通过new的方式来创建Wrapper而是直接调用lambdaQuery和lambdaUpdate方法 基于Lambda查询 Test
void testLambdaQuery() {// 1.查询1个User rose userService.lambdaQuery().eq(User::getUsername, Rose).one(); // .one()查询1个System.out.println(rose rose);// 2.查询多个ListUser users userService.lambdaQuery().like(User::getUsername, o).list(); // .list()查询集合users.forEach(System.out::println);// 3.count统计Long count userService.lambdaQuery().like(User::getUsername, o).count(); // .count()则计数System.out.println(count count);
} 可以发现lambdaQuery方法中除了可以构建条件而且根据链式编程的最后一个方法来判断最终的返回结果可选的方法有 .one()最多1个结果.list()返回集合结果.count()返回计数结果 lambdaQuery还支持动态条件查询。比如下面这个需求 定义一个方法接收参数为username、status、minBalance、maxBalance参数可以为空。 如果username参数不为空则采用模糊查询; 如果status参数不为空则采用精确匹配 如果minBalance参数不为空则余额必须大于minBalance 如果maxBalance参数不为空则余额必须小于maxBalance 这个需求就是典型的动态查询在业务开发中经常碰到实现如下 Test
void testQueryUser() {ListUser users queryUser(o, 1, null, null);users.forEach(System.out::println);
}public ListUser queryUser(String username, Integer status, Integer minBalance, Integer maxBalance) {return userService.lambdaQuery().like(username ! null , User::getUsername, username).eq(status ! null, User::getStatus, status).ge(minBalance ! null, User::getBalance, minBalance).le(maxBalance ! null, User::getBalance, maxBalance).list();
} 基于Lambda更新 Test
void testLambdaUpdate() {userService.lambdaUpdate().set(User::getBalance, 800) // set balance 800.eq(User::getUsername, Jack) // where username Jack.update(); // 执行Update
} lambdaUpdate()方法后基于链式编程可以添加set条件和where条件。但最后一定要跟上update()否则语句不会执行。 lambdaUpdate()同样支持动态条件例如下面的需求 基于IService中的lambdaUpdate()方法实现一个更新方法满足下列需求 1 参数为balance、id、username 2 id或username至少一个不为空根据id或username精确匹配用户 3 将匹配到的用户余额修改为balance 4 如果balance为0则将用户status修改为冻结状态2 实现如下 Test
void testUpdateBalance() {updateBalance(0L, 1L, null);
}public void updateBalance(Long balance, Long id, String username){userService.lambdaUpdate().set(User::getBalance, balance).set(balance 0, User::getStatus, 2).eq(id ! null, User::getId, id).eq(username ! null, User::getId, username).update();
} 2.4.静态工具 有的时候Service之间也会相互调用为了避免出现循环依赖问题可以调用Service 的mapper或者MybatisPlus提供一个静态工具类Db其中的一些静态方法与IService中方法签名基本一致也可以帮助我们实现CRUD功能 Db的静态方法与IService中方法区别除了save、update其他的参数带class类然后就知道要操作哪个表了 示例 Test
void testDbGet() {User user Db.getById(1L, User.class);System.out.println(user);
}Test
void testDbList() {// 利用Db实现复杂条件查询ListUser list Db.lambdaQuery(User.class).like(User::getUsername, o).ge(User::getBalance, 1000).list();list.forEach(System.out::println);
}Test
void testDbUpdate() {Db.lambdaUpdate(User.class).set(User::getBalance, 2000).eq(User::getUsername, Rose);
}