Mybatis—plus
官网:https://baomidou.com/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CmcCe63t-1613443284747)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215204507297.png)]
MyBatis-Plus是什么?
MyBatis-Plus 是一个 MyBatis 的增强,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
plus就是“大,升级”的意思
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速入门
1.数据库的准备
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');
2.创建一个springboot应用
3.导入Mybatis-plus的依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
4.配置数据源
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eCQf5i3o-1613443284751)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215205615054.png)]
mysql 5
spring:
datasource:
username: root
password: root
url: jdbc:mysql:///mybatisplus?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mysql 8 url中还要配置时区
url: jdbc:mysql:///mybatisplus?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
5.pojo、Controller、mapper
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private int age;
private String email;
}
@RestController
public class UserController {
@Autowired
private UserMapper userMapper;
@RequestMapping("queryAllUser")
public String queryAllUser(){
List<User> users = userMapper.selectList(null);
String res = JSON.toJSONString(users);
return res;
}
}
@Repository
public interface UserMapper extends BaseMapper<User> {
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0CMAUziL-1613443284754)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215212510274.png)]
可以看到,我们再UserMapper中没有写任何东西,但是却可以调用UserMapper的方法,这是为什么呢?
所有的答案都来自于这儿:继承了BaseMapper接口+泛型
点进BaseMapper中去看看:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p6QVNlEk-1613443284757)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215213100756.png)]
配置日志
方法:
#配置日志 直接在控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tpw7WKKB-1613443284759)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215213633895.png)]
CRUD
插入
@PostMapping("insert")
public String insert(@RequestBody User user){
System.out.println("user1:"+user);
int result = userMapper.insert(user);
System.out.println("result:"+result);
return JSON.toJSONString(user);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uh9TT77d-1613443284760)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215215512488.png)]
这里我并没有传入id,但是mybatis-plus自动给我生成了一个id,没主键就插不进去,这好理解。
那么这里主键的生成策略是什么呢?是随机的吗?
不是,这里是用雪花算法生成的
SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的,后面的代码中有详细的注解。
如何改变主键的生成策略呢?
public class User {
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private int age;
private String email;
}
其中,IdType取枚举值:
public enum IdType {
AUTO(0), //自增
NONE(1), //没有主键
INPUT(2), //手动输入
ASSIGN_ID(3),//雪花算法生成id
ASSIGN_UUID(4),//uuid
更新
@PutMapping("update")
public String update(@RequestBody User user){
System.out.println("user:"+user);
//根据id来修改,但是参数是user对象
int i = userMapper.updateById(user);
System.out.println("i="+i);
return JSON.toJSONString(user);
}
可以自动实现条件拼接
自动填充
在开发中,像生成时间、修改时间等与时间相关的属性都是自动填充的,绝对不允许手动输入!
首先,在数据库上添加生成时间、修改时间两个字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vmNCy9X-1613443284761)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215225638301.png)]
同步实体类,并且添加注解
public class User {
@TableId(type=IdType.AUTO)
private Long id;
private String name;
private int age;
private String email;
@TableField(fill = FieldFill.INSERT) //添加时填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//添加和更新时填充
private LocalDateTime updateTime;
}
编写处理器,别忘了把他加到ioc容器中去 (忘记了可以看官网)
@Component
public class MyHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
System.out.println("start......");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class,LocalDateTime.now());
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
乐观锁
乐观锁:顾名思义,十分乐观。认为不会出现问题,无论干什么都不会去上锁。如果出现了问题,再次更新测试
悲观锁:顾名思义,十分悲观。认为总会出现问题,无论干什么都会去上锁再去操作。
乐观锁的实现方式:
- 取出时,读取出version
- 修改时,set version=newVersion where version=oldVersion
操作步骤:
1.给数据库添加version字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BF457zzk-1613443284762)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215232340333.png)]
2.同步实体类
@Version //乐观锁
private Integer version;
3.注册组件
@MapperScan("com.xianyu.mapper")
@EnableTransactionManagement //事务自动管理
@Configuration
public class MyBatisConfig {
//注册乐观锁插件
/*该方法已经弃用
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}*/
//注册乐观锁插件
@Bean
public MybatisPlusInterceptor MybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
4.测试成功
@Test
public void testOptimistic(){
User user = userMapper.selectById(1L);
user.setName("lzh咸鱼翻身");
userMapper.updateById(user);
}
5.失败测试
@Test
public void testOptimistic2(){
User user = userMapper.selectById(2);
user.setName("咸鱼fffff");
User user1 = userMapper.selectById(2);
user1.setName("咸鱼sssss");
userMapper.updateById(user1);
userMapper.updateById(user);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38a96593] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@424384387 wrapping com.mysql.cj.jdbc.ConnectionImpl@6f0c45f4] will not be managed by Spring
==> Preparing: UPDATE user SET name=?, age=?, email=?, update_time=?, version=? WHERE id=? AND version=?
==> Parameters: 咸鱼sssss(String), 20(Integer), test2@baomidou.com(String), 2021-02-15T23:46:07.442079300(LocalDateTime), 2(Integer), 2(Long), 1(Integer)
<== Updates: 1 //更新了1条
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38a96593]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@543b0737] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1850116381 wrapping com.mysql.cj.jdbc.ConnectionImpl@6f0c45f4] will not be managed by Spring
==> Preparing: UPDATE user SET name=?, age=?, email=?, update_time=?, version=? WHERE id=? AND version=?
==> Parameters: 咸鱼fffff(String), 20(Integer), test2@baomidou.com(String), 2021-02-15T23:46:07.453050900(LocalDateTime), 2(Integer), 2(Long), 1(Integer)
<== Updates: 0 //更新了0条
查询
批量查询
@Test
public void testBranch(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
for (User user : users) {
System.out.println(user);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wyu7LYCA-1613443284764)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210215235816231.png)]
条件查询的方法之一 ————map
@Test
public void testMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name", "咸鱼");
List<User> users = userMapper.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
查询出所有name为咸鱼的人
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bwva2hxt-1613443284765)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216000045756.png)]
分页查询
1.原始的limit分页
2.pagehelper分页
3.MP其实也内置了分页插件
- 配置组件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2.直接使用page对象即可
@Test
public void testPage(){
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page, null);
List<User> records = page.getRecords();
for (User record : records) {
System.out.println(record);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GND2TjZr-1613443284771)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216001306589.png)]
删除
@Test
public void testDel(){
//通过id删除
userMapper.deleteById(1361312829864538120L);
}
@Test
public void testDel2(){
//通过id批量删除
userMapper.deleteBatchIds(Arrays.asList(1,2,3,4));
}
@Test
public void testDelMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("age", 24);
//通过map删除
userMapper.deleteByMap(map);
}
逻辑删除
物理删除:从数据库中直接删除
逻辑删除:没有从数据库中删除,而是通过一个变量使其失效 deleted =0 --> deleted=1
1.数据库中增加一个deleted的字段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Seaw13Vs-1613443284772)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216002208547.png)]
2.pojo中新增属性
@TableLogic //逻辑删除注解
private Integer deleted;
3.application.yml中配置值
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
4.测试
@Test
public void testDel(){
//通过id删除
userMapper.deleteById(1361312829864538119L);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-67dZgiKS-1613443284773)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216003452314.png)]
测试一下查找
@Test
public void testfind(){
//通过id删除
userMapper.selectById(1361312829864538119L);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7iw2ryGD-1613443284774)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216003652079.png)]
自动拼接上了逻辑删除
性能分析插件
MP提供了一款性能测试插件来测试慢sql,如果执行时间超过这个时间就停止运行!
分析每条sql执行的时间
3.x版本以上mp弃用了
条件构造器
我们写一些复杂的sql就可以用他来替代。
测试一
@Test
public void test1(){
//查询name、email不为空,且年龄大于12的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name")
.isNotNull("email")
.gt("age", 12);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39zcfcMg-1613443284775)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216092142475.png)]
测试二
@Test
public void test2(){
//查询名字等于咸鱼的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "咸鱼");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1uHiNrIS-1613443284776)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216092423877.png)]
测试三
@Test
public void test3(){
//测试查询年龄在20-30之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age", 20, 30);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r2rLKjJ0-1613443284776)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216092728490.png)]
测试四
@Test
public void test4(){
//测试模糊查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name", "y")
.likeRight("id", "13");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OoIaJhJU-1613443284777)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216093008033.png)]
测试五
@Test
public void test5(){
//测试子查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where id < 1361312829864538115");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CiSUjGcC-1613443284778)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216093311131.png)]
测试六
@Test
public void test6(){
//测试排序查询
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id"); //id逆序排序
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qplzOWoy-1613443284779)(C:\Users\11384\AppData\Roaming\Typora\typora-user-images\image-20210216093537077.png)]
代码自动生成器
package com.kuang;
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 java.util.ArrayList;
// 代码自动生成器
public class KuangCode {
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("狂神说");
gc.setOpen(false);
gc.setFileOverride(false); // 是否覆盖
gc.setServiceName("%sService"); // 去Service的I前缀
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
//2、设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/kuang_community?
useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
//3、包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("blog");
pc.setParent("com.kuang");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
//4、策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("blog_tags","course","links","sys_settings","user_record","
user_say"); // 设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); // 自动lombok;
strategy.setLogicDeleteFieldName("deleted");
// 自动填充配置
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified",
FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtModified);
strategy.setTableFillList(tableFills);
// 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //
localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute(); //执行
}
}
easycode替代!