目录
Mybatis-plus快速入门
官网及文档:https://mp.baomidou.com/
数据库配置
数据库以官方例子为例
现有一张 User 表,其表结构如下:
其对应的数据库 Schema 脚本如下:
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)
);
其对应的数据库 Data 脚本如下:
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');
创建SpringBoot项目
引入依赖
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus驱动-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
项目结构
项目配置
这里使用ymal进行配置
spring:
datasource:
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
编码
pojo
这里使用lombok编写,没有lombok要下载插件
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper
要继承BaseMapper
@Repository
public interface UserMapper extends BaseMapper<User> {
//继承BaseMapper<T> 所有的CRUD已经编写完成
}
测试
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}
UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件
结果
配置完日志
CRUD扩展
插入测试
@Test
//测试插入
public void testInsert(){
User user = new User();
user.setName("张三");
user.setAge(18);
user.setEmail("123456@qq.com");
int result = userMapper.insert(user);
System.out.println(result);
System.out.println(user);
}
注意:这里没有写填写id
主键生成策略
分布式系统唯一ID生成方法汇总:https://www.cnblogs.com/haoxinyue/p/5208136.html
通过结果我们可以发现 id会自动填写
通过结果我们可以发现这里默认的是ID_WORKER 全局唯一ID
全局唯一ID
实现的算法为雪花算法
雪花算法
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)。
这里使用的mybatis-plus版本为3.0.5 新版本应该对应ASSIGN_ID
我们在UserMapper上 对id 添加主键注解
点开@TableId源码可以看到以下主键自增策略
主键自增
1.添加注解
2.数据库字段一定是要自增
3.测试
再次插入
发现主键生成策略已是自增
更新测试
@Test
//测试更新
public void testUpdate(){
User user = new User();
user.setName("李四");
user.setId(5L);
user.setEmail("123456@qq.com");
int result = userMapper.updateById(user);//注意参数为User类型
System.out.println(result);
System.out.println(user);
}
结果
我们再次修改 这次修改加入年龄
我们可以发现sql是动态帮我们配置的
自动填充
像创建时间、修改时间,这些操作一般都是自动化完成的,我们不希望手动更新
阿里巴巴开发手册规定:所有的数据库表中都要有:gmt_create gmt_modified 且需要自动化
方式一 修改数据库 (工作中一般不允许改动数据库)
- 在表中新增gmt_create gmt_modified
2.更新实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
private Date gmtCreate;
private Date gmtModified;
}
3.再次更新
结果:
方式二 代码
1.先把默认值和更新去掉
2.实体类上增加注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
3.编写处理器处理这个注解
@Slf4j
@Component //一定不要忘记把处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill...");
//default MetaObjectHandler setFieldValByName(String fieldName字段名, Object fieldVal字段值, MetaObject metaObject)
this.setFieldValByName("gmtCreate",new Date(),metaObject);
this.setFieldValByName("gmtModified",new Date(),metaObject);
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill...");
this.setFieldValByName("gmtModified",new Date(),metaObject);
}
}
4.测试
插入:
修改:
乐观锁
乐观锁:顾名思义十分乐观,认为不会出现问题,不上锁,如果出现了问题则再次更新值测试
悲观锁:顾名思义十分悲观,认为总会出现问题,无论干什么都要先上锁,再去操作
举个例子:
乐观锁:先查询 获得版本号-- version = 1
--A
update user set name = "张三" , version = version + 1
where id = 2 and version = 1
--B B线程抢先完成,这个时候version = 2, 会导致A线程修改失败!
update user set name = "张三" , version = version + 1
where id = 2 and version = 1
mybatis-plus中乐观锁插件
1.给数据库中添加version字段
2.修改实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@Version//代表这个是一个乐观锁
private Integer version;
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
3.注册组件
注意新版本配置不同 以官方文档为准
@MapperScan("com.choi.dao")//可以将Application的Mapper扫描放在配置类里
@EnableTransactionManagement//开启事务
@Configuration//配置类
public class MybatisPlusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
4.测试
//测试乐观锁成功
@Test
public void testOptimisticLocker(){
//1.查询用户信息
User user = userMapper.selectById(1L);
//2.修改用户信息
user.setName("王五");
user.setEmail("123456@qq.com");
//3.执行更新操作
userMapper.updateById(user);
}
//测试乐观锁失败
@Test
public void testOptimisticLocker2(){
//这里模拟多线程
//线程A
User user1 = userMapper.selectById(1L);
user1.setName("王五111");
user1.setEmail("123456@qq.com");
//B线程
User user2 = userMapper.selectById(1L);
user2.setName("王五222");
user2.setEmail("123456@qq.com");
//B线程比A先完成
userMapper.updateById(user2);
userMapper.updateById(user1);
}
查询测试
较为简单 则不放结果
//查询测试
//按ID查询
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
//多ID查询
@Test
public void testSelectBatchId(){
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));//传入集合
userList.forEach(System.out::println);
}
// 条件查询 map
@Test
public void testSelectByMap(){
HashMap<String,Object> map = new HashMap<>();
//自定义查询 map.put("字段名","值") 多个map.put则是多条件查询
map.put("name","张三");
map.put("age",20);
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}
分页查询
分页在网站里面使用的十分之多
1.原始的limit进行分页
2.pageHelper第三方插件
3.mybatis-plus 分页插件
这里使用mybatis-plus分页插件(版本问题可能使用方式不同 具体以官网为准)
1.注册分页插件
@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;
}
------------------------------------------------------------
//这里我们简化 直接返回即可
//注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2.直接使用page对象即可
//分页查询
@Test
public void testPage(){
Page<User> page = new Page<>(1,5);//第一个参数:当前页 第二个参数:页大小
userMapper.selectPage(page, null);//wrapper先写null
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());//一共有多少条数据
System.out.println(page.hasNext());//是否有下一页
System.out.println(page.hasPrevious());//是否有上一页
System.out.println(page.getPages());//一共有多少页
System.out.println(page.getCurrent());//当前是第几页
}
删除测试
基本删除
//删除测试
@Test
public void testDeleteById(){
userMapper.deleteById(1460137473252642820L);
}
//ID批量删除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1460137473252642819L,1460137473252642818L));
}
//通过map删除
@Test
public void testDeleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","王五222");
userMapper.deleteByMap(map);
}
逻辑删除
物理删除:从数据库中直接移除
逻辑删除:再数据库中没有被移除,而是通过一个变量使他失效 如 deleted = 0 => deleted = 1
管理员可以查看被删除的记录,以防止数据的丢失,类似回收站
测试:
1.在数据库中增加 deleted字段
2.修改实体类
3.注册插件
新版本以官网为准
//逻辑删除
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
yaml
#配置逻辑删除
global-config:
db-config:
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
4.测试
按ID删除、批量删除和条件删除各执行一次
结果:
我们可以看到对应的sql语句也和物理删除不同:
本质走的是更新操作
我们这是进行一次查询所有信息
我们可以发现对应的查询sql语句也发生了变化
性能分析插件
我们在平时的开发中,会遇到一些慢sql。
作用:性能分析拦截器,用于输出每条sql语句及其执行的时间
mybatis-plus提供性能分析插件,如果超过这个时间就停止运行
(dev测试环境要在yaml里配置)
spring:
profiles:
active: dev
1.导入插件
新版本配置方法以官网为准
//sql执行效率分析插件
@Bean
@Profile({"dev","test"})//设置dev test环境开启 保证效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1);//ms 设置sql执行的最大时间,超时则不执行
performanceInterceptor.setFormat(true);//是否开启格式化支持
return performanceInterceptor;
}
2.测试
这里我们执行一个查询所有信息 => 报错
条件构造器 wrapper
通过官网我们可以看到:
测试一:
@Test
void contextLoads() {
//查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.ge("age",12);//g:大于 e:等于
userMapper.selectList(wrapper);//wrapper 对比 map
}
测试二:
//查询单个 名字叫Jack
@Test
public void test2(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","Jack");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
测试三:
// 查询年龄20~30的用户有多少个
@Test
public void test3(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
Integer count = userMapper.selectCount(wrapper);//查询结果数
System.out.println(count);
}
测试四:
// 查询年龄20~30的用户有谁
@Test
public void test4(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
List<Object> user = userMapper.selectObjs(wrapper);
user.forEach(System.out::println);
}
同理 not between用法也类似
测试五
//模糊查询
@Test
public void test5(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name","张")
.notLike("age",1)
.likeLeft("email","m")//左模糊 %X
.likeRight("id",1);//右模糊 X%
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
测试六:
//子查询
@Test
public void test6(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//id在子查询中查出来
wrapper.inSql("id","select id from user where id < 4");
List<Object> user = userMapper.selectObjs(wrapper);
user.forEach(System.out::println);
}
测试七:
//排序
@Test
public void test7(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderBy(true,false,"age","id");//参数一:是否要排序 参数二:是否ASC 参数三:字段
userMapper.selectList(wrapper);
}
测试八:
//分组
@Test
public void test8(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("name","email").groupBy(true,"id");
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}
代码生成器
最新配置方法以官方为准
1.首先将依赖和MybatisPlusConfig配置好
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mybatis-plus驱动-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
public class MybatisPlusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
//注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
//逻辑删除
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
//sql执行效率分析插件
@Bean
@Profile({"dev","test"})//设置dev test环境开启 保证效率
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//ms 设置sql执行的最大时间,超时则不执行
performanceInterceptor.setFormat(true);//是否开启格式化支持
return performanceInterceptor;
}
}
2.新建一个类,配置代码生成器
public class MybatisPlusGenerator {
public static void main(String[] args) {
//需要构建一个 代码自动生成器 对象
AutoGenerator autoGenerator = new AutoGenerator();
//配置策略
//1、全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");//获取用户的目录
gc.setOutputDir(projectPath+"/src/main/java/")//输出路径
.setAuthor("choi")//作者名
.setOpen(false)//是否打开资源管理器
.setFileOverride(false)//是否覆盖
.setServiceName("%sService")//去Service的I前缀
.setIdType(IdType.ID_WORKER)
.setDateType(DateType.ONLY_DATE)
.setSwagger2(true);
autoGenerator.setGlobalConfig(gc);
//2、配置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUsername("root")
.setPassword("123456")
.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8")
.setDriverName("com.mysql.cj.jdbc.Driver")
.setDbType(DbType.MYSQL);
autoGenerator.setDataSource(dsc);
//3、包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("test")//模块名
.setParent("com.choi")//放在哪个包下
.setEntity("entity")//实体类
.setMapper("mapper")//mapper
.setService("service")//service层
.setController("controller");//controller层
autoGenerator.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);// 自动生成lombok
strategy.setLogicDeleteFieldName("deleted");//逻辑删除
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);//localhost:8080/hello_id_2
//自动填充设置
TableFill gmt_creat = new TableFill("gmt_creat", FieldFill.INSERT);
TableFill gmt_modified = new TableFill("gmt_modified",FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmt_creat);
tableFills.add(gmt_modified);
strategy.setTableFillList(tableFills);//注意传入的是一个list
//乐观锁
strategy.setVersionFieldName("version");
autoGenerator.setStrategy(strategy);
autoGenerator.execute();//执行
}
3.执行后我们可以看到
可以发现代码都已经生成好了,具体要生成哪个表,怎么生成 改参数即可