mybaitsPlus是国内开发的,并不是springboot的项目,只是学习的时候直接就是适配的springboot。
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速开始
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`
(
id BIGINT NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT 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');
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.52.3:3306/mybatisplus?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# url: jdbc:mysql://192.168.66.3:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
mybatis-plus:
configuration:
# 配置日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
package com.example.mybatisplus;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.example.mybatisplus.dao")
@SpringBootApplication
public class SpringbootMybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisplusApplication.class, args);
}
}
package com.example.mybatisplus.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.pojo.User;
import org.springframework.stereotype.Repository;
// 继承基本的类 BaseMapper
// 所有的crud操作都已经编写完成了,不需要再写方法和xml了
// 如果需要复杂的查询,BaseMapper没有提供,编写的方式就和mybatis一样了
@Repository
public interface UserMapper extends BaseMapper<User> {
}
package com.example.mybatisplus;
import com.example.mybatisplus.dao.UserMapper;
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 SpringbootMybatisplusApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void contextLoads() {
List list = userMapper.selectList(null);
System.out.println(list);
}
// 更新
@Test
public void testUpdate(){
User user = new User(4L, "sundy", 19, "test19@163.com");
userMapper.updateById(user);
}
}
主键策略
// 数据库名与实体类名不一致时的映射
// @TableName("tb_user")
public class User {
/**
* public enum IdType {
* AUTO(0), 自增,数据库id也必须是自增
* NONE(1), 未设置主键
* INPUT(2), 手动输入
* ID_WORKER(3), 默认,全局唯一id
* UUID(4), uuid
* ID_WORKER_STR(5); // ID_WORKER 的string形式
*/
@TableId
private Long id;
@Test
public void testInsert(){
User user = new User();
user.setName("ks");
user.setAge(12);
user.setEmail("139@qq.com");
userMapper.insert(user);
System.out.println(user);
// ==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
// ==> Parameters: 1765291692593553409(Long), ks(String), 12(Integer), 139@qq.com(String)
// 在这儿我们并没有指定id,是mybatisplus帮我们生成的id
// 主键的生成方式有 uuid 自增id 雪花算法 redis zookeeper
}
自动填充
public class User {
// 标记需要填充内容的字段
// 实体类与表的名字不一样,也可以进行映射
// exists 该字段是否在数据库表中存在
@TableField(fill = FieldFill.INSERT,exists = false)
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
package com.example.mybatisplus.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill...");
this.setFieldValByName("createTime", LocalDateTime.now(),metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(),metaObject);
}
// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill...");
this.setFieldValByName("updateTime", LocalDateTime.now(),metaObject);
}
}
乐观锁
public class User {
@Version // 乐观锁version注解,同时给数据库添加version字段
private Integer version;
// 注册组件
package com.example.mybatisplus.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
// 测试乐观锁成功
@Test
public void testOptimisticLockerSuccess(){
User user = userMapper.selectById(1L);
user.setAge(7);
// ==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, update_time=? WHERE id=? AND version=?
// 更新的时候会自动把version条件带上
userMapper.updateById(user);
}
// 测试乐观锁失败!多线程下
@Test
public void testOptimisticLockerLose(){
// 模拟线程1
User user = userMapper.selectById(1L);
user.setAge(1);
// ==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, update_time=? WHERE id=? AND version=?
// 更新的时候会自动把version条件带上
User user2 = userMapper.selectById(1L);
user2.setAge(2);
// 模拟线程2 执行了插队操作
// 执行更新操作的时候 线程1被线程2插队了
userMapper.updateById(user2);
userMapper.updateById(user); // 如果没有乐观锁就会覆盖插队线程的值
// 如果非要更新成功,可以使用自旋锁来多次尝试提交!
/**
*
*
*
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6413d7e7] was not registered for synchronization because synchronization is not active
2024-03-07 10:46:44.052 INFO 18316 --- [ main] c.e.m.handler.MyMetaObjectHandler : start update fill...
JDBC Connection [HikariProxyConnection@1759328722 wrapping com.mysql.jdbc.JDBC4Connection@67022ea] will not be managed by Spring
==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, update_time=? WHERE id=? AND version=?
==> Parameters: sundy(String), 2(Integer), test163@163.com(String), 2(Integer), 2024-03-07 10:46:44.058(Timestamp), 1(Long), 1(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6413d7e7]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@999b951] was not registered for synchronization because synchronization is not active
2024-03-07 10:46:44.070 INFO 18316 --- [ main] c.e.m.handler.MyMetaObjectHandler : start update fill...
JDBC Connection [HikariProxyConnection@1708084589 wrapping com.mysql.jdbc.JDBC4Connection@67022ea] will not be managed by Spring
==> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, update_time=? WHERE id=? AND version=?
==> Parameters: sundy(String), 1(Integer), test163@163.com(String), 2(Integer), 2024-03-07 10:46:44.07(Timestamp), 1(Long), 1(Integer)
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@999b951]
*/
}
查询、分页、删除
mybatis-plus:
configuration:
# 配置日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 逻辑删除
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
public class User {
@TableLogic // 逻辑删除
private Integer deleted;
package com.example.mybatisplus.config;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
// 分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
// 逻辑删除组件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
}
@Test
public void testSelect(){
User user = userMapper.selectById(1L);
List<User> users = userMapper.selectList(null);
List<User> users1 = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
Map<String,Object> map = new HashMap<>();
map.put("name","sundy");
List<User> users2 = userMapper.selectByMap(map);
System.out.println(user);
System.out.println(users);
System.out.println(users1);
System.out.println(users2);
}
// 分页查询
@Test
public void testPage(){
// 当前页面,每页多少条数据
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);
// page中有分页的所有信息
System.out.println(page.getRecords());
}
// Page类继承了IPage接口,并提供了更多的实用方法
// 使用IPage和Page进行分页查询的步骤基本相同,只是Page类提供了更多的方法供我们使用。
// 根据实际需求,可以选择使用IPage或Page来进行分页查询。
@Test
public void testDelete(){
userMapper.deleteById(10L);
userMapper.deleteBatchIds(Arrays.asList(8L,9L));
Map<String,Object> map = new HashMap<>();
map.put("name","ks");
userMapper.deleteByMap(map);
}
配置完后,删除的方法就变成了更新的操作,
性能分析插件
/**
* sql执行效率插件
* @return
*/
@Bean
@Profile({"dev","test"}) // 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); // ms 设置sql执行的最大时间,如果超过了则不执行
// The SQL execution time is too large, please optimize !
performanceInterceptor.setFormat(true); // 让sql更好看
return performanceInterceptor;
}
spring:
profiles:
active: dev
条件构造器
@Test
public void testWrapper(){
System.out.println("=================isNotNull ge===================================");
// 查询name、邮箱不为空,年龄大于等于12
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.isNotNull("name")
.isNotNull("email")
.ge("age",12);
System.out.println(userMapper.selectList(userQueryWrapper));
System.out.println("=================eq===================================");
QueryWrapper<User> userQueryWrapper2 = new QueryWrapper<>();
userQueryWrapper2.eq("name","sindy");
System.out.println(userMapper.selectOne(userQueryWrapper2));
System.out.println("=======================between=============================");
QueryWrapper<User> userQueryWrapper3 = new QueryWrapper<>();
userQueryWrapper3.between("age",15,20);
userMapper.selectCount(userQueryWrapper3);
System.out.println(userMapper.selectList(userQueryWrapper3));
System.out.println("=======================like=============================");
QueryWrapper<User> userQueryWrapper4 = new QueryWrapper<>();
// AND name NOT LIKE '%e%'
// AND email LIKE '%m'
userQueryWrapper4
.notLike("name","e")
// %t
.likeLeft("email","m");
System.out.println(userMapper.selectList(userQueryWrapper4));
System.out.println("=======================in=============================");
QueryWrapper<User> userQueryWrapper5 = new QueryWrapper<>();
// 这儿只是测试这么写,要不然直接id=就可以了
/**
* AND id IN (
* select
* id
* from
* user
* where
* id < 3
* )
*/
userQueryWrapper5.inSql("id","select id from user where id < 3");
System.out.println(userMapper.selectList(userQueryWrapper5));
System.out.println("=======================order by=============================");
QueryWrapper<User> userQueryWrapper6 = new QueryWrapper<>();
userQueryWrapper6.orderByDesc("age");
System.out.println(userMapper.selectList(userQueryWrapper6));
}
lambda 写法
@Test
public void testLambdaWrapper(){
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
userLambdaQueryWrapper.eq(User::getAge,6);
userLambdaQueryWrapper.eq(User::getName,"Tom");
List<User> users = userMapper.selectList(userLambdaQueryWrapper);
}
代码自动生成器
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot-mybatisplus-codegenerate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatisplus-codegenerate</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
package com.example.mybatisplus.codegenerate;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
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.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Arrays;
// 代码自动生成器
@Component
public class CodeGenerate {
// private static String staticDriverName;
// private static String staticUrl;
// private static String staticUsername;
// private static String staticPassword;
//
// @Value("${spring.datasource.driver-class-name}")
// private String driverName;
//
// @Value("${spring.datasource.url}")
// private String url;
//
// @Value("${spring.datasource.username}")
// private String username;
//
// @Value("${spring.datasource.password}")
// private String password;
//
// @PostConstruct
// private void init(){
// staticDriverName = driverName;
// }
public static void main(String[] args) {
// 构建一个代码自动生成器对象
AutoGenerator autoGenerator = new AutoGenerator();
// 配置策略
// 1. 全局配置
GlobalConfig globalConfig = new GlobalConfig();
// 只能到项目,不能到模块儿
// C:\Users\EDY\Desktop\git\interview-materials\code\springboot-study-project
String projectPath = System.getProperty("user.dir");
globalConfig.setOutputDir(projectPath + "/springboot-mybatisplus-codegenerate/src/main/java");
globalConfig.setAuthor("awu");
globalConfig.setOpen(false); // 生成之后打开生成的文件夹
globalConfig.setFileOverride(false); // 是否覆盖之前生成的
globalConfig.setServiceName("%sService"); // todo
globalConfig.setIdType(IdType.ID_WORKER);
globalConfig.setDateType(DateType.ONLY_DATE);
globalConfig.setSwagger2(true);
autoGenerator.setGlobalConfig(globalConfig);
// 2. 设置数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://192.168.52.3:3306/mybatisplus?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC");
dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
dataSourceConfig.setDbType(DbType.MYSQL);
// 3. 包的配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("blog");
packageConfig.setParent("com.awu"); // com.awu.blog
packageConfig.setEntity("pojo");
packageConfig.setMapper("dao");
packageConfig.setService("service");
packageConfig.setController("controller");
autoGenerator.setPackageInfo(packageConfig);
// 4. 策略配置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("user"); // 设置要映射的数据库的表
strategyConfig.setNaming(NamingStrategy.underline_to_camel); // 数据库字段转下划线
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
strategyConfig.setEntityLombokModel(true);
strategyConfig.setRestControllerStyle(true);
strategyConfig.setLogicDeleteFieldName("deleted");
// 自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
strategyConfig.setTableFillList(Arrays.asList(createTime,updateTime));
autoGenerator.setStrategy(strategyConfig);
// 乐观锁
strategyConfig.setVersionFieldName("version");
strategyConfig.setControllerMappingHyphenStyle(true); // localhost:8080/hello_id_2
autoGenerator.setDataSource(dataSourceConfig);
autoGenerator.execute();
}
}
我一向讨厌睡眠的必要性,它跟死亡一样,能让最强大的人倒下。
纸牌屋