Mybatis-plus:最优化持久层开发
一:Mybatis-plus快速入门:
1.1:简介:
| Mybatis-plus(简称MP)是一个Mybatis的增强工具,在mybatis的基础上只做增强不做改变 |
| |
| |
| 自动生成单表的CRUD功能; |
| 提供了丰富的条件拼接方式; |
| 全自动ORM类型持久层框架;(不仅提供数据库操作的方法,还会提供sql语句的实现) |
| |
1.2:Mybatis-plus快速入门:
| !!!!!!!!!!!!!!!!!!!!!!!!!!! |
| 如果我们想要对User表的数据进行单表CRUD: |
| 我们使用Mybatis-plus之后:我们只需要: |
| 1.创建mapper接口 |
| 2.继承 BaseMapper<User> (<>:要操作的表/实体类):我们就会拥有CRUD方法+CURD的sql语句 |
| |
| 注意: |
| 继承的BaseMapper(它里面有单表的增删改查方法),就不用写mapper.xml文件了, |
| 之后就可以直接使用mapper对象调用相应的CRUD方法即可进行数据库的操作了!!! |
Mapper接口
| public interface UserMapper extends BaseMapper<User>{ |
| |
| |
| |
| |
| |
| |
| } |
SpringBoot测试类
| @SpringBootTest |
| public class SpringBootMybatisPlusTest { |
| |
| @Autowired |
| private UserMapper userMapper; |
| |
| public void test(){ |
| List<User> users = userMapper.selectList(null); |
| 直接调用baseMapper接口中相应的方法即可 |
| } |
| } |
二:Mybatis-plus的核心功能
Mybatis-plus是如何增强的
| Mybatis-plus可以对三层架构的两层进行增强: |
| 1.MapperC层:只要继承,就拥有了crud方法 |
| 2.Service层:继承 |
| 原理:!!!!!!!! |
| mapper接口只要继承BaseMapper<实体类> 接口: |
| 接下来我们就能使用通过mapper对象和BaseMapper接口中提供的CRUD方法来对 实体类表 进行操作; |
2.1:基于Mapper接口的CRUD
| (1)mapper接口 |
| |
| 如果我们想要对User表的数据进行单表CRUD: |
| 我们使用Mybatis-plus之后:我们只需要: |
| 1.创建mapper接口 |
| 2.继承 BaseMapper (要操作的表/实体类):我们就会拥有CRUD方法+CURD的sql语句 |
| */ |
| public interface Use rMapper extends BaseMapper<User> { |
| |
| |
| } |
| |
| |
| |
| (2)测试类: |
| |
| @SpringBootTest |
| public class MybatisPlusTest { |
| |
| @Autowired |
| private UserMapper userMapper; |
| |
| |
| @Test |
| public void test(){ |
| User user=new User(); |
| user.setName("kun"); |
| user.setAge(88); |
| user.setEmail("xxx"); |
| |
| int row = userMapper.insert(user); |
| } |
| |
| |
| @Test |
| public void test_delete(){ |
| |
| int rows = userMapper.deleteById(1687124323556002889L); |
| |
| |
| Map param=new HashMap(); |
| param.put("age",20); |
| param.put("name","jack"); |
| int i = userMapper.deleteByMap(param); |
| System.out.println("i = " + i); |
| |
| |
| |
| } |
| |
| |
| |
| @Test |
| public void test_update(){ |
| |
| |
| User user=new User(); |
| user.setId(1L); |
| user.setAge(30); |
| int i = userMapper.updateById(user); |
| |
| |
| User user1=new User(); |
| user1.setAge(20); |
| int update = userMapper.update(user1, null); |
| System.out.println("update = " + update); |
| |
| |
| |
| |
| |
| |
| } |
| |
| |
| |
| |
| public void test_select(){ |
| |
| User user = userMapper.selectById(1); |
| |
| List ids=new ArrayList<>(); |
| ids.add(1L); |
| ids.add(2L); |
| |
| List users = userMapper.selectBatchIds(ids); |
| System.out.println("users = " + users); |
| } |
| |
| } |
2.2:就Service接口的CRUD
service接口继承:
2.3:分页查询实现:
| Mybatis-plus实现分页查询:!!!! |
| 使用步骤: |
| 1.导入分页插件 |
| 2.使用分页查询 |
| |
| -04 |
| |
| @SpringBootApplication |
| @MapperScan("com.atguigu.mapper") |
| |
| |
| |
| |
| |
| |
| public class Main { |
| public static void main(String[] args) { |
| |
| SpringApplication.run(Main.class, args); |
| } |
| @Bean |
| |
| public MybatisPlusInterceptor plusInterceptor(){ |
| |
| MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); |
| |
| mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); |
| |
| return mybatisPlusInterceptor; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| @SpringBootTest |
| public class MybatisPlusTest { |
| @Autowired |
| private UserMapper userMapper; |
| |
| @Test |
| public void testPage(){ |
| |
| |
| Page page=new Page<>(1,3); |
| |
| userMapper.selectPage(page,null); |
| |
| |
| long pages = page.getPages(); |
| long current = page.getCurrent(); |
| List records = page.getRecords(); |
| long total = page.getTotal(); |
| |
| } |
| |
| } |
自定义mapper方法使用分页
| 上面:我们是使用Mybatis提供的方法: userMapper.selectPage(page,null); |
| 如果我们自定义的mapper方法想要使用分页查询: |
| 1.在mapper接口中定义方法: |
| 自定义方法想要分页:在第一个参数加一个IPage对象,返回值也是IPage |
| 2.在mapper文件中实现:使用sql语句实现:不要加limit和; |
| 3.测试 |
| 1. |
| |
| public interface UserMapper extends BaseMapper<User> { |
| |
| |
| |
| |
| |
| |
| |
| |
| IPage<User> queryByAge(IPage<User>page, @Param("age") Integer age); |
| } |
| |
| 2. |
| |
| <select id="queryByAge" resultType="com.atguigu.pojo.User"> |
| select * from user where age>#{age} |
| select> |
| |
| 3. |
| |
| |
| |
| |
| public void test_MyPage(){ |
| |
| Page<User> page=new Page<>(1,3); |
| |
| userMapper.queryByAge(page,1); |
| |
| |
| long pages = page.getPages(); |
| long current = page.getCurrent(); |
| List<User> records = page.getRecords(); |
| long total = page.getTotal(); |
| System.out.println("total:"+total); |
| |
| } |
| |
| } |
2.4:条件构造器使用:
2.4.1条件构造器的作用:
| warpper对象:动态进行 条件的拼接; |
| 就相当于在sql语句后面加条件,只是使用java代码的格式; |
| |
warpper对象:动态进行 条件的拼接;
就相当于在sql语句后面加条件,只是使用java代码的格式;
---->:就不用在mapper.xml文件中使用sql语句实现了,直接使用java代码来代替sql语句;!!!!!!!!
2.4.2条件构造器的类结构:
| 一般使用: |
| 第一种: UpdateWrapper(修改)、QueryWrapper(删除、查询、修改) |
| 第二种: LambdaUpdateWrapper(修改)、 LambdaQueryWrapper(删除、查询、修改) |
| 1.mapper接口: |
| |
| |
| public interface UserMapper extends BaseMapper { |
| |
| |
| |
| |
| |
| |
| |
| IPage queryByAge(IPagepage, @Param("age") Integer age); |
| } |
| |
| |
| 2. |
| |
| |
| |
| |
| |
| |
| @SpringBootTest |
| public class MybatisPlusQueryWrapper { |
| |
| @Autowired |
| private UserMapper userMapper; |
| |
| @Test |
| public void test_01(){ |
| |
| |
| |
| |
| QueryWrapper queryWrapper=new QueryWrapper<>(); |
| |
| queryWrapper.like("name","a"); |
| queryWrapper.between("age",20,30); |
| queryWrapper.isNotNull("email"); |
| List users = userMapper.selectList(queryWrapper); ----->:因为继承了base接口,所以单表直接调用方法即可! |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| } |
| } |
(2)queryWrapper实战使用:
| @SpringBootTest |
| public class MybatisPlusQueryWrapper { |
| |
| @Autowired |
| private UserMapper userMapper; |
| |
| |
| @Test |
| public void test_02(){ |
| |
| |
| QueryWrapper<User> queryWrapper=new QueryWrapper<>(); |
| |
| queryWrapper.orderByDesc("age").orderByAsc("id"); |
| |
| |
| List<User> users = userMapper.selectList(queryWrapper); |
| } |
| |
| |
| @Test |
| public void test_03(){ |
| |
| QueryWrapper<User> queryWrapper=new QueryWrapper<>(); |
| |
| queryWrapper.isNotNull("email"); |
| |
| int delete = userMapper.delete(queryWrapper); |
| } |
| |
| |
| |
| :相应的条件:age>20... 使用queryWrapper封装,然后作为条件放入update方法形参中;!!!!! |
| @Test |
| public void test_04(){ |
| |
| QueryWrapper<User> queryWrapper=new QueryWrapper<>(); |
| |
| queryWrapper.gt("age",20).like("name","a") ---------->B:相当于:where ....... |
| .or().isNotNull("email"); |
| |
| |
| |
| |
| |
| User user=new User(); |
| user.setAge(88); ----->A:相当于update user set name="" age= |
| user.setEmail("hehe"); |
| |
| userMapper.update(user, queryWrapper); |
| } |
| |
| |
| |
| |
| public void test_05() { |
| |
| |
| QueryWrapper<User> queryWrapper = new QueryWrapper<>(); |
| |
| queryWrapper.gt("age",1); |
| |
| queryWrapper.select("name","age"); |
| |
| userMapper.selectList(queryWrapper); |
| } |
| |
| |
| |
| |
| @Test |
| public void test_06(){ |
| |
| String name="xx"; |
| Integer age=19; |
| |
| |
| |
| QueryWrapper<User>queryWrapper=new QueryWrapper<>(); |
| |
| |
| if(StringUtils.isNotBlank(name)){ |
| |
| queryWrapper.eq("name",name); |
| } |
| |
| |
| if (age>18 && age!=null){ |
| queryWrapper.eq("age",age); |
| |
| } |
| |
| userMapper.selectList(queryWrapper); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| queryWrapper.eq(StringUtils.isNotBlank(name),"name",name); |
| queryWrapper.eq(age>18 && age!=null,"age",age); |
| } |
updateWrapper:改
进行修改时:使用updateWrapper:!!!!
| |
| UpdateWrapper queryWrapper=new UpdateWrapper<>(); |
| |
| queryWrapper.gt("age",20).like("name","a").isNotNull("email") |
| .set("email",null).set("age",100); |
| |
| userMapper.update(null,queryWrapper); |
在修改时updateWrapper和queryWrapper区别:
| (参数1:条件; 参数2:修改的数据) |
| queryWrapper修改:参数1只能放条件,且列名不能为null |
| updateWrapper修改:参数1可以放条件, 且列名可以设置为null; |
| 且可以直接调用set方法进行修改 |
LambdaQueryWrapper:增/删/改
| eg: |
| 查询用户名包含 a like,年龄在20-30之间,并且邮箱不为null的用户信息; |
| LambdaQueryWrapper<User>lambdaQueryWrapper=new LambdaQueryWrapper<>(); |
| //即只要将原来的列名,变为Lambda方法引用即可,其余不变!!!!!!!!!! |
| lambdaQueryWrapper.like(User::getName,"a").between(User::getAge,20,30).isNotNull(User::getEmail); |
| |
| userMapper.selectList(queryWrapper); |
LambdaupdateWrapper:改
| eg: |
| LambdaUpdateWrapper<User>lambdaUpdateWrapper=new LambdaUpdateWrapper<>(); |
| |
| //即只要将原来的列名,变为Lambda方法引用即可,其余不变!!!!!!!!!! |
| lambdaUpdateWrapper.gt(User::getAge,20).like(User::getName,"a").isNotNull(User::getEmail) |
| .set(User::getEmail,null).set(User::getAge,100); |
| |
| userMapper.update(null,lambdaUpdateWrapper); |
(3)总结:
| Wrapper:里面:完成了动态条件封装 |
| 最终推荐使用Lambda;(不用写列名,只需类名::方法即可) |
| |
| 如果复杂的sql:qg:嵌套、子查询 不会使用Wrapper怎么办: |
| 我们仍然使用原来的:定义mapper |
| 如果为单表的话:直接使用mapper对象调用CRUD方法即可; |
| 如果为多表的话:在mapper.xml文件中实现; |
2.5Mybatis-plus核心注解的使用:
注解的作用:指定实体类和表之间的映射;
1.@TableName
| 作用:指定数据库表名;(当【BaseMapper<实体类>】 实体类名 和 数据库表名不同时); |
| 使用:@TableName("数据库表名") |
| |
| a:位置:加到实体类上 |
| b:可以不加,不加的话默认使用实体类的名字作为 表名! 忽略大小写; |
| eg:BaseMapper<User>:默认将实体类名:User,作为表名 来进行操作; |
| |
| c:使用@TableName注解场景: |
| 当数据库的表名和实体类名称不同时(忽略大小写),需要使用@TableName来指定 表名: |
| eg:数据库名:t_user; 实体类名:User |
| 两个名称不同,此时我们需要在实体类上方使用: ”@TableName("t_user")“ 来指定表名; |
| d: 如果表名命名规范:eg:都以 "t_"开头,且除了”t_“以外,实体类名=表名 |
| 我们可以将前缀统一提取到配置文件中(application.yml),这样就不需要每个实体类都加@TableName注解来指定表名了 |
| 它会自动查找相应的表; |
| mybatis-plus: |
| global-config: |
| db-config: |
| table-prefix: t_ #表名前缀; |
| #加了这个之后,我们就不需要在每个实体类加@TableName注解了,它就会自己根据前缀"t_"进行查找; |
| #前提:数据库的表都是以 “t_”开头的 |
2.@TableId:
| 作用:描述主键的一些特性; |
| eg: value:指定表主键名; type:设置主键type策略 |
| 使用:在实体类的主键属性上:+@TableId(value="主键列名",type="主键策略"); |
| |
| b:位置:加到实体类的主键属性上 |
| c:使用@TableId的场景: |
| 场景1: |
| 表 主键的列名 和 实体类的属性名 不一致时,使用这个注解来指定表的列名;!!! |
| eg: 1:如果 实体类属性名:id; 数据库主键列名:x_id; |
| @TableId(value="x_id") |
| 场景2: |
| 设置表主键增长: |
| type主键策略: |
type增长策略:
| @SpringBootTest |
| |
| public class MybatisPlusTableIdTest { |
| |
| @Autowired |
| private UserMapper userMapper; |
| |
| @Test |
| public void test_01(){ |
| |
| User user=new User(); |
| user.setName("坤坤"); |
| user.setAge(20); |
| user.setEmail("xxx@qq.com"); |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| userMapper.insert(user); |
| |
| } |
| } |
| |
| User视图类: |
| public class User(){ |
| @TableId(type = IdType.AUTO) |
| private Long id; |
| } |
3.@TableFiled
| 作用:描述非主键字段 |
| 使用:在实体类的非主键字段上:+@TableFiled(value="",exist="") |
| |
| a:使用场景1:value="" (不是必须指定的,String类型) |
| :当实体类的属性名 和 列名 不同的时候 使用@TableFiled指定数据库表名; |
| 使用场景2:esist=ture/false; (默认为true) |
| :eg:@TableFiled(value="name",exist=false): |
| private String name; |
| -->:false代表:认为name没有数据库对应的列,因此在查询等时候不会对他进行操作; |
三:Mybatis-plus高级扩展
3.1逻辑删除实现:
| (1)逻辑删除概念: |
| 逻辑删除:是 "假删除数据":是将想要删除数据的状态修改为 "被删除状态",但是数据并没有消失,之后在查询时,查询没有 删除状态 的数据,也是一种删除; |
| |
| 物理删除:delete from:删除之后就没有了相应的数据; |
| |
| (2)逻辑删除的实现: |
| 方式1:单个表逻辑删除的实现: |
| |
| a:在数据库表中 插入一个逻辑删除字段(int类型,有1/0状态字段!): |
| alert table User add deleted int default 1/0; |
| |
| b:在实体类的对应属性上加:@TableLogic |
| eg: |
| @TableLogic //代表这个属性对应的列是 :逻辑删除状态字段 |
| //当我们删除数据时候,自动变成修改此列的属性值(即将deleted变为1); 默认:0是未删除,1是删除 |
| //当我们查询数据时候:默认只查询 deleted=0 的列的数据 |
| private Integer deleted; |
| |
| 方式2:多个表逻辑删除的实现(全局指定): |
| |
| |
| a:同上 |
| |
| b:在配置文件(application.yml)中:配置 |
| |
| mybatis-plus: |
| global-config: |
| db-config: |
| logic-delete-field: deleted # 全局逻辑删除的实体属性名 |
| |
| 注意:这种就不需要在类的属性名上 + @TableLogic了 |
| 但是仍然需要在实体类中创建一个和列名deleted 对应的属性名; |
代码演示:
| 以单个表实现逻辑删除为例: |
| |
| @TableLogic |
| private Integer deleted; |
| |
| |
| @SpringBootTest |
| public class MybatisPlusTableLogicTest { |
| |
| @Autowired |
| private UserMapper userMapper; |
| |
| public void test_01(){ |
| |
| |
| |
| |
| userMapper.deleteById(5); |
| |
| |
| userMapper.selectList(null); -->之后在查询时:就只会查询deleted=0的了:!!!!!(自动在条件位置加上where deleted=0) |
| |
| } |
3.2乐观锁的实现
| 1.乐观锁 和悲观锁 都是在 并发编程 中用于处理并发访问和资源竞争的两种不同机制; |
| |
| 2.(1)悲观锁: |
| 同步锁/互斥锁:线程在访问共享资源之前会获取到锁,在数据访问时只允许一个线程访问资源,只有当前线程访问完成只后,才会释放锁,让其它线程继续操作资源; |
| |
| (2)乐观锁:不需要提前加锁,而是在数据更新阶段进行检测 |
| |
| 乐观锁和悲观锁是两种解决并发数据问题的思路,不是技术!! |
| |
| 3.具体的技术和方案: |
| 1.乐观锁实现方案和技术 |
| 版本号/时间戳 |
| CAS |
| 无锁数据结构 |
| |
| 2.悲观锁的实现方案和技术 |
| 锁机制 |
| 数据库锁 |
| 信号量 |
| |
| 4. 现在主要学习如何使用乐观锁(版本号)解决并发问题!!!!!!! |
| (1)版本号:解决数据并发更新出现错误数据的问题; |
| |
| (2)乐观锁技术的实现流程: |
| a:每条数据添加一个版本号字段version |
| b:取出记录时,获取当前version |
| c:更新时,检查获取版本号是不是数据库当前最新版本号 |
| d:如果是(代表没人修改过数据),执行更新:set数据更新,version=version+1 |
| e:如果不是(证明已经修改过了),更新失败 |
| |
| 5.使用mybatis-plus数据使用乐观锁 |
| 步骤: |
| 1.数据库插入一个version字段:alert table user add version int default 1 (int类型 乐观锁字段) |
| 2 实体类创建相应的属性: private Integer version; + 在属性上方使用@Version注解 |
| 3.添加拦截器(添加版本号更新插件) |
| 之后在更新的时候就会检查版本,检查数据错误的问题; |
乐观锁测试:
| 1. |
| alter table user add version int default 1; #int 类型 乐观锁字段 |
| |
| 2. |
| public class User { |
| @Version |
| private Integer version; |
| } |
| |
| |
| |
| |
| 3.在配置类/Main类中加入拦截器 |
| |
| public class Main { |
| public static void main(String[] args) { |
| |
| SpringApplication.run(Main.class, args); |
| } |
| @Bean |
| |
| public MybatisPlusInterceptor plusInterceptor(){ |
| |
| |
| mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); |
| |
| return mybatisPlusInterceptor; |
| } |
| |
| |
| 4. |
| |
| |
| |
| @SpringBootTest |
| public class MybatisPlusVersionTest { |
| |
| private UserMapper userMapper; |
| |
| @Test |
| public void testById(){ |
| |
| |
| |
| |
| User user=new User(); |
| User user1=new User(); |
| |
| user.setAge(20); |
| user.setAge(30); |
| |
| userMapper.updateById(user); |
| |
| userMapper.updateById(user1); |
| |
| |
| |
| } |
| } |
3.3防止全表更新和删除
| 在开发中一般不会出现全表的更新/删除: |
| Mybatis-plus提供一种防御插件:一旦检测到是全表的update/delete:就会报异常; |
| |
| |
| 实现方法:添加防止全表更新和删除的拦截器: |
代码测试:
| 1.加入拦截器/插件: |
| public class Main { |
| public static void main(String[] args) { |
| SpringApplication.run(Main.class, args); |
| } |
| @Bean |
| public MybatisPlusInterceptor plusInterceptor(){ |
| |
| |
| mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); |
| |
| return mybatisPlusInterceptor; |
| } |
| |
| |
| 2.测试类中: |
| public void test(){ |
| |
| @Au.. |
| private UserMapper userMapper; |
| |
| |
| mapper.delete(null); ----->:null,代表没有写条件,代表:全表删除; |
| |
| mapper.update(null); ------>: null,代表没有条件,代表全表更新! |
| |
| ----------->:之后就会抛出异常:不允许全表delete/update!!!!!!! |
| |
| } |
四:Mybatis-Plus代码生成器(MybatisX插件)!!!!!!!!!!!!!
| 学习目标: |
| |
| |
| 1.如何使用MybatisX插件: 逆向工程 生成Mybatis-Plus代码; |
| //逆向工程:根据表自动生成实体类、mapper、service;!!!!!!!!!!!!!!!!!!!!!! |
| |
| 2. 使用MybatisX插件 动态生成自定义的sql语句 |
4.1Mybatis插件逆向工程
| //逆向工程:根据表自动生成实体类、mapper、service;!!!!!!!!!!!!!!!!!!!!!! |
| |
| 步骤: |
| 连接数据库; |
| ..... |
4.2Mybatis-plus快速生成单表CRUD代码(自定义sql)!!!!!!!!
| 如果我们需要自己定义一些sql语句: |
| eg:批量查询、根据id查询...... |
| |
| |
| 1.直接在mapper接口中书写相应的 ” 方法名 “ ( |
| eg:批量插入:insertBatch |
| 根据条件查询:selectByNameAndAgeAndAge |
| |
| 2.然后鼠标放在方法名上,点击MybatisX, |
| --->MybatisX 就会在mapper接口自动写好 且 在mapper.xml文件中自动生成sql语句!!!!!!!! |
| |
| |
| |
| |
| |