赞
踩
在mybatis中,我们在操作操作数据库时,还需要我们写SQL语句,尽管mybatis帮我们省去了jdbc中连接数据库等一系列繁杂的工作。我们还可以使用mybatis逆向工程来创建mapper中的基本数据,这也可以帮助我们简化在日常开发中繁重的任务。但是,毕竟逆向工程还需要我们配置,我是我们为了更有效率的开发,我们引进了mybatis-plus。
引入mybatis-plus仅仅是单存地帮助我们开发,使我们的开发效率大大增加。
第一步:创建一个mapper接口。
第二步:让这个mapper继承BaseMapper类。
类似这样:
进入BaseMapper中,你会发现BaseMapper中给我们实现了很多种方法。
如果你觉得这里面的方法不够用的话,你还可以在mapper.xml中继续写SQL语句。这时你需要注意的是将xml文件放置的特定的位置,这是在springboot配置文件默认配置的。
第一步:创建一个service接口
第二步:让这个接口继承IService
类似这样:
这样操作之后就会获得IService模板提供的基础功能。可是这样做之后也会引来弊端,如果我们想要实现我们想要的service层业务时,这样就会行不通。
解决方法:
第一步:创建一个接口service
第二步:创建一个serviceImpl实现service接口
第三步:让serviceImpl去继承IService
类似这样:
这样做之后,我们就可以获得IService模板提供的基础功能,我们也可以在此基础上增强我们的业务。
通常我们在使用mybatis-plus时,都是将表中的数据封装到类中,但是这个类对数据库而言有又特殊的要求,它所默认的是数据库的表名和实体类类名保持一致。
倘若我们在实际开发中,数据库表名和实体类名不一致的话,我们就可以在实体类上加入@TableName注解,在这个注解设置value值,让value值与数据库表名一致。
类似这样:
很明显实体类的类名和数据库中的表名不一致,所以我们使用@TableName注解,设置其中的value值。
mybatis-plus在为主键赋值时,默认的是以类中属性名为id的属性名赋值,倘若在这个类中的主键属性名的值不是id,我们就可以引用@TableId,在这个属性上,并设置其中的value属性。
类似这样:
值得注意的是,@TableId注解还有一个重要的属性type
通常情况下,我们在实际开发中,主键的值都是有算法生成的,但是倘若你想要在数据库中实现数据库id自增的情况下,我们可以在springboot的全局配置文件中去配置。
通常情况下,mybatis-plus在把数据封装给实体类时,都会对实体类中的属性有着特殊的要求,它默认的配置就是要求让数据库中的字段名和实体类中的类名保持一致。另外,值得注意的是我们在mybatis中学了一种驼峰式命名的方法,即表中的字段名user_id,那么我们在类中的属性名就可以设置userId,这在mybatis-plus中同样奏效。
可是我们的实体类中的属性名既没有按照mybatis-plus的默认配置,也没有按照驼峰式命名的方式,我们就可以来设置@TableField中value值。
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据。
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录 使用场景:可以进行数据恢复。
在数据库中,添加如下字段:
添加实体类中的属性 :
我们在实现baseMapper类后,我们可以利用里面的方法实现简单的功能,例如根据id删除数据、批量删除数据等等一系列十分简单的功能。倘若我们想要更加精确实现我们的SQL的操作,我们就必须去了解到Wapper的子类,在那里面提供给我们许多方法,让我们去实现我们想要实现的SQL操作。
wapper的使用只是为了帮助你筛选出你想要的数据。
首先要简单介绍一下条件构造器的基本关系:
例一:组装查询语句
打印出来的日志:
==> Preparing: SELECT id AS uid,name AS userName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
==> Parameters: %a%(String), 20(Integer), 30(Integer)
查看日志,你会发现组装出来的查询语句都被加入到括号中去了。另外值得注意的是,queryWapper追加的方法之后,它所创造出来的SQL语句中的条件,都被and连接了。
例二:组装排序条件
打印出来的日志信息:
==> Preparing: SELECT id AS uid,name AS userName,age,email,is_delete FROM t_user WHERE is_delete=0 ORDER BY age DESC,id ASC
==> Parameters:
例三:组装删除条件
打印出来的日志信息:
==> Preparing: UPDATE t_user SET is_delete=1 WHERE is_delete=0 AND (email IS NULL)
==> Parameters:
例四:查询条件的优先级
举例一个没有用查询条件优先级的:
打印出来的日志
==> Preparing: UPDATE t_user SET age=?, email=? WHERE is_delete=0 AND (name LIKE ? AND age > ? OR email IS NULL)
==> Parameters: 18(Integer), user@atguigu.com(String), %a%(String), 20(Integer)
<== Updates: 1
对比一下使用查询优先级的:
打印出来的日志
==> Preparing: UPDATE t_user SET age=?, email=? WHERE is_delete=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
==> Parameters: 18(Integer), user@atguigu.com(String), %a%(String), 20(Integer)
<== Updates: 0
对比两个SQL语句之后,你会发现两个SQL语句是不一样的。使用拉姆达表达式的逻辑,会在SQL里面加上括号,用来表示查询的优先级。
例五:实现子查询
==> Preparing: SELECT id AS uid,name AS userName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (id IN (select id from t_user where id < 3))
==> Parameters:
<== Total: 0
==> Preparing: UPDATE t_user SET age=?,email=? WHERE is_delete=0 AND (name LIKE ?)
==> Parameters: 18(Integer), joker@12306.com(String), %a%(String)
<== Updates: 1
在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因 此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若 没有选择则一定不能组装,以免影响SQL执行的结果。
先看一下没有使用带condition参数的重载方法构建查 询条件:
这种方式有点类似于拼接SQL语句的意思。
==> Preparing: SELECT id AS uid,name AS userName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (age >= ? AND age <= ?)
==> Parameters: 10(Integer), 24(Integer)
<== Columns: uid, userName, age, email, is_delete
上面的实现方案没有问题,但是代码比较复杂,我们可以使用带condition参数的重载方法构建查 询条件,简化代码的编写。
==> Preparing: SELECT id AS uid,name AS userName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (age >= ? AND age <= ?)
==> Parameters: 10(Integer), 24(Integer)
<== Columns: uid, userName, age, email, is_delete
对比之下,两者打印出来的SQL语句是一样的,很明显,使用带condition参数的重载方法构建查 询条件使得整个代码变得十分整洁。
在学习LambdaQueryWrapper之前,你不妨先对比一下QueryWrapper与LambdaQueryWrapper的区别。我们看例子来学习:
这是使用queryWrapper来查询数据的代码:
使用LambdaQueryWrapper来查询数据
对比之下,你会发现两者打印出来的数据是一样,但是更加强大的是,LambdaQueryWrapper解决了我们常说的硬编码问题。同理,你也可以用同样的方法去测试一下LambdaUpdateWrapper。
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
下面按照具体步骤来演示如何使用分页插件:
首先你需要添加配置类,来添加我们要使用的分页插件
添加完成之后 我们就可以使用分页插件了。
测试一下分页查询的结果:
xml中自定义分页插件
第一步:我们需要在mapper接口中写上我们的方法,类似这样:
第二步:在mapper.xml中写SQL语句,类似这样:
测试:
在学习mybatis-plus实现悲观锁与乐观锁之前,不妨思考一下什么是悲观锁、乐观锁?这两种锁的出现解决了什么问题?带着问题去学习,这样才能得到自己想要的知识。
其实锁的出现主要解决的是进程同步的问题,有这样一个场景:
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。
我们可以把成本价想象成公共资源,如果我们给公共资源加上锁,就可以避免这样的问题。
悲观锁:
可以这样理解,如果给成本价加上悲观锁的话,若小李要先去修改成本价的话,在小李没有放弃修改价格之后,小王是不能去修改成本的。其实,给公共资源加上悲观锁的话,就破外了进程了之间的同步性。牺牲了效率,但是保证了数据的安全性。
乐观锁:
给成本价加上一个版本号,小李和小王都可以去修改成本,但是每个人在修改之前,都会去查询这个版本号,如果版本号没有被修改过,则两人都可以去修改成本价,但是修改完成本价之后,就会对版本号进行修改,若下一个人又想更改成本价,此时他会先去去查询版本号,若版本号已经被修改了,那么他就不能去修改成本价。
下面我们模拟一下修改冲突:
数据库中增加商品表
- CREATE TABLE t_product
- (
- id BIGINT(20) NOT NULL COMMENT '主键ID',
- NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
- price INT(11) DEFAULT 0 COMMENT '价格',
- VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
- PRIMARY KEY (id)
- );
添加数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
添加实体类
- @Data
- public class Product {
- private Long id;
- private String name;
- private Integer price;
- private Integer version;
- }
添加mapper
- @Repository
- public interface ProductMapper extends BaseMapper<Product> {
- }
测试
- @Test
- public void testConcurrentUpdate() {
- //1、小李
- Product p1 = productMapper.selectById(1L);
- System.out.println("小李取出的价格:" + p1.getPrice());
-
- //2、小王
- Product p2 = productMapper.selectById(1L);
- System.out.println("小王取出的价格:" + p2.getPrice());
- //3、小李将价格加了50元,存入了数据库
- p1.setPrice(p1.getPrice() + 50);
- int result1 = productMapper.updateById(p1);
- System.out.println("小李修改结果:" + result1);
-
- //4、小王将商品减了30元,存入了数据库
- p2.setPrice(p2.getPrice() - 30);
- int result2 = productMapper.updateById(p2);
- System.out.println("小王修改结果:" + result2);
- //最后的结果
- Product p3 = productMapper.selectById(1L);
- //价格覆盖,最后的结果:70
- System.out.println("最后的结果:" + p3.getPrice());
-
- }
执行完之后会发现,成本价的价钱变成了七十,不是我们想变成的130。
实现悲观锁:
第一步:在配置类中添加乐观锁插件
第二步:给实体类中的属性加上版本号
测试之前,不要忘了把数据库中的价格还原成默认值。
测试:
- @Test
- public void testConcurrentVersionUpdate() {
- //小李取数据
- Product p1 = productMapper.selectById(1L);
- //小王取数据
- Product p2 = productMapper.selectById(1L);
- //小李修改 + 50
- p1.setPrice(p1.getPrice() + 50);
- int result1 = productMapper.updateById(p1);
- System.out.println("小李修改的结果:" + result1);
- //小王修改 - 30
- p2.setPrice(p2.getPrice() - 30);
- int result2 = productMapper.updateById(p2);
- System.out.println("小王修改的结果:" + result2);
- if(result2 == 0){
- //失败重试,重新获取version并更新
- p2 = productMapper.selectById(1L);
-
- p2.setPrice(p2.getPrice() - 30);
- result2 = productMapper.updateById(p2);
- }
- System.out.println("小王修改重试的结果:" + result2);
- //老板看价格
- Product p3 = productMapper.selectById(1L);
- System.out.println("老板看价格:" + p3.getPrice());
- }
经过测试之后发现,价格变成了一百二。符合逻辑。
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举 来实现
第一步:
先创建通用枚举类型
- @Getter
- public enum SexEnum {
- MALE(1, "男"),
- FEMALE(2, "女");
-
- @EnumValue //将注解所标识的属性的值存储到数据库中
- private Integer sex;
- private String sexName;
-
- SexEnum(Integer sex, String sexName) {
- this.sex = sex;
- this.sexName = sexName;
- }
- }
第二步:
在springboot配置文件配置扫描通用枚举
测试:
- @Test
- public void testSexEnum(){
- User user = new User();
- user.setName("Enum");
- user.setAge(20);
- //设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库
- user.setSex(SexEnum.MALE);
- //INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? )
- //Parameters: Enum(String), 20(Integer), 1(Integer)
- userMapper.insert(user);
- }
-
第一步:引入依赖
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-generator</artifactId>
- <version>3.5.1</version>
- </dependency>
-
- <dependency>
- <groupId>org.freemarker</groupId>
- <artifactId>freemarker</artifactId>
- <version>2.3.31</version>
- </dependency>
第二步:快速生成代码段
-
- public class FastAutoGeneratorTest {
-
- public static void main(String[] args) {
- FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC", "root", "111111")
- .globalConfig(builder -> {
- builder.author("Joker") // 设置作者
- //.enableSwagger() // 开启 swagger 模式
- .fileOverride() // 覆盖已生成文件
- .outputDir("Z:\\00SpringBoot\\24-mybatis-plus-fast-auto-generator\\src\\main\\java"); // 指定输出目录
- })
- .packageConfig(builder -> {
- builder.parent("com.zpc") // 设置父包名
- .moduleName("") // 设置父包模块名
- .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "Z:\\00SpringBoot\\24-mybatis-plus-fast-auto-generator\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径
- })
- .strategyConfig(builder -> {
- builder.addInclude("t_user") // 设置需要生成的表名
- .addTablePrefix("t_", "c_"); // 设置过滤表前缀
- })
- .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
- .execute();
- }
-
- }
在启动代码之前:没有对应的文件
启动代码之后,它会默认打开我们文件创建的位置。
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入
mybatisx
搜索并安装。
第一步:现在idea中连接数据库
第二步: 生成代码
根据自己想要的代码格式去生成
类似这样:
这样之后就可以快速生成代码了。
详细的MyBatisX插件用法:https://baomidou.com/pages/ba5b24/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。