碎碎几句: MyBatis-Plus 是什么,如果不知道这个,那么你一定听到过 Iphone-plus,顾名思义,MyBatis-plus 是 Mybatis 的升级版,在使用Mybatis 的时候,对于建的 CRUD,我们还需要进行手动编写,为简化开发而生的 Mybatis-plus 将这些不费脑子的步骤进行融合,使得我们的开发更加关注于事务的逻辑。
学习过程中看的视频 狂神说MyBatisPlus,参考的文档Mybatis-Plus2.xxx,如果你使用的是 3.x 可能有些注解会显示过时,所以如果你跟我一样看的狂神的视频,可以将我的 pom 中的依赖 CV 到你的项目中。
创建数据库 mybatisplus。
创建表 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)
);
添加数据:
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
添加相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.0.5version>
dependency>
<dependency>
<groupId>com.h2databasegroupId>
<artifactId>h2artifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
配置数据库相关信息:
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
创建实体类: User:>
package com.pxl.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.stereotype.Component;
@Component
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class User {
private long id;
private String name;
private Integer age;
private String email;
}
创建 UserDao 接口 实现 BaseMapper< User> 接口:
package com.pxl.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pxl.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserDao extends BaseMapper<User> {
}
添加 MapperScan 注解,扫描 UserDao:
package com.pxl;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScans;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.pxl.dao")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
测试类:
package com.pxl;
import com.pxl.dao.UserDao;
import com.pxl.entity.User;
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 MybatisPlusApplicationTests {
// 创建一个 userDao 的对象
@Autowired
private UserDao userDao;
@Test
void contextLoads() {
List<User> users = userDao.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
}
在日常的 debug 和开发中, 学会看日志,分析日志内容弄是一个合格的程序员所必备的知识。
在 application.properties 文件中添加如下内容:
# 配置日志输出
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
insert:
@Test
public void testInsert() {
User user = new User();
user.setName("潘小蓝");
user.setAge(18);
user.setEmail("[email protected]");
int insert = userDao.insert(user);
System.out.println(insert);
}
分析一下日志:
Mybatis-Plus 自动实现了回填的功能,而且 id 在大多数情况下都是 唯一的。
默认 ID_WORKER 是全局唯一 id。
雪花算法: 生成全局唯一 id 的一种算法,有兴趣的小伙伴可以去了解一下。
自增方式生成 id:
// long 的包装类是 Long
@TableId(type = IdType.AUTO)
private Long id;
其余的源码解释:
public enum IdType {
AUTO(0), // 数据库 id 自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ASSIGN_ID(3), // 生成全局唯一 id (雪花算法)
ASSIGN_UUID(4), // 生成全局唯一 id (uuid)
/** @deprecated */
@Deprecated
ID_WORKER(3),
/** @deprecated */
@Deprecated
ID_WORKER_STR(3),
/** @deprecated */
@Deprecated
UUID(4);
private final int key;
private IdType(int key) {
this.key = key;
}
public int getKey() {
return this.key;
}
}
@Test
public void testUpdate() {
User user = new User();
user.setId(1L);
user.setName("李小桂");
user.setAge(20);
// 通过 id 更新信息,参数是一个对象
userDao.updateById(user);
}
在实际工作中,有些属性是自动化完成的,我们不希望手动更新。在 阿里巴巴的手册上,所有的数据表:gmt_create,gmt_modified 几乎所有的表都要配置上,而且需要自动化。
方式一:数据库级别:
在 User 类中添加属性:
private Date createTime;
private Date updateTime;
方式二:代码级别:
删除 数据库中的默认值。
在 User 类中的属性中加入注解:
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
编写处理器对注解进行处理:
package com.pxl.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.util.Date;
// 日志操作
@Slf4j
// 将这个类注入到 IOC 容器中
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert .....");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update ...........");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
意图: 当要更新一条记录的时候,希望这条记录没有被别人更新。
关于乐观锁与悲观锁的区别及使用在这篇博客中有详尽的解释:面试必备之乐观锁与悲观锁。
简单理解一下就是:
乐观锁: 这个锁是比较乐观的,它认为每个任务都是安全的,都不用上锁,如果出现了问题,就驳回再次进行更新值进行测试。
悲观锁: 从字面意思可以看出,这个锁是悲观的,看待每件事情都是先往坏的方面考虑,认为每个事务都是有风险的,都会将其上锁,只有当当前任务完成后,才会开锁。
乐观锁实现方式:
测试乐观锁的插件:
在 User 类中添加对应的属性,并且添加 乐观锁的注解:
// 乐观锁添加注解
@Version
private int version;
在配置类中注册 乐观锁的 插件:
package com.pxl.config;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@MapperScan("com.pxl.dao")
@Configuration
public class MyConfig {
@Bean
public OptimisticLockerInterceptor OptimisticLockerInnerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
测试:
@Test
// 测试乐观锁成功
public void testOptimistic() {
User user = userDao.selectById(2L);
user.setName("潘小玮");
user.setAge(18);
userDao.updateById(user);
}
```java
@Test
// 测试乐观锁失败
public void testOptimistic2() {
// 线程 1
User user = userDao.selectById(1L);
user.setName("xiaolan111");
// 线程插队
User user1 = userDao.selectById(1L);
user1.setName("xiaolan222");
userDao.updateById(user1);
userDao.updateById(user);
}
```
- 有乐观锁:
- 无乐观锁:
我们发现,没有乐观锁时,后面的线程结果会覆盖前面线程的结果,当我们更新某条记录的时候,发现这条记录已经被更新了,这就是 乐观锁的作用,用于判别。
// 简单查询
@Test
public void testSelect() {
// 根据 id 进行查询
User user = userDao.selectById(1l);
System.out.println(user);
List<User> users = userDao.selectList(null);
for (User user1 : users) {
System.out.println(user1);
}
}
// 批量查询
@Test
public void testSelect2() {
List<User> users = userDao.selectBatchIds(Arrays.asList(1, 2, 3));
for (User user : users) {
System.out.println(user);
}
}
// 条件查询
@Test
public void testSelect3() {
Map<String,Object> map = new HashMap<String,Object>();
// map 用来保存各个条件, MP 会进行自动拼接
map.put("age",18);
List<User> users = userDao.selectByMap(map);
for (User user : users) {
System.out.println(user);
}
}
分页在网页的各个地方随处可见,随之而来的插件也是应有尽有。
MyBatis-Plus 也有自身内置的分页插件,我们在此使用其内置的分页插件。
注册插件到 Spring 的 IOC 容器中。
// 注册分页插件到 IOC 容器中
@Bean
public PaginationInterceptor PaginationInterceptor() {
return new PaginationInterceptor();
}
利用 Page 对象进行分页查询。
// 分页查询
@Test
public void testPageHelper() {
/**
* 参数1 : 当前页
* 参数2 : 页面大小
*/
Page<User> page = new Page<>(1, 3);
// 查询数据库
Page<User> userPage = userDao.selectPage(page, null);
// 获取查询到的结果
List<User> records = userPage.getRecords();
// 输出结果
for (User user : records) {
System.out.println(user);
}
}
// 简单删除
@Test
public void testDelete(){
int result = userDao.deleteById(3L);
System.out.println(result);
}
// 批量删除
@Test
public void testDelete2() {
int result = userDao.deleteBatchIds(Arrays.asList(1323889317277933569L, 1323889317277933571L));
System.out.println(result);
}
// 条件删除
@Test
public void testDelete3() {
Map<String,Object> maps = new HashMap<>();
maps.put("age",18);
int i = userDao.deleteByMap(maps);
System.out.println(i);
}
在数据库中数据信息没有被移除,而是通过一个变量让这条数据失效。
在 User 类中添加 deleted 属性。
// 逻辑删除
@TableLogic
private Integer deleted;
在全局配置文件中添加如下属性。
# 逻辑删除
mybatis-plus.global-config.db-config.logic-delete-value=0
mybatis-plus.global-config.db-config.logic-not-delete-value=1
测试:
@Test
public void testLogicDelete() {
// 删除操作
int i = userDao.deleteById(1L);
System.out.println(i);
// 查询是否删除完成
List<User> users = userDao.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
在项目的上线过程中,项目的运行效率是我们经常要考虑的一个因素,项目一旦上线,修改起来相对麻烦,可以选择在开发过程或者测试过程中对性能进行分析,从而优化其代码。
在这里主要使用性能分析拦截器:用于输出每条SQL 语句及其执行时间。
导入插件:
// 性能分析插件
@Bean
@Profile({
"dev", "test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
performanceInterceptor().setMaxTime(1);
performanceInterceptor().setFormat(true);
return new PerformanceInterceptor();
}
测试:
@Test
public void testPerformance() {
List<User> users = userDao.selectList(null);
// System.out.println(user);
for (User user : users) {
System.out.println(user);
}
}
实体包装器,用于处理 sql 拼接,排序,实体参数查询等!
使用的是数据库字段,不是Java属性!
查询方式 | 说明 |
---|---|
setSqlSelect | 设置 SELECT 查询字段 |
where | WHERE 语句,拼接 + WHERE 条件 |
and | AND 语句,拼接 + AND 字段=值 |
andNew | AND 语句,拼接 + AND (字段=值) |
or | OR 语句,拼接 + OR 字段=值 |
orNew | OR 语句,拼接 + OR (字段=值) |
eq | 等于= |
allEq | 基于 map 内容等于= |
ne | 不等于<> |
gt | 大于> |
ge | 大于等于>= |
lt | 小于< |
le | 小于等于<= |
like | 模糊查询 LIKE |
notLike | 模糊查询 NOT LIKE |
in | IN 查询 |
notIn | NOT IN 查询 |
isNull | NULL 值查询 |
isNotNull | IS NOT NULL |
groupBy | 分组 GROUP BY |
having | HAVING 关键词 |
orderBy | 排序 ORDER BY |
orderAsc | ASC 排序 ORDER BY |
orderDesc | DESC 排序 ORDER BY |
exists | EXISTS 条件语句 |
notExists | NOT EXISTS 条件语句 |
between | BETWEEN 条件语句 |
notBetween | NOT BETWEEN 条件语句 |
addFilter | 自由拼接 SQL |
last | 拼接在最后,例如:last(“LIMIT 1”) |
测试一: 查询 name 不为空的用户,并且邮箱不为空的用户,年龄 >= 22.
@Test
public void test1() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.isNotNull("email")
.ge("age",22);
List<User> users = userDao.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
测试二:查询名字潘小猪:
@Test
public void test2() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","潘小猪");
List<User> users = userDao.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
测试三:查询年龄在 20 ~ 30 岁之间的用户:
@Test
public void test3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.between("age","20","30");
List<User> users = userDao.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
测试四:模糊查询:
@Test
public void test4() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
// name 中不包含 e 的
.notLike("name","e")
.likeRight("email","t");
List<User> users = userDao.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
测试五:id 在子查询中查询出来:
// 子查询
@Test
public void test5() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.inSql("id","select id from user where id <= 5");
List<User> users = userDao.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}
测试六:通过 id 进行排序:
@Test
public void test6() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
// 升序排列
.orderByAsc("id");
List<User> users = userDao.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
wrapper
// 降序排列
.orderByDesc("id");
List<User> users1 = userDao.selectList(wrapper);
for (User user : users1) {
System.out.println(user);
}
}
package com.pxl;
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;
import java.util.Properties;
/**
* 代码自动生成器
*/
public class MyCode {
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("panxiaolan");
gc.setOpen(false);
// 是否覆盖
gc.setFileOverride(false);
// 去 Service 的 I 前缀
gc.setServiceName("%sService");
// 设置 id 的生成策略
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("user");
pc.setParent("com.pxl");
pc.setMapper("mapper");
pc.setEntity("pojo");
pc.setController("controller");
pc.setService("service");
mpg.setPackageInfo(pc);
// 策略配置
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");
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
// 自动填充
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);
mpg.execute();
}
}