目录
1.MyBatis-Plus介绍
2.第一个MyBatis-Plus程序
3.配置日志输出
4. 主键生成策略
4.1 ID_WORKER -- 雪花算法
4.2 主键生成策略 -- 详情
5. 自动填充
6.MyBatis-Plus的CRUD
6.1 insert -- 插入
6.2 delete -- 删除
6.3 update -- 更新
6.4 select -- 查询
7. 逻辑删除插件
8. 乐观锁组件
9. 分页查询组件
10. 性能分析组件
11. wrapper条件查询器
12. 代码自动生成器
1.MyBatis-Plus介绍
看到plus我们应该大致能够了解到这是一个Mybatis的增加版本。相较于传统的MyBatis开发,它在诸多方面有了质的提升,例如:
- 提供了更为强大的CRUD策略,我们不必再去书写繁琐的sql语句;
- 内置分页插件,不必你再去手工limit;
- 性能分析插件、全局拦截插件、无侵入、损害小......
我们可以将MyBatis和MyBatis-Plus视为“彼此之间最好的搭档,正所谓:'基友搭配,效率翻倍!'”。接下来依赖来探索下MyBatis-Plus的魔力吧!
2.第一个MyBatis-Plus程序
学好一个框架的最优策略,我认为是先从整体上来学会如何正确的进行基础的运用,才能知其底层,继而行云流水,倒背如流。与传统的MyBatis发挥的作用类似,MyBatis-Plus作为一种持久层的与数据库进行交互的框架,我们得现有一张数据库表来进行操作。接下来我们来一块实现这个小程序:
3.配置日志输出
默认情况下,MyBatis-Plus的执行是不会在任何地方输出执行的日志信息的,而在开发中,为了更高效的发现和出现可能出现的运行问题,查看程序的日志是必不可少的,MyBatis-Plus通过以下配置项配置日志输出:
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
4. 主键生成策略
不知道大家有没有发现,在上边我们插入的数据中并没有设置插入数据的主键id信息,而我们现在来查看下数据库中插入的数据:
这就要谈谈MyBatis-Plus的主键生成策略了,默认情况下,它的主键生成策略是 ID_WORKER —— 全球唯一ID。这也是为什么会出现这个数字ID的原因。在每次插入数据时,Mybatis-Plus都会生成一个唯一的主键ID。那么Mybatis-Plus是如何保证这个ID的唯一性呢?
4.1 ID_WORKER -- 雪花算法
我们知道,生成随机值的方式有很多种,像UUID,Redis生成、Zookeeper生成等等,那为什么MyBatis-Plus要使用雪花算法来实现主键ID的生成呢?对了,唯一性!雪花算法能够在保证随机性的同时相较于其他的随机算法能够更加提高唯一ID生成的概率。接下来我们来看看雪花算法是如何生成一个随机唯一ID的:
4.2 主键生成策略 -- 详情
Mybatis-Plus提供了6种主键的生成策略:
默认情况下使用的主键生成策略是全局唯一ID(ID_WORKER生成),这个在上面已经体验过了。接下来我们来使用下它的AUTO自增生成和INPUT手动输入生成:
5. 自动填充
为了更好的学习MyBatis-Plus关于时间字段的生成策略,我们先修改一下表的结构:
在之前的项目开发中,对于数据库中相关时间字段的值的插入和更新,我往往是采用手动创建插入的方式来进行处理的,
后来阅读了阿里巴巴的开发者手册,我感觉到之前的处理方式效率太低了。为了解决这种问题,MyBatis-Plus为我们提供了数据库表中时间相关的字段的生成策略,来帮助我们更高效的实现持久层和数据库表之间的交互过程。 接下来一起来看下怎么实现吧:
6.MyBatis-Plus的CRUD
通过上面的学习,我们已经知道,Mybatis-Plus已经为我们实现好了CRUD的操作方法,那么对于不同方法的使用细节,复杂查询SQL的组装向条件查询,分页查询以及新增、修改删除数据应该如何去调用呢?我们知道,自己创建的mapper接口需要去继承MyBatis-Plus的BaseMapper<T>接口,而继承这个接口就是为了能够调用其中的接口方法继而动态绑定到这个持久层框架已经为我们提供好的操作方法中,我们来探索一下这个接口:
6.1 insert -- 插入
对于插入来说,很直接。我们只需要调用自己创建的继承自BaseMapper<T>接口中的insert方法并传入携带数据的实体对象即可完成,如下:
@Test @Transactional void testInsert() { User user = new User(); user.setAge(20); user.setName("白小帅"); user.setEmail("bxshuai@yeah.net"); userMapper.insert(user); //执行的sql //INSERT INTO user ( name, age, email, create_time, update_time ) VALUES ( '白小帅', 20, bxshuai@yeah.net, now(), now()); }
6.2 delete -- 删除
对于删除来说,这个框架给我们提供了很多种删除的方式,比如根据主键ID删除、根据指定条件删除等等,我们一起来看下:
@Test @Transactional void testDelete() { userMapper.deleteById(1679119918046838790L); //指定id删除 //执行的sql: delete from user where id=1679119918046838790L; // 或者 userMapper.deleteBatchIds(Arrays.asList(1, 2, 3)); //根据id批量删除 //执行的sql:delete from user where id in (1,2,3); // 或者 HashMap<String, Object> wrapperMap = new HashMap<>(); wrapperMap.put("name", "白小帅"); //构造where条件 userMapper.deleteByMap(wrapperMap); //执行的sql:delete from user where name='白小帅'; }
6.3 update -- 更新
@Test void testUpdate() { User user = new User(); user.setId(1679119918046838791L); user.setEmail("bxsxiao@sina.com"); userMapper.updateById(user); //执行的sql: //UPDATE user SET email=bxsxiao@sina.com, update_time=2023-07-13 15:16:49.166 WHERE id=1679119918046838791 }
6.4 select -- 查询
Mybatis-Plus的查询有很多种方式,我们总结了以下几种,其原理都是相同的,实现也类似。无非都是通过修改查询条件来实现的。
@Test void testSelect() { List<User> users = userMapper.selectList(null);//无条件查询所有数据 users.forEach(System.out::println); //执行的SQL:SELECT id,name,age,email,create_time,update_time FROM user User user = userMapper.selectById(2); System.out.println(user); //执行的SQL:SELECT id,name,age,email,create_time,update_time FROM user WHERE id=2; HashMap<String, Object> wrapperMap = new HashMap<>(); wrapperMap.put("name", "白小帅"); wrapperMap.put("email", "bxsxiao@sina.com"); List<User> users1 = userMapper.selectByMap(wrapperMap); users1.forEach(System.out::println); //执行的SQL:SELECT id,name,age,email,create_time,update_time FROM user WHERE name = '白小帅' AND email = 'bxsxiao@sina.com'; }
7. 逻辑删除插件
不知道大家有没有发现,csdn给我们提供了blog的回收站功能。包括还有像某些网站的上你删除的评论信息仍然能够被后台管理员看到。向这种功能的实现就称为数据的逻辑删除。与普通删除不同的是,普通删除直接将数据从数据库中移除了;而逻辑删除在数据库表中新增了一个逻辑删除字段,例如当这个逻辑删除字段值为0代表未删除,值为1代表已删除。
使用了Mybatis-Plus后,我们就不用再手动编写sql来实现逻辑删除了,让我们一起来看下MyBatis-Plus如何实现这种功能吧!
8. 乐观锁组件
这里的乐观锁和我们在前面学习的JUC中的乐观锁的概念相同,我们一块来回忆下什么是乐观锁和悲观锁:
- 乐观锁:通常认为不会出现并发问题,不会去真正的加锁。当出现问题时,就更新值重新测试。
- 悲观锁:通常总是认为可能出现问题,因此在进行任何操作时都进行加锁再去操作。
那么,MyBaits-Plus提供的乐观锁又是怎么实现的呢?我们又该如何编写代码使用呢?一起来学习下吧!
MyBatis-Plus提供的乐观锁的实现原理:
- 数据库表中要有一个版本号字段version;
- 取出数据时会带上这个version信息;
- 执行更新操作时会判断当前的version和之前获取的旧版本的version是否相同;
- 前后查询到的version相同就更新并修改版本号,否则更新失败。
通过上述的这些步骤,就可以避免并发环境下更新数据带来的覆盖问题。
MyBatis-Plus乐观锁的代码实现:
了解了乐观锁的实现原理,我们得现在数据库表中新增一个version字段信息:
接下来我们就来编写代码实现这一功能:
9. 分页查询组件
在之前的项目中,我们往往是通过接口前端传递的参数并计算然后结合数据库关键字limit来实现分页功能的,学习了MyBatis-Plus之后,就不用这么麻烦了!它为我们提供了一个非常好用的分页查询组件,在查询的时候只需要传入页码和每页的数据条数就可以了,大大提高了我们开发的效率!接下来一起来感受下组件分页带来的便利吧!
实现MyBatis-Plus的分页查询:
10. 性能分析组件
MyBatis-Plus也为我们提供了SQL执行的性能分析组件,比如我们可以通过配置该组件在开发或者测试的过程中来检测SQL的执行时间并做出优化等处理。接下来来记录下早Mybatis
-Plus中该如何注册并使用这个组件:
11. wrapper条件查询器
在上面我们的学习过程中,已经提到了wrapper条件构造器,它主要用来构造执行的SQL语句中的负责查询条件,这些条件又是通过map条件查询很难实现的,同时也通过api的调用更加简化了我们编写SQL语句的步骤,提高了开发效率。接下来一起来学习下wrapper构造器构造简单查询条件的实现,具体的使用可以参照:Mybatis-Plus官方文档
package com.shuai.mybatis_plus; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.shuai.mybatis_plus.entity.User; import com.shuai.mybatis_plus.mapper.UserMapper; 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 java.util.Map; @SpringBootTest public class WrapperTest { @Autowired private UserMapper userMapper; @Test public void wrapperTest1() { QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.select("id", "name"); /** * 设置只查询user表中的id,name字段 * 执行的SQL: * SELECT * id, * name * FROM * user * WHERE * deleted=0 */ userMapper.selectList(userQueryWrapper).forEach(System.out::println); } @Test public void wrapperTest2() { QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper .eq("name", "白小帅") .ge("id", 6); /** * 查询 name=白小帅 并且 id>=6 的用户信息 * 执行的SQL: * SELECT * id, * name, * age, * email, * version, * create_time, * update_time * FROM * user * WHERE * deleted=0 * AND name = '白小帅' * AND id >= 6 */ List<User> users = userMapper.selectList(userQueryWrapper); users.forEach(System.out::println); } @Test public void wrapperTest3() { QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper .gt("age", 16) .likeLeft("email", ".com"); /** * 查询年龄大于16,并且模糊匹配email字段为 .com% * 执行的SQL: * SELECT * COUNT( 1 ) * FROM * user * WHERE * deleted=0 * AND age > 16 * AND email LIKE '%.com' */ Integer count = userMapper.selectCount(userQueryWrapper); System.out.println(count); } @Test public void wrapperTest4() { QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper .between("age", "18", "28") .select("id", "name", "age") .in("id", 2, 3, 4) .orderByDesc("age"); /** * 查询年龄在18-28之间的用户,包含字段 id,name和 age 并且 id在2,3,4的用户信息,并降序排序 * 执行的SQL: * SELECT * id, * name, * age * FROM * user * WHERE * deleted=0 * AND age BETWEEN '18' AND '28' * AND id IN ( * 2,3,4 * ) * ORDER BY * age DESC */ List<Map<String, Object>> userMaps = userMapper.selectMaps(userQueryWrapper); userMaps.forEach(System.out::println); } }
ps:
在使用时注意wrapper对象的likeLeft和likeRight方法,likeLeft相当于模糊匹配的 ‘%’ 在左侧,likeRight相当于模糊匹配的 ‘%’在右侧,例如:
- userQueryWrapper.likeLeft('abc') 对应 SQL中模糊匹配 like '%abc'
- userQueryWrapper.likeRight('abc') 对应 SQL中模糊匹配 like 'abc%'
- userQueryWrapper.like('abc') 对应 SQL中模糊匹配 like '%abc%'
12. 代码自动生成器
前面学习到的Mybatis-Plus组件可以说是开胃菜,而这个代码自动生成器可以说是重头戏,学完这个组件你将会止不住惊叹。它能够为我们自动生成后端架构的Controller、Service乃至Entity和Mapper层的所有基础代码,快来一块学习下吧!
package com.shuai.mybatis_plus; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.po.TableFill; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.ArrayList; import java.util.Scanner; public class CodeGeneratorAuto { private static final String URL = "jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8"; private static final String USERNAME = "root"; private static final String PASSWORD = "BXS552ZXY"; private static final DbType DBTYPE = DbType.MYSQL; //自动生成代码的父目录 private static final String PACKAGE_PARENT_CONFIG = "com.shuai.mybatis_plus"; //设置逻辑删除字段名 private static final String LOGIC_FIELD_NAME = "deleted"; //设置乐观锁字段名 private static final String VERSION_FIELD_NAME = "version"; //设置自动填充属性名 private static final String AUTO_FILL_FIELD1 = "gmt_create"; //自动填充属性1 private static final FieldFill AUTO_FIELD1_FILL_STRATEGY = FieldFill.INSERT; //属性1自动填充策略 private static final String AUTO_FILL_FIELD2 = "gmt_modified"; //自动填充属性2 private static final FieldFill AUTO_FIELD2_FILL_STRATEGY = FieldFill.INSERT_UPDATE; //属性2自动填充策略 private static final String DB_DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver"; public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } public static void main(String[] args) { //创建代码生成器对象 AutoGenerator mpg = new AutoGenerator(); //全局配置 GlobalConfig gc = new GlobalConfig(); //不要导错包了:com.baomidou.mybatisplus.generator.AutoGenerator; String projectPath = System.getProperty("user.dir"); //获取项目路径 gc.setOutputDir(projectPath + "/src/main/java"); //设置自动生成代码的输出路径 gc.setAuthor(scanner("作者名")); //设置作者信息 gc.setOpen(false); //生成代码时不用打开文件资源管理器 gc.setFileOverride(false); // 是否覆盖 gc.setServiceName("%sService"); // 去Service的I前缀 gc.setIdType(IdType.ID_WORKER); //设置主键生成策略为自增 gc.setDateType(DateType.ONLY_DATE); //设置时间格式为Date mpg.setGlobalConfig(gc); //数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl(URL); dsc.setUsername(USERNAME); dsc.setPassword(PASSWORD); dsc.setDriverName(DB_DRIVER_CLASS_NAME); dsc.setDbType(DBTYPE); mpg.setDataSource(dsc); //包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(scanner("模块名")); pc.setParent(PACKAGE_PARENT_CONFIG); //设置每一层的包名 pc.setController("controller"); pc.setService("service"); pc.setMapper("mapper"); pc.setEntity("entity"); mpg.setPackageInfo(pc); //策略配置 StrategyConfig sc = new StrategyConfig(); sc.setNaming(NamingStrategy.underline_to_camel); //设置数据库表名映射到实体属性的命名策略为下划线转驼峰 sc.setColumnNaming(NamingStrategy.underline_to_camel); //设置数据库字段映射到实体属性的命名策略为下划线转驼峰,未指定按照naming执行 sc.setEntityLombokModel(true); //【实体】是否为lombok模型(默认 false) sc.setRestControllerStyle(true); //生成 @RestController 控制器 @Controller -> @RestController sc.setInclude(scanner("表名,多个英文逗号分割").split(",")); sc.setLogicDeleteFieldName(LOGIC_FIELD_NAME); //设置逻辑删除属性名 // 自动填充配置 TableFill gmtCreate = new TableFill(AUTO_FILL_FIELD1, AUTO_FIELD1_FILL_STRATEGY); TableFill gmtModified = new TableFill(AUTO_FILL_FIELD2, AUTO_FIELD2_FILL_STRATEGY); ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(gmtCreate); tableFills.add(gmtModified); sc.setTableFillList(tableFills); sc.setVersionFieldName(VERSION_FIELD_NAME); //设置乐观锁字段名 sc.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2 mpg.setStrategy(sc); mpg.execute(); //执行 } }