因为考虑到安装mysql比较复杂,这里直接使用基于内存的H2数据库,这样可以让大家快速的开始。
前期准备:
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lhb</groupId>
<artifactId>springboot-mybatisplus</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<artifactId>springboot2.1.3</artifactId>
<groupId>com.lhb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
springboot的resource下建立H2需要的创建表的语句
data-h2.sql
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');
schema-h2.sql
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 '邮箱',
create_time datetime,
update_time datetime,
version LONG,
deleted Int,
PRIMARY KEY (id)
);
springboot的yml配置H2
spring:
profiles:
active: dev
datasource:
platform: h2
driver-class-name: org.h2.Driver
schema: classpath:db/schema-h2.sql
data: classpath:db/data-h2.sql
url: jdbc:h2:mem:test
username: root
password: test
h2:
console:
enabled: true
settings:
web-allow-others: true
path: /h2-console
经过上面的配置后,就可以在springboo的test下进行mybatis-plus的操作了,如下
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
userMapper.selectList(null);
}
}
Mybatis-plus的使用
配置日志:
程序运行时,我们默认是看不到mybatis-puls运行的sql的,毕竟输出日志是很浪费时间的。但是如果开发过程中想看到这些运行的sql相关日志时,需要进行配置。本节将介绍日志的配置。
配置日志的方式如下:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #这个日志的实现类有很多,可自行选择
配置日志后,可以发现打印结果有了mybtis-plus执行的过程了和sql了
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659f226a] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1020421154 wrapping conn0: url=jdbc:h2:mem:test user=ROOT] will not be managed by Spring
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Columns: ID, NAME, AGE, EMAIL
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 2, Jack, 20, test2@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Row: 4, Sandy, 21, test4@baomidou.com
<== Row: 5, Billie, 24, test5@baomidou.com
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@659f226a]
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
CRUD扩展
1.insert插入操作
package com.lhb.practice;
import com.lhb.practice.entity.User;
import com.lhb.practice.mapper.UserMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert(){
User user = new User();
user.setName("john");
user.setAge(3);
int result = userMapper.insert(user);//帮我们自动生成id,观察log
System.out.println(result); //相应行数
System.out.println(user); // 自动将生成的id进行对象的回填
}
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@713ec32d] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@730949640 wrapping conn0: url=jdbc:h2:mem:test user=ROOT] will not be managed by Spring
==> Preparing: INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
==> Parameters: 1482594978522202114(Long), john(String), 3(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@713ec32d]
1
User(id=1482594978522202114, name=john, age=3, email=null)
从上图log中看到id是一长串数字,那么这些主键的生成策略有哪些呢?下面来看看
主键生成策略(mybatis-puls默认雪花算法)
分布式系统唯一ID生成方案汇总:分布式系统唯一ID生成方案汇总 - nick hao - 博客园
雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,比如有的数据中心在北京,有的在上海,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。
自增主键策略实现:
@Data
public class User {
// 通过这个注解来设置ID的策略
@TableId(type=IdType.AUTO)
private Long id;
private String name;
}
// IdType的类型中含有的一些id主键生成策略
public enum IdType {
AUTO(0), // 自增长,数据库必须勾上自增再能用这个
NONE(1), // 不设置主键id
INPUT(2), // 通过代码手动设置id
ID_WORKER(3), //全局唯一ID(Long型),也就是雪花算法,它是默认的
UUID(4), // uuid作为主键
ID_WORKER_STR(5); // ID_WORKER字符串表示法(字符串类型)
private int key;
private IdType(int key) {this.key = key;}
public int getKey() {return this.key;}
}
自动填充
例如一般表中的更新时间,创建时间都是默认的,不需要设置的,这种就属于自动填充,不需要我们想对象中插入值。
阿里巴巴java开发手册要求数据库表中必须有一下三个字段:id, gmt_create, gmt_modified。而且这三个字段必须自动化赋值。自动化的方式有哪些呢?如下:
方法1:数据库级别(在数据库设置操作时自动填充的值)
方法2:代码级别
通过代码设置进行插入和更新时自动对那些属性进行填充,然后保存到表对应的字段中
@Data
public class User { // 这里设置对创建时间和更新时间使用自动填充
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
private String email;
@TableField(fill= FieldFill.INSERT)
private Date createTime;
@TableField(fill= FieldFill.INSERT_UPDATE) //更新和插入时都要修改这个字段
private Date updateTime;
}
通过fill参数来设置自动填充的策略(什么时候进行自动填充) ,具体含有的策略如下
//自动填充时的策略
public enum FieldFill {
DEFAULT,// 默认值,不进行填充
INSERT, // 只有插入操作时自动填充
UPDATE, // 只有更新操作时自动填充
INSERT_UPDATE; // 插入和更新时进行自动填充
private FieldFill() {
}
}
设置了自动填充策略后,那么究竟把什么数据填充到这两个字段中呢?这就需要再写一个专门用来生成数据的类。如下,创建一个handler包,创建一个类MyMetaOjectHandler.java,如下:
@Component // 把组件加入到spring容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//第一个参数:要给谁填充值
//第二个参数:填充的值
setFieldValByName("createTime",new Date(),metaObject);
setFieldValByName("updateTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("updateTime",new Date(),metaObject);
}
}
测试:此时执行代码,然后可以在控制台看到打印出的插入后的对象自动填充了日期
@Test
public void testInsert(){
User user = new User();
user.setName("john");
user.setAge(3);
int result = userMapper.insert(user);//帮我们自动生成id,观察log
System.out.println(result); //相应行数
System.out.println(user); // 自动将生成的id进行对象的回填
}
乐观锁
实现方式:
- 取数据时,获取当前的version
- 更新世带上这个version
- 执行更新时,set version = newVersion where version = oldversion
- 如果version不对,就更改失败
存在问题:乐观锁有ABA问题。
Mybatis-plus乐观锁的使用:
第一步:给表中添加version字段,初始值为1
第二步:给实体类同步添加version属性,然后给这个属性添加@version属性,这就表示这个字段用来实现乐观锁了。
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
@TableField(fill= FieldFill.INSERT)
private Date createTime;
@TableField(fill= FieldFill.INSERT_UPDATE)
private Date updateTime;
@Version
private long version;
}
第三步:注册一个乐观锁组件,用来实现乐观锁,一般这个会放到配置类的config包中
@EnableTransactionManagement //开启事务处理,乐观锁要用
@Configuration
public class MyBatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
测试:
// 乐观锁成功例子
public void testOptimisticLocker(){
User user = new User();
user.setName("john");
user.setAge(3);
userMapper.updateById(user);
}
// 乐观锁失败的例子
@Test
public void testOptimisticLocker(){
// 模拟线程1
User user1 = new User();
user1.setName("john1");
user1.setAge(31);
//模拟线程2
User user2 = new User();
user2.setName("john2");
user2.setAge(32);
userMapper.updateById(user2);
//由于线程2先插入了,导致version变了,所以线程一的user1在更新时version不对了,
//就不会在更新了。如果没有乐观锁这个功能,那么就会覆盖user2更新的值了
// 如果非要让user1更新到表中,就可以采用自旋锁来解决
userMapper.updateById(user1);
}
查询用法进阶
注意:如果开启了逻辑删除功能,那么在查询时逻辑删除的记录会自动过滤掉,不会查询出来(sql的select语句的where会自动带上deleted=0这个条件。deleted时我们自己设置的逻辑删除的字段,可以自己起名字)
例子1:查询id为1,2,3的用户
selectBatchIds通过接收一个集合的参数,查询出满足条件数据,最后会变为sql中的in条件
@Test
public void testSelect() {
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1,2,3));
userList.forEach(System.out::println);
}
例子2:通过map作为条件进行查询
@Test
public void testSelect() {
Map<String,Object> map = new HashMap<>();
map.put("name","Jack");
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}
删除进阶操作:
可以通过传递一个集合参数,来批量删除,跟查询用法相同。这里就不写代码了。
逻辑删除
第一步:在数据库添加逻辑删除字段deleted
第二步:在实体类中添加属性deleted,并且在属性上添加注解@TableLogic
@TableLogic
private Integer deleted;
第三步:在上面的那个配置类中再次添加逻辑删除的组件
@Bean
private ISqlInjector injector(){
return new LogicSqlInjector(); //也可以自己重写这个接口
}
第四步:在yml中配置逻辑删除
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 void testDelete(){
userMapper.deleteById(1L);//配置逻辑删除后,就会自动进行逻辑删除了
}
mybatis-plus逻辑删除最后执行的实际就是一个更新操作,把deleted字段由0改为1 。
分页查询
第一步:在上面的config包中的那个配置乐观锁的类MyBatisPlusConfig.java中添加分页插件
// 分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
第二步:使用Page对象操作
@Test
public void testSelect() {
//参数意思:5条为一页,现在要去第一页的内容
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
}
性能分析插件
开发中有可能会遇到一些慢sql,mybatis-plus提供了一个性能分析插件,这个插件会检测查询语句是否过慢,如果太慢就会停止sql执行。
作用:分析每个sql执行的时间
第一步:在config包的那个配置类中配置这个分析插件需要的组件
@Bean
@Profile({"dev","test"}) // 只有yml中的active设置的时dev或者test才会向容器中注册这个组件,也就是让组件起作用。
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1);//设置sql执行的最长时间,如果超过这个时间sql就不执行了,默认单位为毫秒
performanceInterceptor.setFormat(true);//开启格式化,控制台打印出的sql会格式化
return performanceInterceptor;
}
第二步:配置yml的active属性
spring:
profiles:
active: dev
第三步:执行看效果
随便执行一个查询语句,由于我们设置的最慢时间时1毫秒,所以sql执行肯定会超过1毫秒,这是就会停止查询,然后把异常打印出来,如下,并且还不会sql格式化后显示出来。
JDBC Connection [HikariProxyConnection@571251299 wrapping conn0: url=jdbc:h2:mem:test user=ROOT] will not be managed by Spring
==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0
==> Parameters:
<== Total: 0
Time:11 ms - ID:com.lhb.practice.mapper.UserMapper.selectList
Execute SQL:
SELECT
id,
name,
age,
email,
create_time,
update_time,
version,
deleted
FROM
user
WHERE
deleted=0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3cd26422]
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize !
### The error may exist in com/lhb/practice/mapper/UserMapper.java (best guess)
### The error may involve com.lhb.practice.mapper.UserMapper.selectList
### The error occurred while handling results
### SQL: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize !
条件构造器-Wrapper
使用复杂的条件查询时,就需要这个条件构造器
例子1:
@Test
public void testSelect() {
// 查询名字不为空,邮箱不为空,年龄大于12的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name").isNotNull("email").gt("age",12);
userMapper.selectList(wrapper);
}
例子2
@Test
public void testSelect() {
// 查询20-30岁之间用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
userMapper.selectCount(wrapper);
}
例子3:
@Test
public void testSelect() {
// 模糊查询,名字中不含有e的并且email还有t的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
// likeRight和likeLeft就是只%位置
wrapper
.notLike("name","e")
.likeRight("email","t"); // t%
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
我们通过日志中的sql来观察这个模糊查询最后生成的sql、
==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?
==> Parameters: %e%(String), t%(String)
例子4:条件含有子查询
@Test
public void testSelect() {
// 条件中使用子查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.inSql("id","select id from user where id<3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
// 日志打印的sql
SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0 AND id IN (select id from user where id<3)
代码自动生成器
第一步:导入maven的mybatis-plus依赖
第二步:yml配置数据库
第三步:在test下创建一个我们自己的代码生成器类
注意:官网的代码生成器分为新旧两版,注意版本对应
package com.lhb.practice;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
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;
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 java.util.ArrayList;
public class MyCodeGen {
public static void main(String[] args) {
//需要创建一个 代码自动生成器对象
AutoGenerator mpg = new AutoGenerator();
//配置策略
//1. 全局配置
GlobalConfig gc = new GlobalConfig();
// 项目所在目录
String projectPath = System.getProperty("user.dir");
// 生成的代码放到项目所在目录
gc.setOutputDir(projectPath + "src/main/java");
// 设置作者
gc.setAuthor("lhb");
//生成后是否自动打开
gc.setOpen(false);
//是否覆盖
gc.setFileOverride(false);
// 设置接口前缀的名字:例如我们IHelloService中的I
gc.setServiceName("%sService");// 就可以去掉前缀的I
gc.setIdType(IdType.ID_WORKER);//id的生成缩略
gc.setSwagger2(true);// 是否配置Swagger文档
gc.setDateType(DateType.ONLY_DATE);
mpg.setGlobalConfig(gc);
// 2. 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:h2:mem:test");
dsc.setDriverName("org.h2.Driver");
dsc.setPassword("test");
dsc.setUsername("root");
dsc.setDbType(DbType.H2);
mpg.setDataSource(dsc);
//3.配置生成包的位置
PackageConfig pc = new PackageConfig();
pc.setParent("com.lhb"); // 包名
pc.setModuleName("blog"); // 生成com.lhb.blog
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 策略配置,比如乐观锁
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user");// 想要哪个表自动生成,可以写多个
strategy.setNaming(NamingStrategy.underline_to_camel);//字段下划线转驼峰
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);//是否直接让生成的entity里直接使用lombok
strategy.setRestControllerStyle(true);
strategy.setLogicDeleteFieldName("deleted");//设置逻辑删除的字段名,作用就是entity中对象自动生成逻辑删除(添加逻辑删除注解)
// 自动填充策略配置
TableFill createTime = new TableFill("createTime", FieldFill.INSERT);
TableFill updateTime = new TableFill("updateTime", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFill = new ArrayList<>();
tableFill.add(createTime);
tableFill.add(updateTime);
strategy.setTableFillList(tableFill);
// 乐观锁策略配置
strategy.setVersionFieldName("version");
strategy.setControllerMappingHyphenStyle(true);// 将请求由多个单词时,自动下环线,例如:localhost:8080/hello_id_2
mpg.setStrategy(strategy);
// 最后执行代码生成
mpg.execute();
}
}