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)
);
-- 真实开发中会有字段 version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified(创建、修改时间)
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]');
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.0.5version>
dependency>
说明:不要同时导入 mybatis 和 mybatis-plus,依赖的版本有差异会导致一些问题
# mysql 5 驱动 com.mysql.jdbc.Driver
# mysql 8 驱动 com.mysql.cj.jdbc.Driver 高版本兼容低版本,url还比5需要多添加时区设置:serverTimezone=GMT%2B8
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
username: root
password: 123456
6. 传统操作数据库方式:pojo-dao(连接 mybatis, 配置 mapper.xml 文件)-service-controller
使用 mybatis-plus:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
@Repository
public interface UserMapper extends BaseMapper<User> {
//@Repository 注册进容器并标识为持久层
//此时所有的 CRUD 操作都已编写完成,无需再配置一堆文件,因为继承了 BaseMapper,它有许多操作的方法
//我们也可以编写自己的扩展方法
}
别忘了主启动类上加 Mapper 扫描注解
@MapperScan("com.sky.mapper")
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//参数是一个 Wrapper(条件构造器),这里先不用
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
我们所有的 sql 现在不可见,但我们希望知道它如何执行,所以必须配置日志
只需要在 application.yaml 加一个配置即可:
#日志配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
输入 mylog 首选就是这个属性,再输入 std 就出来属性值了
依旧不用任何配置,直接测试类测试
@Test
public void testInsert(){
User user = new User();
user.setName("sky");
user.setAge(18);
user.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result);
System.out.println(user);
}
测试后我们发现我们并未设置 id,但是 id 却自动生成了
我们知道数据库插入的 id 的默认值为:全局的唯一 id,那么这是如何实现的?
默认使用 ID_WORKER 全局唯一 id 策略
主键自增
我们需要的配置:
其他
public enum IdType {
AUTO(0), //数据库 id 自增
NONE(1), //未设置主键
INPUT(2), //手动输入
ID_WORKER(3), //默认的全局唯一 id
UUID(4), //全局唯一id uuid
ID_WORKER_STR(5); //ID_WORKER 的字符串表示法
}
直接测试类
@Test
public void testUpdate(){
User user = new User();
user.setId(1L);
user.setAge(19);
userMapper.updateById(user);
}
注意传参是一个 user 对象,而不是真的是传个 id 进去,测试后发现 Jone 的 age 变成了 19
你多修改几个属性观察打印的 sql 会发现 MyBaitsPlus 会自动帮你动态拼接 sql
阿里巴巴开发手册:所有的数据库表 gmt_create、gmt_modified 几乎所有的表都要配置这两个字段,并且需要自动化(gmt:格林尼治时间)
创建时间别勾选根据当前时间戳更新,不然更新操作连创建时间一起更新
对应的,实体类也添加字段
private Date createTime;
private Date updateTime;
再次更新测试发现确实更新时间
先删除数据库中刚才为时间添加的配置
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
编写处理器来处理注解即可!
@Slf4j
@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);
}
}
乐观锁:顾名思义,总是认为不会出现问题,无论干什么都不去上锁,如果出现问题就再次更新值测试
悲观锁:顾名思义,十分悲观,认为干什么都会出现问题,无论干什么都会上锁再去操作
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
举个例子:
线程A:update user set name = "Jack",version = version + 1 where id = 2 and version = 1
线程B:update user set name = "Jack",version = version + 1 where id = 2 and version = 1
如果线程 A 在查询到 version 为 1 时,线程 B 不仅查询到 version 为 1,都进行完了修改操作,那么此时线程 A 继续工作,在执行更新操作时要把 1 更新为 2,但是 此时发现 version 都已经变成 2 了,那么就不更新
@Version
private Integer version;
@EnableTransactionManagement
/**
* 这是 MyBatisPlus 应该做的,所以把包扫描注解从主启动类移了过来
*/
@MapperScan("com.sky.mapper")
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
@Test
public void testOptimisticLocker2(){
//线程1
User user = userMapper.selectById(1L);
user.setAge(111);
//模拟另一个线程执行插队操作
User user2 = userMapper.selectById(1L);
user2.setAge(222);
userMapper.updateById(user2);
//如果没有乐观锁,那么 age 本来更新成了 222 会被覆盖成 111,但是有乐观锁,所以还是 222
userMapper.updateById(user);
}
根据 id 查询一个用户就不说了
根据一堆 id 查询一堆用户:
@Test
public void testSelectBatchId(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
使用 map 条件查询
@Test
public void testSelectByMap(){
HashMap<String,Object> map = new HashMap<>();
map.put("name","Jack");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
MP 配置类中加一行,添加组件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
@Test
public void testPage(){
//参数1:当前页,参数2:页面大小
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
}
物理删除:从数据库中直接移除
逻辑删除:在数据库中未被移除,而是通过变量来让他失效,也就是软删除
新增 int 类型 deleted 字段,长度 1,默认值 0,代表未被删除;1 代表被删除
实体类增加字段
@TableLogic
private Integer deleted;
同样的 MP 配置类中添加组件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
其实不配置好像也没关系,但是字段上的注解以及 application 配置一定要有,不然人家怎么知道什么值代表删除
#日志配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#逻辑删除
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
配置完以后测试删除操作时,不再删除记录,而是将 deleted 字段的值置为你设置的 1,此时进行查询测试,观察 sql 语句会发现自动拼接了 and deleted=0
我用了直接原地爆炸,官网也已经移除这个
还是一样我们先把组件注册进来,在 MP 配置类中
@Bean
@Profile({"dev","test"})
public PerformanceInterceptor performanceInterceptor(){
//性能分析插件
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//设置执行sql的最大时间,如果超过了则不执行
performanceInterceptor.setMaxTime(1);
//设置是否开启格式化
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
这里注意这个配置只在 dev 和 test 环境下生效,所以我们需要在 application.yaml 中设置当前环境
之前的都是简单的条件查询,而使用条件构造器,几乎可以满足所有 sql 语句的实现
@Test
public void contextLoads() {
//查询名字、邮箱不为空且年龄大于 20 的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name").isNotNull("email").ge("age",20);
userMapper.selectList(wrapper).forEach(System.out::println);
}
我的测试类用的是 import org.junit.Test;
,然后我就发现我得在类上加 @RunWith(SpringRunner.class)
不然测试跑不起来
如果用 import org.junit.jupiter.api.Test;
则不需要上面那个注解,并且方法不能加 public
ge 竟然是 GreaterEqual 也就是大于等于的意思,这名字起的真不咋的,你用 greatEq 都比这个见名知意
@Test
void test2(){
//查询名字等于 Jone 的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","Jone");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
查询一个的时候用 selectOne 否则别用,不然一个对象变量怎么接受两个对象的值
@Test
void test3(){
//查询年龄在 20 - 25 之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,25);
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
selectCount 可以得到查询结果有几条,用 selectList 还是一样获得查询到的用户
@Test
void test4(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name","e").likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
likeLeft 就是比如 %t,t 开头所以用 rightLike 就是 t%,直接 like 就是 %t%
用子查询查询 id 小于 3 的
@Test
void test5(){
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);
}
如果是 in,那么你能理解这个条件,比如 select * from user where id in (1,2,3)
那么这里的 inSql 其实就是 select * from user where id in (sql)
最终结果就是 select * from user where id in (select id from user where id<3)
@Test
void test6(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
userMapper.selectList(wrapper).forEach(System.out::println);
}
万物皆对象,所以先创建一个自动生成器,然后执行代码生成操作,当然此时执行是等于啥也没干
public class CodeGenerator {
public static void main(String[] args) {
//构建代码生成器对象
AutoGenerator autoGenerator = new AutoGenerator();
//执行生成代码操作
autoGenerator.execute();
}
}
生成肯定是根据你配置的生成策略来生成,所以来先全局配置一下
//配置策略
//1.全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//文件输出目录
config.setOutputDir(projectPath + "/src/main/java");
//设置作者
config.setAuthor("sky");
//生成完了是否打开资源管理器中生成的目录
config.setOpen(false);
//是否覆盖原来生成的
config.setFileOverride(true);
//去掉生成的 service 的 I 前缀
config.setServiceName("%sService");
//主键生成策略为雪花算法
config.setIdType(IdType.ASSIGN_ID);
//设置日期格式
config.setDateType(DateType.ONLY_DATE);
//自动配置swagger文档
config.setSwagger2(true);
//为代码生成器设置为该全局配置
generator.setGlobalConfig(config);
别导错包,我一开始就导成核心类的了
import com.baomidou.mybatisplus.generator.AutoGenerator;
然后设置数据源
//2.设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?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);
generator.setDataSource(dsc);
然后包配置
//3.包配置
PackageConfig pc = new PackageConfig();
//生成的模块名
pc.setModuleName("test");
//此时最后生成的文件都会在 com.sky.test 下
pc.setParent("com.sky");
pc.setEntity("model");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
generator.setPackageInfo(pc);
最后是策略配置
//4.策略配置
StrategyConfig strategyConfig = new StrategyConfig();
//包名下换线转驼峰
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
//列名下划线转驼峰
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
//要生成的表名
strategyConfig.setInclude("user");
//自动为实体类增加 lombok 注解
strategyConfig.setEntityLombokModel(true);
//逻辑删除字段
strategyConfig.setLogicDeleteFieldName("deleted");
//自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.UPDATE);
strategyConfig.setTableFillList(Arrays.asList(createTime,updateTime));
//乐观锁
strategyConfig.setVersionFieldName("version");
//controller 使用 restful 风格
strategyConfig.setRestControllerStyle(true);
//controller层请求配置驼峰转连字符
strategyConfig.setControllerMappingHyphenStyle(true);
generator.setStrategy(strategyConfig);
老实说最后代码生成器代码不多,关键就是解决依赖是真的麻烦中的麻烦,首先是下面这个报错,说是什么什么版本不兼容,我就按照网上的把 MP 依赖版本都改成 3.4.0
com.baomidou.mybatisplus.core.toolkit.StringUtils.isNotEmpty
然后又是报错,不过这个是模板引擎的问题,添加依赖就好
org/apache/velocity/context/Context
最后附上 pom.xml 中的全部依赖
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.4version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>mybatis_plusartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mybatis_plusname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plusartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.4.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.velocitygroupId>
<artifactId>velocity-engine-coreartifactId>
<version>2.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-testartifactId>
<version>2.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.2.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiter-apiartifactId>
<version>5.3.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>5.2.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>2.3.28version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
<version>1.5.13version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-site-pluginartifactId>
<version>3.7.1version>
plugin>
plugins>
build>
project>
package com.sky.mybatis_plus;
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.Arrays;
/**
* @author sky
* @date 2022/4/1 17:06
*/
public class CodeGenerator {
public static void main(String[] args) {
//构建代码生成器对象
AutoGenerator generator = new AutoGenerator();
//配置策略
//1.全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//文件输出目录
config.setOutputDir(projectPath + "/src/main/java");
//设置作者
config.setAuthor("sky");
//生成完了是否打开资源管理器中生成的目录
config.setOpen(false);
//是否覆盖原来生成的
config.setFileOverride(true);
//去掉生成的 service 的 I 前缀
config.setServiceName("%sService");
//主键生成策略为雪花算法
config.setIdType(IdType.ASSIGN_ID);
//设置日期格式
config.setDateType(DateType.ONLY_DATE);
//自动配置swagger文档
config.setSwagger2(true);
//为代码生成器设置为该全局配置
generator.setGlobalConfig(config);
//2.设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?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);
generator.setDataSource(dsc);
//3.包配置
PackageConfig pc = new PackageConfig();
//生成的模块名
pc.setModuleName("test");
//此时最后生成的文件都会在 com.sky.test 下
pc.setParent("com.sky");
pc.setEntity("model");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
generator.setPackageInfo(pc);
//4.策略配置
StrategyConfig strategyConfig = new StrategyConfig();
//包名下换线转驼峰
strategyConfig.setNaming(NamingStrategy.underline_to_camel);
//列名下划线转驼峰
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
//要生成的表名
strategyConfig.setInclude("user");
//自动为实体类增加 lombok 注解
strategyConfig.setEntityLombokModel(true);
//逻辑删除字段
strategyConfig.setLogicDeleteFieldName("deleted");
//自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.UPDATE);
strategyConfig.setTableFillList(Arrays.asList(createTime,updateTime));
//乐观锁
strategyConfig.setVersionFieldName("version");
//controller 使用 restful 风格
strategyConfig.setRestControllerStyle(true);
//controller层请求配置驼峰转连字符
strategyConfig.setControllerMappingHyphenStyle(true);
generator.setStrategy(strategyConfig);
//执行生成代码操作
generator.execute();
}
}