MyBatis-Plus的使用
MyBatis-plus 是一个MyBatis的增强工具,在 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
快速开始
学习新技术的方式
1、导入对应的依赖
2、进行相关的配置
3、进行代码编写
步骤
1、创建数据库Mybatis_plus
2、创建user表
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)
);
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');
-- 实际开发中还有 version(乐观锁) deleted(软删除) create_time modify_time 等等
3、创建springboot的项目
4、导入对应依赖
<dependencies>
<!-- 连接Mysql的驱动-->
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
说明:使用mybatis-plus 可以简化代码、注意不要mybatis-plus和mybatis 重复导入 以免依赖出错误
5、连接数据库 这个和mybatis相似
# mysql 5 驱动不同 com.mysql.jdbc.Driver
spring.datasource.name=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=true&characterEncoding=utf8
#mysql 8 驱动不同 com.mysql.cj.jdbc.Driver 需要连接时区的配置 serverTimeZone=GMT%2B8
6、传统方式:pojo-mapper(连接mybatis 、配置mapper.xml文件)-service-controller
6、 使用了MyBatis-plus以后
-
pojo
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Long age; private String email; }
-
mapper
// 继承BaseMapper 就有了基本的CRUD 和查询功能 @Mapper public interface UserMapper extends BaseMapper<User> { }
注意启动类上:需要添加一个扫描注解
@MapperScan("com.hejiaxing.mapper")
-
测试使用
@SpringBootTest class MybatisPlusApplicationTests { @Resource private UserMapper userMapper; @Test void contextLoads() { List<User> list = userMapper.selectList(null); list.forEach(System.out::println); } } -- 结果 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)
配置日志
开发的时候要查看SQL执行情况,上线以后可以取消
#配置日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
输出结果
CRUD拓展
Insert插入操作
@Test
void testInsert() {
User user=new User();
user.setName("Hjx");
user.setAge(18L);
user.setEmail("906@qq.com");
int res = userMapper.insert(user); //自动帮我们生成了ID
System.out.println(res); //返回结果 受影响的行数
System.out.println(user); //生成以后的对象
}
结果:成功插入一条数据 ID自动生成 1392720750414139394
ID的生成策略:全局唯一
主键的生成策略
默认全局唯一ID ID_WORKER
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
//ID的 策略一般有 UUID、自增ID、雪花算法、zookeeper、ridis 等等
@TableId(type = IdType.ID_WORKER)
private Long id; //雪花算法默认
private String name;
private Long age;
private String email;
})
分布式系统唯一ID:https://zhuanlan.zhihu.com/p/63802895
**雪花算法**
Twitter开源,生成一个64bit(0和1)字符串(1bit不用,41bit表示存储时间戳,10bit表示工作机器id(5位数据标示位,5位机器标识位),12bit序列号)
![snowflake](C:\Users\hjx\Pictures\snowflake.jpg)
> 主键自增
配置主键自增需要执行的操作:
1、在实体类上设置ID的type;`@TableId(type = IdType.AUTO)`
2、设置数据库的ID为自增
3、再次执行插入数据测试
备注:上一个是雪花算法产生的ID、自增加1 ;结果正确
> 其余源码解释
~~~ java
public enum IdType {
AUTO(0), //自增策略
NONE(1), //不填写ID
INPUT(2),// 自己输入
ID_WORKER(3),//默认 雪花算法生成id
UUID(4),//UUID生成
ID_WORKER_STR(5);// 雪花算法的String类型
}
Update更新操作
@Test
void testUpdate() {
User user=new User();
user.setName("Update测试");
user.setId(1L);
int update = userMapper.updateById(user); //传入一个User 对象 自动拼接SQL 根据Id进行更新
System.out.println(update);
}
自动填充
一般正式环境中 还会有create_time 、modify_time 等;这些字段希望可以自动填充数据
方式1:数据库设置(一般不允许操作数据库)
Alter `user` add `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
Alter `user` add `modify_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间'
//新增User的属性
private Date createTime;
private Date modifyTime;
执行Insert插入操作-发现时间以及有了
方式2:mybatis-plus的自动填充
1、还原开始的数据库设置
2、配置bean的字段
//在对应字段上加入 @TableField(fill=) fill 填充 表示执行什么操作时候需要做的事情 默认是不执行任何操作
@TableField(fill = FieldFill.INSERT ) //表示执行插入的时候执行操作
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//表示执行插入-更新 的时候执行操作
private Date modifyTime;
3、创建自己的Handler 执行操作
@Slf4j
@Component //交给Sprng的IOC容器管理
public class MyMetaDataHandler implements MetaObjectHandler {
// 插入执行的事情
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("modifyTime",new Date(),metaObject);
}
//更新填充的时候的操作
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.setFieldValByName("modifyTime",new Date(),metaObject);
}
}
4、执行插入或者更新操作验证正确!
乐观锁插件
OptimisticLockerInnerInterceptor
乐观锁:顾名思义十分乐观、他总是以为不会出问题、无论干什么先不会上锁!如果出现了问题、再次更新值进行测试
悲观锁:顾名思义十分悲观、他总是以为会出问题、无论干什么都先上锁!在执行操作!
乐观锁实现方式:
-
取出记录时,获取当前version
-
更新时,带上这个version
-
执行更新时, set version = newVersion where version = oldVersion
-
如果version不对,就更新失败
步骤1、新增mysql字段
Alter `user` add `version` int(10) DEFAULT '1' COMMENT '版本控制'
步骤2、User对象新增version字段 并添加标签
@Version
private Integer version;
步骤3、spring boot配置config 乐观锁插件
@EnableTransactionManagement //管理事务
@MapperScan("com.hejiaxing.mapper") //扫描mapper
@Configuration //配置类
public class MybatisPlusInterceptor {
@Bean
public OptimisticLockerInterceptor mybatisPlusInterceptor() {
OptimisticLockerInterceptor interceptor = new OptimisticLockerInterceptor();
return interceptor;
}
}
步骤4、进行乐观锁的成功和失败测试
成功的正确执行-及在单线程的情况下无其他人执行操作
//测试乐观锁正确的方式仅一人执行更新操作
@Test
void testInterceptor() {
//第一步先查出对应的数据
User user = userMapper.selectById(1L);
//第二步 修改信息
user.setName("乐观锁1");
//第三步 执行更新操作
userMapper.updateById(user);
}
结果:先从数据库找到该条数据、然后执行更新操作的时候条件中有version=1 、然后version版本被修改为2 、修改成功!
失败的情况-有多人或者多线程执行操作;当版本不一样时无法执行
//测试乐观锁失败的方式 多人执行更新
@Test
void testInterceptorFail() {
//第一个人 找到数据 想要进行修改 但是被第二人抢先了
User user = userMapper.selectById(1L);
user.setName("乐观锁1");
//第二个人 找到数据 先修改了
User use1 = userMapper.selectById(1L);
use1.setName("乐观锁2");
//第三步 第二个人先 执行更新操作
int update = userMapper.updateById(use1);
System.out.println(use1);
int update1 = userMapper.updateById(user);
System.out.println(user);
//在有乐观锁的情况下:第二人先执行、此时版本version已经改变 第一人去执行时候Version版本不对应 即修改失败 数据库是第二个的修改内容
}
// 日志信息 1.找到数据
==> Preparing: SELECT id,name,age,email,create_time,modify_time,version FROM user WHERE id=?
==> Parameters: 1(Long)
<== Columns: id, name, age, email, create_time, modify_time, version
<== Row: 1, 乐观锁1, 18, test1@baomidou.com, null, 2021-05-14 07:20:18, 2
<== Total: 1
// 2.第二个人先执行更新 version改成3
==> Preparing: UPDATE user SET name=?, age=?, email=?, modify_time=?, version=? WHERE id=? AND version=?
==> Parameters: 乐观锁2(String), 18(Long), test1@baomidou.com(String), 2021-05-14 07:30:04.789(Timestamp), 3(Integer), 1(Long), 2(Integer)
<== Updates: 1
// 此时的version改为3
User(id=1, name=乐观锁2, age=18, email=test1@baomidou.com, createTime=null, modifyTime=Fri May 14 07:20:18 CST 2021, version=3)
//此时第一人再去修改:但是版本参数依旧是2 所有更新失败
==> Preparing: UPDATE user SET name=?, age=?, email=?, modify_time=?, version=? WHERE id=? AND version=?
==> Parameters: 乐观锁1(String), 18(Long), test1@baomidou.com(String), 2021-05-14 07:30:06.586(Timestamp), 3(Integer), 1(Long), 2(Integer)
// 受影响的行为0 更新失败
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e5beb8a]
User(id=1, name=乐观锁1, age=18, email=test1@baomidou.com, createTime=null, modifyTime=Fri May 14 07:20:18 CST 2021, version=2)
查询操作
@Test
void testSelect () {
// 根据ids进行查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L));
//实际SQL: SELECT id,name,age,email,create_time,modify_time,version FROM user WHERE id IN ( ? , ? )
users.forEach(System.out::println);
HashMap<String,Object> map=new HashMap();
map.put("name","hjx");
List<User> users = userMapper.selectByMap(map);
//实际执行SQL:: SELECT id,name,age,email,create_time,modify_time,version FROM user WHERE name = ?
// 自动拼接高级查询
users.forEach(System.out::println);
}
分页查询
分页方式:
1、使用limit进行分页
2、使用pageHelper进行分页
3、MP内置分页插件
使用步骤
1、配置分页插件
@EnableTransactionManagement //管理事务
@MapperScan("com.hejiaxing.mapper") //扫描mapper
@Configuration //配置类
public class MybatisPlusInterceptor {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
// 这是分页插件的config配置bean
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
return paginationInterceptor;
}
}
2、直接进行使用
@Test
void testSelectPage() {
// 分页查询 设置当前页为第一页 分页数为3
IPage<User> page=new Page(1,3);
IPage<User> userIPage = userMapper.selectPage(page, null);
//1.userIPage.getRecords() 获取所有记录
userIPage.getRecords().forEach(System.out::println);
// 2.userIPage.getTotal();获取所有总数
System.out.println(userIPage.getTotal());
//3. userIPage.getCurrent() 当前页 1
System.out.println(userIPage.getCurrent());
//4. userIPage.getPages() 获取一共有几页 4
System.out.println(userIPage.getPages());
}
删除操作
@Test
void testDelete() {
//根据id删除记录
//实际SQL:DELETE FROM user WHERE id=?
userMapper.deleteById(1392720750414139399L);
// 根据一些id进行删除
//实际SQL: DELETE FROM user WHERE id IN ( ? , ? )
userMapper.deleteBatchIds(Arrays.asList(1392720750414139398L,1392720750414139397L));
//根据map进行删除
//SQL:DELETE FROM user WHERE name = ?
HashMap<String,Object> map=new HashMap();
map.put("name","Hjx");
userMapper.deleteByMap(map);
}
但是实际开发中、很多地方会用到软删除;删除时改变指定字段的值、查询时候增加相应条件进行查询 数据库记录依旧存在;
逻辑删除
使用步骤
1、数据库新增 逻辑删除字段 deleted
Alter `user` add `deleted` int(1) DEFAULT '0' COMMENT '逻辑删除'
2、bean中新增对应字段
@TableLogic //注解TableLogic 表示是逻辑删除字段
private Integer deleted;
3、进行配置
@Bean
public ISqlInjector iSqlInjector(){
return new LogicSqlInjector();
}
# 配置逻辑删除 logic-delete-value 删除的值为1 没删除的为0
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
4、删除和查询测试 是否加上了逻辑删除
@Test
void testLogicDelete() {
//根据id删除记录
userMapper.deleteById(3L);
}
进行查询操作、查看是否已经加上了逻辑删除字段
@Test
void testLogicSelect () {
userMapper.selectById(1L);
}
条件构造器
查询时候需要构造各种方法 可以设置对应的Wrapper进行查询 一般使用QueryWrapper
allEq
判断全部相同或者个别是nulll
@Test
void contextLoads() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
HashMap<String,Object> map=new HashMap<String,Object>();
map.put("name","乐观锁2");
map.put("id",1L);
map.put("create_time",null);
wrapper.allEq(map);
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}
Eq 判断相等
@Test
void test() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("id",1L);
List<User> list = userMapper.selectList(wrapper);
// SELECT id,name,age,email,create_time,modify_time,version,deleted FROM user WHERE deleted=0 AND id = ?
list.forEach(System.out::println);
}
Ne 不等于 <>
gt 大于>
ge 大于等于 >=
lt 小于 <
le 小于等于 <=
between 两者之间
BETWEEN 值1 AND 值2
@Test
void test1() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//查询年龄在10-20的人 id大于等于2的
wrapper.ge("id",2L)
.between("age",10,20);
List<User> list = userMapper.selectList(wrapper);
//SELECT id,name,age,email,create_time,modify_time,version,deleted FROM user WHERE deleted=0 AND id >= ? AND age BETWEEN ? AND ?
list.forEach(System.out::println);
}
notBetween 不在两者之间
其他查询
1、like --->
like ‘%xxx%’
2、notLike --->
not like ‘%xxx%’
3、likeLeft --->
like ‘%xxx’
4、likeRight --->
like ‘xxx%’
5、isNull isNull("name")
—>name is null
6、isNotNull isNotNull("name")
—>name is not null
7、in in(“age”,{1,2,3})--->
age in (1,2,3)
8、notIn notIn(“age”,{1,2,3})--->
age not in (1,2,3)
@Test
void test2() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//查询 名字中带有乐观 创建时间是空的 并且id在 234的
wrapper.like("name","乐观")
.isNull("create_time")
.in("id",2L,3L,4L);
List<User> list = userMapper.selectList(wrapper);
//SELECT id,name,age,email,create_time,modify_time,version,deleted FROM user WHERE deleted=0 AND id >= ? AND age BETWEEN ? AND ?
list.forEach(System.out::println);
}
9、inSql
- 字段 IN ( sql语句 )
- 例:
inSql("age", "1,2,3,4,5,6")
—>age in (1,2,3,4,5,6)
- 例:
inSql("id", "select id from table where id < 3")
—>id in (select id from table where id < 3)
10、 notInSql
- 字段 NOT IN ( sql语句 )
- 例:
notInSql("age", "1,2,3,4,5,6")
—>age not in (1,2,3,4,5,6)
- 例:
notInSql("id", "select id from table where id < 3")
—>id not in (select id from table where id < 3)
11、 groupBy
- 例:
groupBy("id", "name")
—>group by id,name
12、orderByAsc 升序排列
13、orderByDesc 降序排列
其他还有很多 可以查看官网详细内容
代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
/**
* Author: hjx
* Date: 2021/5/17 16:05
* Describe: 这是MP的代码自动生成
*/
public class CodeGenerator {
public static void main(String[] args) {
// 1、创建代码生成器对象
AutoGenerator generator = new AutoGenerator();
//当前项目的文件夹地址
String projectPath = System.getProperty("user.dir");
// 2、全局配置
GlobalConfig globalConfig=new GlobalConfig();
globalConfig.setAuthor("HJX");// 设置作者
globalConfig.setOpen(false);// 设置自动打开文件夹
globalConfig.setOutputDir(projectPath+ "/src/main/java"); //设置文件输出的地址
globalConfig.setFileOverride(true); //设置文件重新生成覆盖之前的
globalConfig.setDateType(DateType.ONLY_DATE); //定义生成的实体类中日期类型
globalConfig.setServiceName("%sService"); //去掉Service接口的首字母I
globalConfig.setIdType(IdType.ID_WORKER_STR); //主键策略
globalConfig.setSwagger2(false);//开启Swagger2模式
generator.setGlobalConfig(globalConfig); //将配置放入到生成器中
// 3、数据库配置
DataSourceConfig dsc=new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=true&characterEncoding=utf8");
dsc.setDbType(DbType.MYSQL);
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
generator.setDataSource(dsc);
//4、包配置
PackageConfig pc=new PackageConfig();
pc.setController("controller");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setXml("mapper");
pc.setParent("com.hejiaxing");
// pc.setModuleName("testGen");
generator.setPackageInfo(pc);
//5、策略配置
StrategyConfig strategyConfig=new StrategyConfig();
strategyConfig.setInclude("t_dept"); //设置需要生成的表名称
strategyConfig.setNaming(NamingStrategy.underline_to_camel);//设置表的类型 t_user的 进行驼峰命名
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel); //字段 驼峰命名
strategyConfig.setEntityLombokModel(true);
strategyConfig.setRestControllerStyle(true); //开启rest的风格
//设置逻辑删除字段
strategyConfig.setLogicDeleteFieldName("deleted");
// 数据自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);//插入的时候自动更新
TableFill modifyTime = new TableFill("modify_time", FieldFill.INSERT_UPDATE);//插入 更新的时候自动更新
ArrayList<TableFill> tableFills=new ArrayList();
tableFills.add(createTime);
tableFills.add(modifyTime);
strategyConfig.setTableFillList(tableFills);
// 乐观锁
strategyConfig.setVersionFieldName("version");
generator.setStrategy(strategyConfig);
generator.execute(); //执行代码
trategyConfig.setEntityLombokModel(true);
strategyConfig.setRestControllerStyle(true); //开启rest的风格
//设置逻辑删除字段
strategyConfig.setLogicDeleteFieldName("deleted");
// 数据自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);//插入的时候自动更新
TableFill modifyTime = new TableFill("modify_time", FieldFill.INSERT_UPDATE);//插入 更新的时候自动更新
ArrayList<TableFill> tableFills=new ArrayList();
tableFills.add(createTime);
tableFills.add(modifyTime);
strategyConfig.setTableFillList(tableFills);
// 乐观锁
strategyConfig.setVersionFieldName("version");
generator.setStrategy(strategyConfig);
generator.execute(); //执行代码
}
}