目标:基于 MyBatisPlus 完成标准 Dao 的增删改查功能掌握 MyBatisPlus 中的分页及条件查询构建掌握主键 ID 的生成策略了解 MyBatisPlus 的代码生成器
目录
1,MyBatisPlus入门案例与简介
1.1 入门案例
1.2 MybatisPlus简介
2,标准数据层开发
2.1 标准CRUD使用
2.2 新增
执行测试后,数据库表中就会添加一条数据。 编辑 2.3 删除
2.4 修改
2.5 根据ID查询
2.6 查询所有
2.7 Lombok
2.8 分页功能
3,DQL编程控制
3.1 条件查询
3.1.1 条件查询的类
3.1.2 环境构建
3.1.3 构建条件查询
3.1.4 多条件构建
3.1.5 null判定
3.2 查询投影
3.2.1 查询指定字段
3.2.2 聚合查询
3.2.3 分组查询
3.3 查询条件
3.3.2 范围查询
3.3.3 模糊查询
3.3.4 排序查询
3.4 映射匹配兼容性
4,DML编程控制
4.1 id生成策略控制
4.1.1 环境构建
4.1.2 代码演示
4.1.3 ID生成策略对比
4.1.4 简化配置
4.2 多记录操作
4.3 逻辑删除
4.4 乐观锁
4.4.1 概念
4.4.2 实现思路
4.4.3 实现步骤
5,快速开发
5.1 代码生成器原理分析
5.2 代码生成器实现
勾选配置使用的技术,能够实现自动添加起步依赖包
SpringBoot整合MyBatisPlus具体的实现步骤为:
步骤2:创建SpringBoot工程
步骤5:添加MP的相关配置信息
步骤6:根据数据库表创建实体类
说明 : Dao 接口要想被容器扫描到,有两种解决方案 :方案一 : 在 Dao 接口上添加 @Mapper 注解,并且确保 Dao 处在引导类所在包或其子包中该方案的缺点是需要在每一 Dao 接口中添加注解方案二 : 在引导类上添加 @MapperScan 注解,其属性为所要扫描的 Dao 所在包该方案的好处是只需要写一次,则指定包下的所有 Dao 接口都能被扫描到, @Mapper 就可以不写。
说明 :userDao 注入的时候下面有红线提示的原因是什么 ?UserDao 是一个接口,不能实例化对象只有在服务器启动 IOC 容器初始化后,由框架创建 DAO 接口的代理对象来注入现在服务器并未启动,所以代理对象也未创建, IDEA 查找不到对应的对象注入,所以提示报红一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。
Spring-IOC管理Dao层对象
由于dao层只有接口,需要使用sqlsession工厂对象,通过代理获取dao层对象。
使用IOC管理dao层对象需要进行如下步骤:
1、在spring容器中使用org.mybatis.spring.mapper.MapperFactoryBean创建dao接口对象;
2、注入sqlsession配置,即给sqlSessionFactory属性注入数据库信息;
3、给mapperInterface属性指定接口名字;
4、在测试类中加载spring配置文件,通过bean id获取dao层对象;并调用dao层提供方法。
MP 的特性 :无侵入:只做增强不做改变,不会对现有工程产生影响强大的 CRUD 操作:内置通用 Mapper ,少量配置即可实现单表 CRUD 操作支持 Lambda :编写查询条件无需担心字段写错支持主键自动生成内置分页插件……
int insert (T t)
在测试类中进行新增操作:
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("黑马程序员");
user.setPassword("itheima");
user.setAge(12);
user.setTel("4006184000");
userDao.insert(user);
}
}
在进行删除之前,我们可以分析下删除的方法:
int deleteById (Serializable id)
Serializable :参数类型思考 : 参数类型为什么是一个序列化类 ?
从这张图可以看出,String 和 Number 是 Serializable 的子类,Number 又是 Float,Double,Integer 等类的父类,能作为主键的数据类型都已经是 Serializable 的子类,MP 使用 Serializable 作为参数类型,就好比我们可以用 Object 接收任何数据类型一样。
int:返回值类型,数据删除成功返回1,未删除数据返回0。
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
userDao.deleteById(1401856123725713409L);
}
}
int updateById(T t);
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(1L);
user.setName("Tom888");
user.setPassword("tom888");
userDao.updateById(user);
}
}
T selectById (Serializable id)
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetById(){
User user = userDao.selectById(2L);
System.out.println(user);
}
}
List selectList(Wrapper queryWrapper)
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll() {
List userList = userDao.selectList(null);
System.out.println(userList);
}
}
如果在IDEA中找不到lombok插件,可以访问如下网站
Lombok 常见的注解有 :@Setter: 为模型类的属性提供 setter 方法@Getter: 为模型类的属性提供 getter 方法@ToString: 为模型类的属性提供 toString 方法@EqualsAndHashCode: 为模型类的属性提供 equals 和 hashcode 方法@Data: 是个组合注解,包含上面的注解的功能@NoArgsConstructor: 提供一个无参构造函数@AllArgsConstructor: 提供一个包含所有参数的构造函数
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
}
分页查询使用的方法是 :IPage
selectPage(IPage page, Wrapper queryWrapper) IPage: 用来构建分页查询条件Wrapper :用来构建条件查询的条件,目前我们没有可直接传为 NullIPage: 返回值,你会发现构建分页条件和方法的返回值都是 IPageIPage 是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到 IPage 类中按 ctrl+h, 会找到其有一个实现类为 Page 。
@SpringBootTest
class Mybatisplus01QuickstartApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetByPage(){
//IPage对象封装了分页操作相关的数据
IPage page = new Page(2,3);
//执行分页查询
userDao.selectPage(page,null);
//获取分页结果
System.out.println("当前页码值:"+page.getCurrent());
System.out.println("每页显示数:"+page.getSize());
System.out.println("一共多少页:"+page.getPages());
System.out.println("一共多少条数据:"+page.getTotal());
System.out.println("数据:"+page.getRecords());
}
}
Mybatis-plus分页查询底层原理
PageHelper内部原理是将传⼊的页码和每页条数赋值给了Page对象,保存到了⼀个本地线程ThreadLoacl中,然后会进⼊Mybatis的拦截器中。然后在拦截器中获取本地线程中保存的分页的参数。最后再将这些分页参数和原本的sql以及内部定义好的sql进⾏拼接完成sql的分页处理。中间会进⾏判断该sql 的类型是查询还是修改操作。如果是查询才会进⼊分页的逻辑并判断封装好的Page对象是否是null,null则不分页,否则分页。
IPage内部原理也是基于拦截器,但是这个拦截的是⽅法以及⽅法中的参数,这个也会判断是否是查询操作。如果是查询操作,才会进⼊分页的处理逻辑。进⼊分页逻辑处理后,拦截器会通过反射获取该⽅法的参数进⾏判断是否存在IPage对象的实现类。如果不存在则不进⾏分页,存在则将该参数赋值给IPage对象。然后进⾏拼接sql的处理完成分页操作。
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor(){
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
如果想查看MP执行的SQL语句,可以修改application.yml配置文件,
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.0
com.itheima
mybatisplus_02_dql
0.0.1-SNAPSHOT
1.8
com.baomidou
mybatis-plus-boot-starter
3.4.1
org.springframework.boot
spring-boot-starter
com.alibaba
druid
1.1.16
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.18.12
org.springframework.boot
spring-boot-maven-plugin
@Mapper
public interface UserDao extends BaseMapper {
}
@Data
@TableName("tbl_user")
public class User {
private Long id;
private String name;
@TableField(value = "pwd",select = false)
private String password;
private Integer age;
private String tel;
@TableField(exist = false)
private Integer online;
}
@SpringBootApplication
public class Mybatisplus02DqlApplication {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus02DqlApplication.class, args);
}
}
# dataSource
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
username: root
password: root
main:
banner-mode: off
# mp日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
@SpringBootTest
class Mybatisplus02DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetAll(){
List userList = userDao.selectList(null);
System.out.println(userList);
}
}
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
需求:查询数据库表中,年龄在10岁到30岁之间的用户信息
gt:大于(>),最终的SQL语句为
SELECT id,name,password,age,tel FROM user WHERE (age < ? AND age > ?)
or()就相当于我们sql语句中的or关键字,不加默认是and,最终的sql语句为:
SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
select(...)方法用来设置查询的字段列,可以设置多个,最终的sql语句为:
SELECT id,name,age FROM user
需求 : 聚合函数查询,完成 count 、 max 、 min 、 avg 、 sum 的使用count: 总记录数max: 最大值min: 最小值avg: 平均值sum: 求和
groupBy为分组,最终的sql语句为
SELECT count(*) as count,tel FROM user GROUP BY tel
前面我们只使用了 lt() 和 gt(), 除了这两个方法外, MP 还封装了很多条件对应的方法,这一节我们重点把 MP 提供的查询条件方法进行学习下。MP 的查询条件有很多 :范围匹配( > 、 = 、 between )模糊匹配( like )空判定( null )包含性匹配( in )分组( group )排序( order )……
eq(): 相当于 = ,对应的sql语句为
SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
需求:查询表中name属性的值以J开头的用户信息,使用like进行模糊查询
除了上面演示的这种实现方式,还有很多其他的排序方法可以被调用,如图:
orderBy 排序condition:条件, true 则添加排序, false 则不添加排序isAsc:是否为升序, true 升序, false 降序columns:排序字段,可以有多个orderByAsc/Desc( 单个 column): 按照指定字段进行升序 / 降序orderByAsc/Desc( 多个 column): 按照多个字段进行升序 / 降序orderByAsc/Desccondition:条件, true 添加排序, false 不添加排序多个columns :按照多个字段进行排序
问题2:编码中添加了数据库中未定义的属性
问题3:采用默认查询开放了更多的字段查看权限
问题4:表名与编码开发设计不同步
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.0
com.itheima
mybatisplus_03_dml
0.0.1-SNAPSHOT
1.8
com.baomidou
mybatis-plus-boot-starter
3.4.1
org.springframework.boot
spring-boot-starter
com.alibaba
druid
1.1.16
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.18.12
org.springframework.boot
spring-boot-maven-plugin
从源码中可以看到,除了 AUTO 这个策略以外,还有如下几种生成策略 :NONE: 不设置 id 生成策略INPUT: 用户手工输入 idASSIGN_ID: 雪花算法生成 id( 可兼容数值型与字符串型 )ASSIGN_UUID: 以 UUID 生成算法作为 id 生成策略其他的几个策略均已过时,都将被 ASSIGN_ID 和 ASSIGN_UUID 代替掉。
拓展 :分布式 ID 是什么 ?当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储比如订单表就有可能被存储在不同的服务器上如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突这个时候就需要一个全局唯一 ID, 这个 ID 就是分布式 ID 。
注意:这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。
生成的ID就是一个Long类型的数据。
1. 1bit, 不用 , 因为二进制中最高位是符号位, 1 表示负数, 0 表示正数。生成的 id 一般都是用整数,所以最高位固定为 0 。2. 41bit- 时间戳,用来记录时间戳,毫秒级3. 10bit- 工作机器 id ,用来记录工作机器 id, 其中高位 5bit 是数据中心 ID 其取值范围 0-31 ,低位5bit 是工作节点 ID 其取值范围 0-31 ,两个组合起来最多可以容纳 1024 个节点4. 序列号占用 12bit ,每个节点每毫秒 0 开始不断累加,最多可以累加到 4095 ,一共可以产生 4096个 ID
介绍了这些主键 ID 的生成策略,我们以后该用哪个呢 ?NONE: 不设置 id 生成策略, MP 不自动生成,约等于 INPUT, 所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的 ID 造成主键冲突,为了保证主键不冲突就需要做很 多判定,实现起来比较复杂AUTO: 数据库 ID 自增 , 这种策略适合在数据库服务器只有 1 台的情况下使用 , 不可作为分布式 ID 使用ASSIGN_UUID: 可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是 32 位的字符 串,长度过长占用空间而且还不能排序,查询性能也慢ASSIGN_ID: 可以在分布式的情况下使用,生成的是 Long 类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键综上所述,每一种主键策略都有自己的优缺点,根据自己项目业务的实际情况来选择使用才是最明智的选择。
确实是稍微有点繁琐,我们能不能在某一处进行配置,就能让所有的模型类都可以使用该主键ID策略呢? 答案是肯定有,我们只需要在配置文件中添加如下内容:
配置完成后,每个模型类的主键ID策略都将成为assign_id.
配置起来还是比较繁琐,简化方式为在配置文件中配置如下内容:设置表的前缀内容,这样MP就会拿 tbl_加上模型类的首字母小写,就刚好组装成数据库的表名。
具体该如何实现多条删除,我们找找对应的API方法
int deleteBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList);
List selectBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList);
在多表中,要将某信息删除,但该信息中的某以字段要保留,此时我们可以使用逻辑删除,标记要删除的信息,那么此时该信息已经假定删除了,但其字段信息仍然保留。
运行测试,会发现打印出来的sql语句中会多一个查询条件,如:
数据库表中添加 version 列,比如默认值给 1第一个线程要修改数据之前,取出记录时,获取当前数据库中的 version=1第二个线程要修改数据之前,取出记录时,获取当前数据库中的 version=1第一个线程执行更新时, set version = newVersion where version = oldVersionnewVersion = version+1 [2]oldVersion = version [1]第二个线程执行更新时, set version = newVersion where version = oldVersionnewVersion = version+1 [2]oldVersion = version [1]假如这两个线程都来更新数据,第一个和第二个线程都可能先执行假如第一个线程先执行更新,会把 version 改为 2 ,第二个线程再更新的时候, set version = 2 where version = 1, 此时数据库表的数据 version 已经为 2 ,所以第二个线程会修改失败假如第二个线程先执行更新,会把 version 改为 2 ,第一个线程再更新的时候, set version = 2 where version = 1, 此时数据库表的数据 version 已经为 2 ,所以第一个线程会修改失败不管谁先执行都会确保只能有一个线程更新数据,这就是 MP 提供的乐观锁的实现原理分析。
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.5.1
com.itheima
mybatisplus_04_generator
0.0.1-SNAPSHOT
1.8
org.springframework.boot
spring-boot-starter-web
com.baomidou
mybatis-plus-boot-starter
3.4.1
com.alibaba
druid
1.1.16
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.18.12
com.baomidou
mybatis-plus-generator
3.4.1
org.apache.velocity
velocity-engine-core
2.3
org.springframework.boot
spring-boot-maven-plugin
步骤4:创建代码生成类
package com.itheima;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
public class CodeGenerator {
public static void main(String[] args) {
//1.获取代码生成器的对象
AutoGenerator autoGenerator = new AutoGenerator();
//设置数据库相关配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
autoGenerator.setDataSource(dataSource);
//设置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置
globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录
globalConfig.setAuthor("黑马程序员"); //设置作者
globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件
globalConfig.setMapperName("%sDao"); //设置数据层接口名,%s为占位符,指代模块名称
globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//设置包名相关配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //设置生成的包名,与代码所在位置不冲突,二者叠加组成完整路径
packageInfo.setEntity("domain"); //设置实体类包名
packageInfo.setMapper("dao"); //设置数据层包名
autoGenerator.setPackageInfo(packageInfo);
//策略设置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名,参数为可变参数
strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称,模块名 = 数据库表名 - 前缀名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格
strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名
strategyConfig.setEntityLombokModel(true); //设置是否启用lombok
autoGenerator.setStrategy(strategyConfig);
//2.执行生成操作
autoGenerator.execute();
}
}