目录
MyBatisPlus
含义:mybatis-plus是个mybatis的增强工具,在mybatis的基础上只做增强不做改变,为简化开发,提高效率而生
注意:我们可以直接在mybatis的基础上直接去集成mybatisplus,这样并不会影响mybatis的功能,同时我们也可以使用他所提供的功能。
MP特点
- 对mybatis只做增强不做改变,引入他不会对现有工程产生影响
- 只需要简单的配置即可快速进行单表CRUD操作,从而节省大量的时间
- 代码生成,自动分页,逻辑删除,自动填充功能等一应俱全
MP框架结构
理解:
- MP由MP启动器、注解部分、扩展部分、core核心部分、代码生成部分所组成;由这些内容共同支持了MP去实现功能的过程
- MP实现功能:首先,扫描实体类,扫描之后通过反射技术将实例类中的属性进行抽取,抽取之后来分析表和实体类之间的关系;以及通过反射所抽取出来的实体类中的属性与我们当前字段之间的关系;再根据我们当前调用的方法来生成相对应的sql语句,然后再把增删改查的sql语句注入到mybatis的容器中从而实现最终的功能
MP使用准备
导入依赖
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-test test org.projectlombok lombok true com.baomidou mybatis-plus-boot-starter 3.5.1 mysql mysql-connector-java runtime
springboot整合mybatisplus配置文件
spring: #设置数据源信息 datasource: #配置数据源类型 type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql:///mybatis_plus?characterEncoding=utf-8&userSSL=false&serverTimezone=GMT%2B8 username: root password: rootmybatis-plus: configuration: #MP提供了日志功能 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #驼峰映射(默认就是开启的) map-underscore-to-camel-case: true #设置MP的全局配置 global-config: db-config: #这样设置的话,那么实体类所有的表都会加上t_前缀 table-prefix: t_ #设置统一主键生成策略 id-type: auto #映射文件路径 mapper-locations: classpath:/mapper/UserMapper.xml #配置类型别名所对应的包 type-aliases-package: cn.tedu.mybatisplus.pojo #扫描通用枚举的包 type-enums-package: cn.tedu.mybatisplus.enums
定义好实体类User后编辑mapper接口
//使用MP提供的通用mapper——BaseMapper//BaseMapper里的泛型表示实体类的类型@Mapperpublic interface UserMapper extends BaseMapper {}
注意:
- MP封装了许多常用的CRUD方法,用户需要的时候只需要继承公共的业务接口BaseMapper即可,进而提高了效率
- BaseMapper接口必须添加泛型,因为其要根据泛型对象获得指定的注解(并获取其中注解的值)、表名及其其中的属性
@Mapper与@MapperScan("包名")区别
- @MapperScan("包名"):扫描指定包下的mapper接口,将该接口的代理类交给spring容器来保存
- @Mapper:将该接口的代理类交给spring容器来保存
- @Mapper用在指定接口上,@MapperScan("对应接口所在包名")用在启动类或配置文件上
MP基本操作
注意:使用前注入userMapper
新增操作
User user = new User(); user.setName("lili").setAge(23).setEmail("lili@qq.com"); int insert = userMapper.insert(user); System.out.println(insert); //mybatis-plus会自动获取id System.out.println(user.getId());
删除操作
通过id删除用户
int i = userMapper.deleteById(7); System.out.println(i);
通过map作为条件删除
Map map=new HashMap<>(); map.put("name", "张三"); map.put("age", 23); //删除name为张三,age为23的人 int i = userMapper.deleteByMap(map); System.out.println(i);
通过多个id实现删除
List list = Arrays.asList(1L, 2L, 3L); int i = userMapper.deleteBatchIds(list); System.out.println(i);
更新用户
通过id进行用户更新
User user = new User(); user.setId(3L).setName("lan").setEmail("lan@qq.com"); //根据id修改元素 int i = userMapper.updateById(user); System.out.println(i);
查询用户
根据id查询用户
User user = userMapper.selectById(1L); System.out.println(user);
根据多个id查询用户
List list = Arrays.asList(1L, 2L, 3L); List users = userMapper.selectBatchIds(list); users.forEach(System.out::println);
根据map集合作为条件查询用户
HashMap map = new HashMap<>(); map.put("name", "lan"); map.put("age", 28); List users = userMapper.selectByMap(map); //list会直接打印对象数组 System.out.println(users);
通用Service接口
说明:通用Service封装了IService接口,进一步封装了CRUD采用get查询单行,remove删除,list查询集合,page分页等前缀命名方式,区分Mapper层,避免混淆
//service接口public interface UserService extends IService {}
//service实现类@Servicepublic class UserServiceImpl extends ServiceImpl implements UserService {}
注意:
- ServiceImpl
实现了IService 接口 - 使用时应先注入userService
一些操作
查询总记录数
//查询总记录数 long count = userService.count(); System.out.println(count);
批量添加数据
ArrayList list = new ArrayList<>(); for (int i = 1; i <=10 ; i++) { User user = new User(); user.setName("cjc"+i).setAge(10+i); list.add(user); } //批量添加数据 boolean b = userService.saveBatch(list); //操作成功或失败 System.out.println(b);
MP常用注解
//设置实体类所对应的表名,若对象与表名一致,则表名中()可以省略@TableName("t_user")public class User { //将当前属性对应的字段指定为主键(将该属性与数据库中的id字段进行映射),并通过雪花算法生成主键id //type标识主键的生成策略为自动递增,要求数据库的主键为自增策略(默认为雪花算法——IdType.ASSIGN.ID) @TableId(value = "id") private Long id; //将该注解标识的属性与数据库中的name字段一一映射,若属性名与字段名相同,则注解可省略 @TableField(value = "name") private String name; private Integer age; private String email; //逻辑删除0标识未删除,1标识已删除 //被逻辑删除的数据用户查不到,但是可以在数据库中看到,只是该属性变为1;(为修改操作) @TableLogic private Integer isDeleted;}
注意:@TableField(exit=false)注解一般用在注入的属性上,被该注解标识表名当前属性不参与MP的操作
雪花算法
前言
背景:需要合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量
数据库表的扩展方式:业务分库、主从复制、数据库分表
数据库分表的两种方式
- 垂直分表
- 水平分表
垂直分表
将重要的数据放到一个表中,不在业务查询中用到的独立到另一个表中,以提升一定的性能
水平分表
主键自增:比如按照范围分表(1-9999放入表一,10000-20000放入表二)
取模:主键%数据库个数,余数相同的放入一个表中
雪花算法:
雪花算法是由Twitter分布式主键生成算法,他保证不同表的主键的不重复性,以及相同表的主键的有序性
核心思想:
- 长度为64bit
- 首先是符号位,1bit标识,由于long基本类型在java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
- 4bit是时间戳(ms级别),存储的是时间戳的差值(当前时间戳-开始时间戳),结果约为69.73年
- 10bit作为机器的id(5bit是数据中心,5bit是机器id,可以部署在1024个节点上)
- 12bit作为ms内的流水号(意味着每个节点在每毫秒可以产生4096个id)
优点:整体上按照时间自增排序,并且整个分布式系统内不会产生id碰撞
条件构造器
作用:封装当前的条件
继承结构
AbstractWrapper:用于条件查询封装,生成sql的where条件
- QueryWrapper:查询条件封装
- UpdateWrapper:Update条件封装
- AbstractLambdaWrapper:使用lambda语法
AbstractLambdaWrapper
- LambdaQueryWrapper:用于lambda语法使用的查询Wrapper
- LambdaUpdateWrapper:lambda更新封装Wrapper
使用条件构造器实现查询操作
查询所有用户
//通过条件构造器查询一个list集合,若没有条件则可以设置null(相当于查询所有) List users = userMapper.selectList(null); users.forEach(System.out::println);
根据构造器查询主键字段集合
//查询name为lei的主键字段 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name", "lei"); List
根据条件构造器查询多用户
//查询用户名包含a,年龄在20-30之间,邮箱信息不为null的用户信息 QueryWrapper queryWrapper = new QueryWrapper<>(); //queryWrapper可以实现链式加载 queryWrapper.like("name", "a").between("age", 20, 30).isNotNull("email"); List users = userMapper.selectList(queryWrapper); System.out.println(users);
//对象形式 User user = new User(); user.setAge(28); QueryWrapper queryWrapper = new QueryWrapper<>(user); List users = userMapper.selectList(queryWrapper); System.out.println(users); //user内的属性最终会以and形式拼接
关于模糊查询
- like:表示a左右都有%
- likeleft:表示a左边有%
- likeright:表示a右边有%
in查询
//in查询,查询id为1,2,3的数据 Integer[] ids={1,2,3}; QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.in("id",ids); List users = userMapper.selectList(queryWrapper); System.out.println(users);
转义字符方式查询并排序
//查询id>2的用户,按照年龄降序排序,若年两相同则按照id升序排序 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.gt("id", 2).orderByDesc("age").orderByAsc("id"); List users = userMapper.selectList(queryWrapper); System.out.println(users);
转义字符
- >:gt
- <:lt
- =:eq
- >=:ge
- <=:le
- != :ne
条件构造器实现删除操作
//删除邮箱地址为null的用户信息 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.isNull("email"); int i = userMapper.delete(queryWrapper); System.out.println(i);
使用条件构造器实现修改操作
//将年龄>20并且用户名中包含a或邮箱为null的用户进行修改(默认情况下就是and连接) //修改条件 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.gt("age", 20).like("name", "a").or().isNull("email"); User user = new User(); user.setName("lei").setEmail("test@cj.com"); int i = userMapper.update(user, queryWrapper); System.out.println(i);
条件的优先级
//将用户名中包含a并且(年龄大于20或邮箱为null)的用户信息进行修改 QueryWrapper queryWrapper = new QueryWrapper<>(); //lambda中的条件优先执行(i就表示条件构造器) queryWrapper.like("name", "a").and(i-> i.gt("age", 20).or().isNull("email")); User user = new User(); user.setName("red").setEmail("test@cj.com"); int i = userMapper.update(user, queryWrapper); System.out.println(i);
组装select语句
//查询出来一个以map为泛型的list集合 //查询用户名、年龄、邮箱信息 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.select("name","age","email"); List
组装子查询
//select * from t_user where id in(select id from t_user where id<=100) //查询id<=100的用户信息 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.inSql("id", "select id from t_user where id<=100"); List users = userMapper.selectList(queryWrapper); System.out.println(users);
动态sql查询
String name=null; String age="21"; //判断字符串是否为null或空串若为返回false,不为返回true boolean pn = StringUtils.hasLength(name); boolean pa = StringUtils.hasLength(age); QueryWrapper queryWrapper = new QueryWrapper<>(); //判断属性是否为true,为true则执行该条件,不为则忽略该条件 queryWrapper.eq(pn,"name",name).eq(pa, "age", age); List users = userMapper.selectList(queryWrapper); System.out.println(users);
注意:queryWrapper.clear();为清除多余的条件,清除后queryWrapper可以继续使用
使用updateWrapper实现修改功能
//查询用户名中包含a(年龄>20或邮箱为null)的员工信息 UpdateWrapper updateWrapper = new UpdateWrapper<>(); //修改条件 updateWrapper.like("name", "a").and(i->i.gt("age", 20).isNull("email")); //修改内容 updateWrapper.set("name", "lala").set("email", "www@cjc.com"); int i = userMapper.update(null, updateWrapper); System.out.println(i);
LambdaQueryWrapper
作用:防止我们太笨,而把字段名写错进而提供了一个函数式接口来访问我们实体类中的某一个属性,当我们把属性访问之后,那么他就可以自动的获取属性所对应的字段名,来当作作为条件的哪个字段
String name="a"; Integer ageBegin=null; Integer ageEnd=30; //主要避免了名称写错进而提供了直接访问表达式:: LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.like(StringUtils.isNotBlank(name), User::getName,name) .ge(ageBegin!=null, User::getAge,ageBegin) .le(ageEnd!=null, User::getAge,ageEnd); List users = userMapper.selectList(lambdaQueryWrapper); System.out.println(users);
LambdaUpdateWrapper
//查询用户名中包含a(年龄>20或邮箱为null)的员工信息 LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); //修改条件 updateWrapper.like(User::getName, "a").and(i->i.gt(User::getAge, 20).isNull(User::getEmail)); //修改内容 updateWrapper.set(User::getName, "lala").set(User::getEmail, "www@cjc.com"); int i = userMapper.update(null, updateWrapper); System.out.println(i);
MP的分页插件
配置配置类(必然)
@Configurationpublic class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //创建mybatisplus拦截器 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //向拦截器中添加分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }}
MP实现分页
//测试类内 //两个参数——当前页页码,每页信息条数 Page page = new Page<>(2,3); //两个参数——分页对象,条件构造器 userMapper.selectPage(page, null);//因为我对所有的查询所以条件构造器为null——返回值还为page //获取当前页数据 List records = page.getRecords(); System.out.println(records); //获取总记录数 long total = page.getTotal(); System.out.println(total); //获取总页数 long pages = page.getPages(); System.out.println(pages); //是否有下一页 System.out.println(page.hasNext()); //是否有上一页 System.out.println(page.hasPrevious());
自定义分页功能
//自定义接口://mybatisplus提供的分页对象,必须为于第一个参数的位置Page selectPageVo(@Param("page") Page page,@Param("age") Integer age);//自定义配置文件sql
//测试类 Page page = new Page<>(2, 2); userMapper.selectPageVo(page,20); //获取当前页数据 List records = page.getRecords(); System.out.println(records); //获取总记录数 long total = page.getTotal(); System.out.println(total); //获取总页数 long pages = page.getPages(); System.out.println(pages); //是否有下一页 System.out.println(page.hasNext()); //是否有上一页 System.out.println(page.hasPrevious());
MP乐观锁
在需要改变的实体类中加version注解
@Datapublic class Product { private Long id; private String name; private Integer price; @Version//用来标识乐观锁版本号字段 private Integer version;}
添加乐观锁插件
@Configurationpublic class MPConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //创建mybatisplus拦截器 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //向拦截器中添加乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }}
测试
注意:下面小王和小李拿的是同一个数据
//小李查询商品价格 Product productLi = productMapper.selectById(1); System.out.println("小李"+productLi.getPrice()); //小王查询商品价格 Product productWang = productMapper.selectById(1); System.out.println("小王"+productWang.getPrice()); //小李将商品价格+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi); //小王将商品价格-30 productWang.setPrice(productWang.getPrice()-30); int result = productMapper.updateById(productWang); if (result==0){ //操作失败后重试 Product productNew = productMapper.selectById(1); productNew.setPrice(productNew.getPrice()-30); productMapper.updateById(productNew); }
通用枚举
实体类中有枚举类型,那么怎么将该枚举类型存入到数据库中呢
//为该枚举添加注解@Getterpublic enum SexEnum { MALE(1,"男"), FEMALE(2,"女"); @EnumValue//将注解所标识的属性的值存储到数据库中(因为数据库中存放的是数字) private Integer sex; private String sexName; SexEnum(Integer sex, String sexName) { this.sex = sex; this.sexName = sexName; }}
配置通用枚举扫描包
mybatis-plus.type-enums-package=cn.tedu.mybatisplus.enums
将有枚举的对象插入到数据库
MP自动填充功能
添加配置类
@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("created", new Date(), metaObject); this.setFieldValByName("updated", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updated", new Date(), metaObject); }}
为实体类添加注解
@Data@Accessors(chain = true)public class Product { private Long id; private String name; private Integer price; //在插入数据时自动填充 @TableField(fill = FieldFill.INSERT) private Date created; //在插入和更新操作时自动填充 @TableField(fill = FieldFill.INSERT_UPDATE) private Date updated; @Version//用来标识乐观锁版本号字段 private Integer version;}
测试
//测试 Product product = new Product(); product.setName("cake").setId(3L).setPrice(66); int insert = productMapper.insert(product); System.out.println(insert);
来源地址:https://blog.csdn.net/m0_60027772/article/details/126570742