四、条件构造器wrapper
相关条件构造器
条件构造器:包含所有条件信息超详细。
eq:=
in:in ()
insql:in (sql)
like:like ‘%xx%’
强烈推荐使用lambda形式来筛选出指定字段!
4.1、QueryWrapper(独有select)
普通筛选字段以及where查询
介绍一下普通绑定属性以及lambda形式(推荐)绑定:
@SpringBootTest class MybatisplusexerApplicationTests { @Resource private UserMapper userMapper; @Test public void select() throws JsonProcessingException { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); //静态绑定形式 // queryWrapper .select("last_name","email") .eq("age",18); //lambda表达式(可动态绑定指定字段) => SELECT last_name AS lastName,email FROM user WHERE (age = ?) queryWrapper.lambda() .select(User::getLastName,User::getEmail) .eq(User::getAge,18); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); } }
子查询配合模糊查询
@Resource private UserMapper userMapper; @Test public void select() throws JsonProcessingException { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); //子查询 and 模糊查询 => SELECT id,last_name AS lastName,age,email FROM user WHERE (age IN (18,22) AND email LIKE ?) 默认多个条件时就是使用and连接! queryWrapper.lambda() .inSql(User::getAge,"18,22") //子查询 .like(User::getEmail,"baomidou.com"); //模糊查询 List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
4.2、UpdateWrapper(独有set方法)
更新指定记录的某个字段
强烈推荐使用lambda表达式:因为只要你对应实体类字段没有写错,就不会有问题其是动态的
@Resource private UserService userService; @Test public void select() throws JsonProcessingException { UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); //采用lambda表达式 => UPDATE user SET last_name=? WHERE (id = ?) updateWrapper.lambda() .set(User::getLastName,"changlu") //更新某个字段 .eq(User::getId,1); userService.update(updateWrapper);//配合service层 }
五、全局id生成策略
IdType选项
其他一系列配置
我们可以通过全局yml配置的形式来为主键id设置生成策略:
# mp配置 mybatis-plus: global-config: db-config: id-type: auto # 全局配置
单表指定字段设置:
@TableId(value = "id",type = IdType.AUTO)
注意点:若是设置id策略为自增,那么一定要在数据库的指定表中的主键id字段勾选自动递增,不然的话mp插入报异常,一定要注意下这个点。
六、逻辑删除
逻辑删除
逻辑删除:在指定的表中添加一个字段默认为1,当我们想要删除该表的某条记录时,将该字段标注为0表示删除,并不是真正的将该条记录给删除掉。
mp中配置逻辑删除十分简单:
表中添加任意一个字段,设置为int类型,默认1表示未删除。
实体类中对该字段添加@TableLogic注解,可以单独对该注解进行配置来描述未删除、已删除分别的状态表示。也可进行全局配置。
配置好注解后,我们使用mp给我们在mapper或service的增强方法的select与remove时都会执行逻辑删除操作!
详细过程
step1:表中添加字段,并且设置默认值为1
step2:可以进行局部配置(针对单表)或者全局配置(针对所有的表)
局部配置
@TableLogic(value = "1",delval = "0") //局部配置逻辑删除,未删除为1,删除为0 private Integer deleted;
全局配置
mybatis-plus: global-config: db-config: logic-delete-field: deleted # 配置逻辑删除的字段 logic-delete-value: 0 # 删除标注1 logic-not-delete-value: 1 # 未删除标注0
测试:我们来进行删除与查询操作
①插入
@Resource private UserService userService; @Test public void save() throws JsonProcessingException { User user = new User(null, "xiaotian", 18, "86547528@qq.com"); userService.save(user); }
由于我们对deleted设置了默认值,所以我们插入对应实体类时不设置该字段也会给我们自动添加上默认值1,并且其中id为自动生成。
②删除:也就是逻辑删除,一旦我们配置好@TableLogic执行删除操作就都是逻辑删!
@Test public void update() throws JsonProcessingException { //逻辑删除:删除刚刚新增的记录 userService.remove(new QueryWrapper<User>().lambda().eq(User::getLastName,"xiaotian")); }
mp的删除操作底层实际上就是对指定表示字段置为删除状态 ,也就是执行一个更新字段的操作!
③查询:设置逻辑删除的表在进行查询时,就会自动过滤掉已经是删除状态的记录
@Test public void select() throws JsonProcessingException { List<User> users = userMapper.selectList(null); users.forEach(System.out::println); }
额外说明:一般对于某个表设置逻辑删除时,正常层面业务中是不会执行物理删除操作的,若是想要进行物理删除操作呢,就只能我们来编写xml的sql文件来进行删除了!
七、自动填充
自动填充功能
对于数据库表,我们需要自动填充的字段包含:id, gmt_create, gmt_modified,后两个是创建与更新时间,创建是在插入时自动填充,而更新是在插入与修改时进行更新!
有两种方式来实现自动填充:
①数据库层面
对于日期字段可以设置默认值,在mysql5.7版本可以设置根据当前时间戳更新,而在5.5不能设置时间戳更新。
CURRENT_TIMESTAMP
设置好以后,进行插入或者更新时我们不传参则会进行自动更新时间戳!
②mp提供的自动填充
首先是指定自动填充字段设置注解
@TableField(fill = FieldFill.INSERT) private Date gmtCreate; @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified;
编写handler执行器,在进行插入与更新时来对指定标注了注解执行填充操作,插入时对两个字段都进行填充,更新时只对修改字段进行填充:
@Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); //第一个是更新的字段名;第二个为更新的值;第三个为指定的原对象 this.setFieldValByName("gmtCreate",new Date(),metaObject); this.setFieldValByName("gmtModified",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.setFieldValByName("gmtModified",new Date(),metaObject); } }
测试:
@Test public void save() throws JsonProcessingException { User user = new User(null, "xiaomei", 20, "86547528@qq.com"); userService.save(user); } @Test public void update() throws JsonProcessingException { User user = new User(null, "mining", 28, "86547528@qq.com"); userService.saveOrUpdate(user,new QueryWrapper<User>().lambda().eq(User::getLastName,"xiaomei")); }
下面是更新时的sql打印:
此时我们就通过在代码层面实现了自动填充的效果!而不是直接在数据库上进行操作。
八、执行 SQL 分析打印
执行 SQL 分析打印
效果:
配置
①引入依赖
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>
②yml配置数据源:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver # 设置为p6spy的驱动 # jdbc:p6spy:前缀包含p6spy url: jdbc:p6spy:mysql://127.0.0.1:3306/mybatisplusExer?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
③在resource目录下新建spy.properties文件:
#3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory #3.2.1以下使用或者不配置 #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系统记录 sql #appender=com.p6spy.engine.spy.appender.Slf4JLogger # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 实际驱动可多个 #driverlist=org.h2.Driver # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2
此时就已经配置完成,我们执行sql语句即可出现对应效果!
说明:在生产环境不建议进行分析配置,有可能会影响性能!
再介绍一个IDEA插件:Mybatis-log,对于执行的sql语句会在一个小窗口显示:
九、代码生成器(学习)
代码生成器、代码生成器配置
可看视频:图灵学院—mp的代码生成器讲解
引入依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> </dependency> <-- 辅助 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
效果:根据指定的数据库来进行代码自动生成
重点关注这几行:
25行设置指定数据库。 35行指定生成的目录文件名称。 36行指定的包路径,根据对应的项目。 54行,过滤数据库表的前缀,如一些t_blog、t_user我们在生成对应的实体类时是不需要这些前缀的此时就可以进行过滤。 public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir");//默认为当前工程路径,如D:\workspace\workspace_idea\mybatisplusexer gc.setOutputDir(projectPath + "/src/main/java");//设置输出文件路径 gc.setAuthor("changlu"); gc.setOpen(false);//代码生成是不是要打开文件夹 gc.setSwagger2(true); //生成Swagger2 注解 gc.setBaseResultMap(true);//每个mapper文件中都生成通用结果映射集 gc.setFileOverride(true);//下次生成文件时进行覆盖(不设置的话当进行第二次生成就会在同一个目录产生相同的文件) // gc.setEnableCache(true);//开启二级缓存 //设置对应文件名称 gc.setEntityName("%sEntity");//生成实体类文件名,如:%sEntity 生成 UserEntity gc.setMapperName("%sMapper");//生成dao,这里我们配置生成如 UserMapper gc.setMapperName("%sMapper");//生成mapper.xml文件,这里生成如 UserMapper.xml gc.setServiceName("%sService");//生成service接口 gc.setServiceImplName("%sServiceImpl");//生成service实现类 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); //生成的模块名,也就是生成目标指定文件名的路径下 // pc.setModuleName(scanner("模块名")); pc.setModuleName("blog"); pc.setParent("com.changlu.mybatisplusexer"); mpg.setPackageInfo(pc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel);//表名生成策略:下划线转驼峰,如pms_pro=>PmsPro strategy.setColumnNaming(NamingStrategy.underline_to_camel);//字段名生成策略,同上,如last_name=>lastName // strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); strategy.setEntityLombokModel(true);//支持lombok // strategy.setRestControllerStyle(true);//controller中设置@RestController // 公共父类 // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!"); //要生成的表名 多个用逗号分隔 若是想要按照前缀生成多张表:strategy.setLikeTable(new LikeTable("pms_")) // strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); //默认控制器映射配置为:pms_pro=> controller @ReuqestMapping("/pms/pmsProduct") 也就是/模块名/表名 // strategy.setControllerMappingHyphenStyle(true);//驼峰转连字符,如pms_pro=> controller @ReuqestMapping("/pms/pms_product") //设置表的替换前缀:这里就是起到过滤作用 strategy.setTablePrefix("t_"); strategy.setEntityTableFieldAnnotationEnable(true);//生成字段注解 mpg.setStrategy(strategy); // mpg.setTemplateEngine(new FreemarkerTemplateEngine()); //根据默认模板生成 mpg.execute(); }
插件
1、乐观锁使用
介绍
乐观锁:一直保持乐观,总认为其他人没有抢占锁,通常使用一个version版本控制字段来保证更新的原子性,保证其是没有被干扰的。对于乐观锁是很容易产生失败的情况,其是非阻塞的。
悲观锁:一直保持着悲观的心态,当操作一个公共资源的前会去拿锁,若是锁已被别人先拿走就会进入阻塞状态,会等待锁释放,一旦释放抢占锁,来进行操作。
实操
step1:给表添加版本控制字段
@Version private Integer version;
step2:配置插件,在第9行进行添加插件
@Configuration @MapperScan("com.changlu.mapper") //使用了分配类,即可将启动器的自动打包放置在这里 public class MybatisPlusConfig { // 最新版 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //分页插件,设置方言为MySQL interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//添加乐观锁插件 return interceptor; } }
此时我们就配置完成了,使用也十分简单,当我们想要对某条记录进行修改时,先查询,接着进行更新,mp就会自动为我们实现底层的判断version字段操作。
乐观锁实现过程:
取出记录时,获取当前version。
新时,带上这个version。
执行更新时, set version = newVersion where version = oldVersion。
如果version不对,就更新失败。
若是我们自己手写实现乐观锁应该是这样的:①查询指定字段,将对应version字段也查询出来。②执行更新业务操作。③业务操作完成后进行更新,我们需要额外带上更新判断条件version=xxx,这个xxx就是我们初始查询出来version。
mp中就很简单了,你只要在更新前先查一下拿到对应的实体类(该实体类中有version字段嘛),接着调用mp提供给我们的update方法,我们无需自己去考虑version的判断,mp会自动来为我们进行添加version的更改逻辑。
测试:
@Test public void test(){ User user = userMapper.selectById("2");//模拟线程1 User user2 = userMapper.selectById("2");//模拟线程2 user.setUserName("xiaoxiao");//线程1执行业务操作 user2.setUserName("dada");//线程2执行业务操作 System.out.println(userService.updateById(user));//进行更新字段,此时数据库中该条记录version+1 System.out.println(userService.updateById(user2));//这里再进行更新时由于version版本不匹配就会失败! }
总结:
当你某条记录使用update()时,该实体类的内部属性必须要有version字段值,若是为nullnamemp就不会走乐观锁实现了。
我们可以通过自己填充或者查询数据库的方式来得到实体类(推荐使用selectById先拿到),接着调用mp的update()即可实现乐观锁。
注意点:当你在mp中执行update()更新操作时,它会对该实体类的version进行加1,若是重试多次就会依次累加去与数据库比对
@Test public void test(){ User user = new User((long)2, "hello", 22, "test",1); User user2 = new User((long)2, "hello2", 22, "test",1); //进行更新操作(2次) boolean b; b = userService.updateById(user); user.setVersion(2); b = userService.updateById(user); boolean b2; int bb2 = 1; //进行重试操作:重试时,mp会先将该实体类中的version字段进行自增之后再进行比对数据库(直至插入成功) do{ b2 = userService.updateById(user2); System.out.println("bb2:"+bb2+++",user2:"+user2); }while(!b2); }
搞了好久,这里仅仅是用调用接口的角度去推测内部的实现操作,未来之后进行深入源码底层来去探究实现!
实际业务
1、查询指定记录中的某个字段
根据字段查询某条记录的某个字段
@Resource private UserMapper userMapper; @Test public void testSelect() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // SELECT name FROM user WHERE (name = ?) queryWrapper.select("name").eq("name","Jone"); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
查询出除开某个字段其他所有字段
@Resource private UserMapper userMapper; @Test public void testSelect() { QueryWrapper<User> queryWrapper = new QueryWrapper<>(); // SELECT id,name FROM user ,省略掉了age与email queryWrapper.select(User.class, info -> !info.getColumn().equals("age") && !info.getColumn().equals("email")); List<User> users = userMapper.selectList(queryWrapper); System.out.println(users); }
注意:我测试了去除id,最终还是给我查出来id了!可能是主键原因。