基于SpringBoot的Mybatis Plus使用笔记
Mybatis Plus
快速开始
mybatis plus可以说是一款惊艳到我的插件,mybatis plus对mybatis做了一个全面的提升,不需要再写麻烦的mapper.xml,并且自带Basemapper不需要开发者写大量重复的sql语句,使用起来非常简单,能够节省节省大量的时间。但其在一对多或多对一映射的友好程度还不及JPA,需要自己手动实现手写sql语句以及自定义类。不过整体来说已经大大降低了mybatis的使用门槛
引入依赖
一个是springboot的集成依赖,一个是数据库连接池依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.18</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency>
配置连接数据库的配置文件
写在
application.yaml中,要记得加上时区的配置,不然会出错
spring: thymeleaf: prefix: classpath:/templates/ suffix: .html encoding: UTF-8 mode: HTML datasource: username: root password: xxxxxxxx url: jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver
创建数据库
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) );
DELETE FROM user; INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
以上步骤完毕后,我们需要编写pojo类,对应数据库的字段,并且写一个操作接口
pojo类
@Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; private int age; private String name; private String email; }
新建一个mapper文件夹,写一个接口继承BaseMapper,并将pojo的泛型引入
package com.zhou.mapper; @Repository public interface UserMapper extends BaseMapper<User> { }
在运行类中加入
@MapperScan("com.zhou.mapper")
@SpringBootApplication @ComponentScan("com.zhou") @MapperScan("com.zhou.mapper") @ServletComponentScan public class SpringbootdemoApplication { public static void main(String[] args) { SpringApplication.run(SpringbootdemoApplication.class, args); } }
因为Mybatis Plus已经帮我们完成了很多基本操作,因此我们只需要直接使用即可
这里使用了selectList,并且输出
package com.zhou.springbootdemo; import com.zhou.entity.Account; import com.zhou.entity.User; import com.zhou.mapper.UserMapper; import org.apache.catalina.core.ApplicationContext; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class SpringbootdemoApplicationTests { @Autowired UserMapper userMapper; @Test void contextLoads() { // wrapper参数,条件构造器,先不用写成Null List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
配置sql日志 打印sql语句
我们把sql打印出来,查看执行了什么sql
只需要在
application.yaml加上这个配置
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
CRUD拓展
插入
为了保证id的唯一,我们可以不设置id的值,mybatis plus会默认使用
雪花算法生成一个唯一ID
@Test public void testInsert(){ User user = new User(); user.setAge(3); user.setName("周哥"); user.setEmail("21312@qq.com"); userMapper.insert(user); System.out.println(user); }
如果需要更改生成id的策略,我们回到pojo类中,就可以更改了
默认是
IdType.ID_WORKER
@Data @NoArgsConstructor @AllArgsConstructor public class User { @TableId(type = IdType.ID_WORKER) private Long id; private int age; private String name; private String email; }
更新 update
@Test public void testUpdate(){ User user = new User(); user.setId(3L); user.setName("周周"); user.setEmail("21312@qq.com"); userMapper.updateById(user); System.out.println(user); }
mybatis plus通过条件自动拼接动态sql
自动填充
这边以日期为例子,一般数据库都要记录一条字段的创建时间和更新时间
先在pojo字段上边加上注解
@TableField
@Data @NoArgsConstructor @AllArgsConstructor public class User { @TableId(type = IdType.ID_WORKER) private Long id; private Integer age; private String name; private String email; @TableField(fill = FieldFill.INSERT_UPDATE) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }
创建handler文件夹,在里边新建MyHandler类之后改写一个MetaObjectHandler接口
一定要记得使用
@Component将其托管,不然无法使用
@Slf4j @Component public class MyHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.info("开始填充"); // 这里将new Date()作为自动填充值 this.setFieldValByName("createTime",new Date(),metaObject); this.setFieldValByName("updateTime",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime",new Date(),metaObject); } }
再插入或者更新,就会自动填充当前时间节点值
乐观锁
乐观锁: 总是认为不会出现问题,无论干什么都不上锁,有问题再次更新值测试
悲观锁:总是认为会出现问题,各种上锁
实现方式:
- 取出记录的时候,获取当前version
- 更新的时候,带上这个version
- 执行更新时,set version = new Version where version = oldVersion
- 如果version不对,更新失败
线程A update user set name = "zhou", version = version + 1 where id = 2 and version = 1; 线程B update user set name = "zhou", version = version + 1 where id = 2 and version = 1;
- 先在数据库中加入version字段,并且使其默认值为1
- 实体类加对应字段
@Version private Integer version;
- 注册组件,新建一个文件夹config,配置类
这些MapperScan的配置可以写在配置类之上
@EnableTransactionManagement @MapperScan("com.zhou.mapper") @Configuration public class MybatisConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } }
为了多线程保证正确,如果有一个线程抢先操作了,后来的线程不会覆盖前边的线程
@Test public void testVersion(){ User user = userMapper.selectById(2); user.setName("周杰伦"); User user2 = userMapper.selectById(2); user2.setName("周杰伦222"); userMapper.updateById(user2); userMapper.updateById(user); }
查询操作
删除操作(delete)和这里同理,后边不赘述
- 批量查询使用一个list来操作
- 如果需要查询特定字段来查询,引入一个map即可
@Test public void testVersion(){ User user = userMapper.selectById(2); user.setName("周杰伦"); User user2 = userMapper.selectById(2); user2.setName("周杰伦222"); userMapper.updateById(user2); userMapper.updateById(user); } // 批量查询 @Test public void testFindById(){ User user = userMapper.selectById(1); System.out.println(user); List<User> users = userMapper.selectBatchIds(List.of(1,2,3)); users.forEach(System.out::println); } // 条件查询 @Test public void testFindById(){ HashMap<String, Object> map = new HashMap<>(); map.put("name","周周"); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }
分页
先在配置类中注册插件
@Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false // paginationInterceptor.setOverflow(false); // 设置最大单页限制数量,默认 500 条,-1 不受限制 // paginationInterceptor.setLimit(500); // 开启 count 的 join 优化,只针对部分 left join paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; }
直接使用即可
@Test public void testPage(){ Page<User> userPage = new Page<>(2,5); userMapper.selectPage(userPage, null); userPage.getRecords().forEach(System.out::println); }
逻辑删除
物理删除:直接从数据库中删除
逻辑删除:在数据库中没有被移除,而是通过一个变量让其失效,
deleted = 0⇒
deleted = 1
管理员可以查看被删除的记录,防止数据库的丢失,类似于回收站
步骤:
- 在数据表中增加一个deleted字段,默认是0,长度为1
- 在Pojo类中增加一个字段,并加上
@TableLogic
注解
@Data @NoArgsConstructor @AllArgsConstructor public class User { @TableId(type = IdType.ID_WORKER) private Long id; private Integer age; private String name; private String email; @Version private Integer version; @TableField(fill = FieldFill.INSERT_UPDATE) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; @TableLogic private Integer deleted; }
- 在
application.yaml
中配置逻辑删除
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config: db-config: logic-delete-field: flag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
其实本质走的是更新操作,不是删除操作,因此会保留操作字段
UPDATE user SET deleted=1 WHERE id=1 AND deleted=0
删除之后,我们就不能通过select操作将其查询,因为查询时会自动拼接上deleted
性能分析插件
引入依赖 p6spy
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.8.2</version> </dependency>
因为这个是用于开发阶段,因此我们最好新建一个
application-dev.yaml然后在
application.yaml中激活该配置文件,在
application.yaml中激活该配置
spring: profiles: active: dev
application-dev.yaml写法
spring: thymeleaf: prefix: classpath:/templates/ suffix: .html encoding: UTF-8 mode: HTML datasource: username: root password: xxxxxx url: jdbc:p6spy:mysql://localhost:3306/db1?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 driver-class-name: com.p6spy.engine.spy.P6SpyDriver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config: db-config: logic-delete-field: flag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) server: port: 9090
新建一个
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上输出执行时间
Wrapper 复杂查询
查询名字邮箱不为空并且年龄大于30的字段
public void testWrapper1(){ QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.isNotNull("name").isNotNull("email").ge("age",30); List<User> users = userMapper.selectList(userQueryWrapper); users.forEach(System.out::println); }
查询名字等于特定值的字段
@Test public void testWrapper1(){ QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq("name","Billie"); User user = userMapper.selectOne(userQueryWrapper); System.out.println(user); }
区间查询
SELECT id,age,name,email,version,create_time,update_time,deleted FROM user WHERE deleted=0 AND (age BETWEEN 20 AND 30)
@Test public void testWrapper3(){ QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.between("age",20,30); List<User> users = userMapper.selectList(userQueryWrapper); users.forEach(System.out::println); }
模糊查询
@Test public void testWrapper3(){ QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.notLike("name","周").likeRight("email","t"); List<User> users = userMapper.selectList(userQueryWrapper); users.forEach(System.out::println); }
代码自动生成
首先建立好规范的数据库,自动生成的意思就是根据数据库,建立一系列你需要的文件
例如pojo,controller,mapper,service
引入依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.3.1.tmp</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.6.1</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.6.1</version> </dependency>
- 保留config中的所有文件,配置类需要自己写好
@EnableTransactionManagement @MapperScan("com.zhou.mapper") @Configuration public class MybatisConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false // paginationInterceptor.setOverflow(false); // 设置最大单页限制数量,默认 500 条,-1 不受限制 // paginationInterceptor.setLimit(500); // 开启 count 的 join 优化,只针对部分 left join paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; }}
- 编写生成类 自动生成文件的代码如下
package com.zhou.springbootdemo; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.FieldFill; 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; import com.baomidou.mybatisplus.generator.config.po.TableFill; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.List; public class Generator { public static void main(String[] args) { AutoGenerator mpg = new AutoGenerator(); // 1.全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); System.out.println(projectPath); // 输出目录 gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("zhou"); gc.setFileOverride(true); gc.setOpen(false); gc.setServiceName("%Service"); gc.setSwagger2(true); //实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); // 2.设置数据源 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUsername("root"); dsc.setPassword("xxxxx"); dsc.setUrl("jdbc:mysql://localhost:3306/db1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8"); dsc.setDriverName("com.mysql.cj.jdbc.Driver"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 3. 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("blog"); pc.setParent("com.zhou"); pc.setEntity("entiry"); pc.setController("controller"); pc.setService("service"); pc.setMapper("mapper"); mpg.setPackageInfo(pc); // 4.策略设置 // 策略配置 StrategyConfig strategy = new StrategyConfig(); // 重点,设置映射的表名 strategy.setInclude("user"); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); // 设置逻辑删除字段 strategy.setLogicDeleteFieldName("deleted"); // 自动填充字段 TableFill createTime = new TableFill("create_time", FieldFill.INSERT); TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE); strategy.setTableFillList(List.of(createTime,updateTime)); // 乐观锁字段 strategy.setVersionFieldName("version"); strategy.setRestControllerStyle(true); // Controller用rest风格 strategy.setControllerMappingHyphenStyle(true); mpg.setStrategy(strategy); mpg.execute(); } }
执行该文件,就可以得到自动生成的各个结果,生成后注意以下几点
-
在接口加上
@Repository
-
配置类的扫描需要扫描对
@MapperScan("com.zhou")
以下是自动生成结果
联表查询以及一对多,多对一查询
预备知识: 了解inner join
这部分需要我们自己写一个接口,并且需要自己实现sql语句,以下是实现步骤
先建立两张练习表,表示班级以及学生表,用以练习,两张表用class_id做一个连接,数据库中尽量不要出现外键,会大大降低查询速度,并且提高表之间的耦合度,不利于维护
class表
create table class ( id int not primary key, classname varchar(20) null );
INSERT INTO `class` VALUES (1,'一班'),(2,'二班'),(3,'三班');
Student表
CREATE TABLE `student` ( `id` int(11) NOT NULL, `name` varchar(20) COLLATE utf8mb4_general_ci DEFAULT NULL, `age` int(11) DEFAULT NULL, `class_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO `student` VALUES (1,'zhou',11,1),(2,'li',22,1), (3,'wang',23,2),(4,'sam',23,2),(5,'jack',25,3);
随后我们自己定义自己需要的pojo类
class类 一对多
@Data public class Class implements Serializable { private static final long serialVersionUID=1L; private Integer id; private String classname; // 用列表存储 private List<Student> studentList; }
Student类 多对一
@Data public class Student implements Serializable { private static final long serialVersionUID=1L; private Integer id; private String name; private Integer age; private Integer classId; // Class班级类 private Class classes; }
MyStudentMapper和MyClassMapper接口,自己实现Basemapper接口,用注解可以直接写SQL
@Repository public interface MyClassMapper extends BaseMapper<Class> { @Select("select s.id,s.name,s.age,c.classname from student s inner join class c on s.class_id = c.id where c.id = #{id}") List<Student> findAllStudentById(int id); }
@Repository public interface MyStudentMapper extends BaseMapper<Student> { @Select("select s.id,s.name,s.age,c.classname from student s , class c where s.class_id = c.id") List<Student> findAllStudent(); @Select("select s.id,s.name,s.age,c.classname from student s inner join class c on s.class_id = c.id where s.id = #{id}") List<Student> findAllStudentById(int id); }
我们直接将Sql写在注解内,用
#{ }可以完成参数传递
@SpringBootTest class SpringbootdemoApplicationTests { @Autowired MyStudentMapper mystudentMapper; @Autowired MyClassMapper myClassMapper; @Test void testFindAll() { List<Student> allStudent = mystudentMapper.findAllStudent(); allStudent.forEach(System.out::println); } @Test void testFindByIs() { List<Student> allStudent = myClassMapper.findAllStudentById(1); allStudent.forEach(System.out::println); } }
完成自动注入以及测试类编写,我们完成了多表查询以及一对多和多对一的操作,和传统的Mybatis相比,Mybatis Plus不需要配置复杂的Association映射关系以完成多对一或者一对多的配置,非常方便,以下是输出结果
查询学生班级信息
查询班级内的学生信息,例如一班
这个只是简单的表操作,复杂表也是同理
- spring boot多数据源mybatis-plus的baseMapper的里面的方法无法使用
- SpringBoot整合Mybatis-plus笔记
- springboot+mybatisplus(使用总结,配置总结!!!干货...)
- SpringBoot中Mybatis Plus的使用
- springboot+mybatis+freemarker+shiro多模块项目搭建笔记(二)--集成mybatis plus
- JeeFast是一款基于SpringBoot+Mybatis-Plus+Bootstrap+Vue搭建的JAVA WEB快速开发平台
- spring-boot-mybatis-plus使用心得
- 架构实战项目心得(七):使用SpringBoot+Dubbo+Mybatisplus+Oracle搭建后台项目框架(一)
- springboot使用-Mybatis-Plus 的自动生成代码功能
- Spring Boot + Mybatis-Plus 集成与使用(六)
- Spring Boot + Mybatis-Plus 集成与使用(七)
- 基于springboot+mybatisplus+shiro+dubbo+layuicms2.0搭建权限管理系统
- 使用Springboot + Gradle快速整合Mybatis-Plus
- JeeFast是一款基于SpringBoot+Mybatis-Plus+Bootstrap+Vue搭建的JAVA WEB快速开发平台
- 架构实战项目心得(七):使用SpringBoot+Dubbo+Mybatisplus+Oracle搭建后台项目框架(二)
- 分库分表中间件-sharding-jdbc学习笔记-基于SpringBoot+TKMybatis搭建
- spring boot,mybatis plus,layui,后台使用sql语句进行特殊的增删改查,不用封装的方法和mapper
- Spring Boot + Mybatis-Plus 集成与使用(四)
- springboot中使用Mybatis_plus
- Spring Boot + Mybatis-Plus 集成与使用(五)