Insert 插入操作
@Test
void insertTest() {
User user = new User(); // 创建新的 对象
// 字段
user.setName("qhl");
user.setAge(1);
user.setEmail("qhl@xx.com");
int insert = userMapper.insert(user); // 自动生成了 id
System.out.println("insert = " + insert); // 受影响的行数
System.out.println("user = " + user);
}
运行结果如下, 可以发现, 没有设置 id 的时候, insert方法 自动生成了 id
原因: 使用 插入方法 插入的 id 的 默认值 为 全局的 唯一ID
产生奇妙 id 的 原因
在主键上使用不同的注解, 将会产生不同的ID.
如果没有 添加注解 且 主键为 Number子类, 默认自动生成 雪花算法 计算得到的 ID. 因此, 在 主键属性上 添加注解 至关重要; 添加的注解名称为 @TableId, 添加内容如下:
@TableId
描述: 主键注解
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 主键类型 |
#IdType
值 | 描述 |
---|---|
AUTO | 数据库 ID自增 |
NONE | 无状态, 该类型为 未设置主键类型 (注解里 等于 跟随全局, 全局里 约等于 INPUT) |
INPUT | insert前 自行 set 主键值 |
ASSIGN_ID | 分配ID (主键类型 为 Number(Long 和 Integer) 或 String) (since 3.3.0), 使用接口 IdentifierGenerator 的方法 nextId (默认实现类 为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配UUID, 主键类型 为 String(since 3.3.0), 使用接口IdentifierGenerator 的方法 nextUUID( 默认 default方法) |
分布式 全局唯一ID 长整型类型 (please use ASSIGN_ID) | |
32位 UUID字符串 (please use ASSIGN_UUID) | |
分布式 全局唯一ID 字符串类型 (please use ASSIGN_ID) |
默认 生成的方式为 NONE, 当插入对象 ID为空 且类型为 Number, 使用 雪花算法 自动补充.
@Getter
public enum IdType {
/**
* 数据库 ID自增
* <p> 该类型 请确保 数据库 设置了 ID自增 否则无效 </p>
*/
AUTO(0),
/**
* 该类型为 未设置主键类型 (注解里等于 跟随全局, 全局里 约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* <p>该类型 可以通过 自己注册 自动填充 插件 进行填充</p>
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空, 才自动填充. */
/**
* 分配ID (主键类型为 number 或 string),
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator} (雪花算法)
*
* @since 3.3.0
*/
ASSIGN_ID(3),
/**
* 分配UUID (主键类型为 string)
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
*/
ASSIGN_UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
主键 自增方案
1. 实体类字段 加上 @TableId 注解
@TableId(type = IdType.AUTO)
private Long id;
2. 数据库 设置为 主键自增
[删除原本字段后, 修改自动递增数为正常值]
3. 运行测试方法, 恢复正常 自增ID 即为成功
拓展: 分布式主键ID 生成方案
分布式系统 唯一ID 生成: https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法
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)
Update 更新操作
更新测试 代码如下:
@Test
void updateTest() {
User user = new User();
user.setId(6L); // 6 long
user.setName("John");
// 传入一个对象进行更新
int i = userMapper.updateById(user);
System.out.println("i = " + i);
}
// 注意: 虽然方法是 updateById, 但是 传入的参数 是 一个对象
自动填充
创建时间修改时间等操作一般都是自动化完成的,并不希望手动更新。
阿里巴巴开发手册里说明,几乎所有的数据库表都得有字段:gmt_create 和 gmt_modified. 并且需要自动化,即数据的创建时间和最后更新时间都得由程序来记录而不是人工设置。
数据库级别填充 (不推荐)
在表中新增字段 create_time, update_time
在 Navicat 中 设置如下, 类型和默认值都需要设置,将 create_time 和 update_time 的默认值 设置为 时间戳更新.
再次测试 插入方法, 同时 实体类同步
private Date createTime;
private Date updateTime;
重新运行 更新方法, 在 数据表中 即可看到 创建时间 和 更新时间 生成
**不推荐原因:**数据库并没有资格随便更改.
代码级别填充 (推荐)
删除 数据库的默认值
在 实体类字段 属性上 需要添加 注释@TableField
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
自定义实现类 MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
}
// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
注意:MySQL 不支持 LocalDateTime 转换,跟着开发文档里的 LocalDateTime 来 没有效果. 使用 Date对象 是 最保险方法.
如果需要从 蛇形命名 映射为 驼峰式命名, 配置里 进行配置.(默认开启,不配置也行)
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
设置完后,创建时间 和 更新时间 都可以 自动生成,而不必修改 数据库表设计.
乐观锁
乐观锁:顾名思义 十分乐观,它总是认为 不会出现问题,无论干什么 都 不会上锁。如果出现问题 就 再次更新值测试.(版本号version)
悲观锁:顾名思义 十分悲观,它总是认为 总会出现问题,无论干什么 都会上锁后 再去操作
乐观锁机制
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败, 例:先查询获得版本号并更新版本号
-- A
update user set name = "qhl", version = version + 1
where id = 2 and version = 1
-- B 如果该线程抢先完成,这个时候version = 2,会导致A修改失败
update user set name = "qhl", version = version + 1
where id = 2 and version = 1
前置操作:
数据库添加version字段,默认值为1 (记得 数据库 修改时,相应的实体类 也得进行 修改!!)
乐观锁配置 需要两步:
1. 配置插件
/** 主体插件: MybatisPlusInterceptor (3.4.0)
该插件内部插件集:
分页插件: PaginationInnerInterceptor
多租户插件: TenantLineInnerInterceptor
动态表名插件: DynamicTableNameInnerInterceptor
乐观锁插件: OptimisticLockerInnerInterceptor
sql性能规范插件: IllegalSQLInnerInterceptor
防止全表更新与删除插件: BlockAttackInnerInterceptor
*
*/
创建 config包,编写 配置类; 插件配置后 记得写 `@Bean`,将插件交给 IOC容器 进行托管
@Configuration
@MapperScan("com.qhl.mybatis_plus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
2. 在 实体类的字段上 加上 @Version
注解
@Version
private Integer version;
说明:
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion 会回写到 entity 中
仅支持 updateById(id) 与 update(entity, wrapper) 方法
在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
3. 测试乐观锁
乐观锁 正常运行情况
测试代码:
@Test
void optimisticLockerTest() {
// 1. 查询用户信息
User user = userMapper.selectById(1L);
// 2. 修改用户信息
user.setName("test01");
user.setEmail("test01@163.com");
// 3. 执行更新操作
userMapper.updateById(user);
}
测试结果 可得,数据成功 被更改的同时,version 自增1
查看IDE 控制台 日志,可以看到 使用的SQL语句
乐观锁 运行失败情况
测试代码:
@Test
void optimisticLockerTest2() {
// 线程1
User user = userMapper.selectById(1L);
user.setName("test02");
user.setEmail("test02@163.com");
// 模拟线程2 插队
User user2 = userMapper.selectById(1L);
user2.setName("test0222222");
user2.setEmail("test0222222@163.com");
userMapper.updateById(user2);
// 如果 没有乐观锁 就会覆盖 插队线程的值
userMapper.updateById(user);
}
测试结果 可得,user 因为 version 不匹配,无法更新.
Select 查询操作
// 测试查询 (测试类执行)
@Test
void selectById() {
User user = userMapper.selectById(1L);
System.out.println("user = " + user);
}
// 测试批量查询
@Test
void selectByBatchId() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println); // 遍历
}
// 按 条件查询 之 使用map操作
@Test
void selectByMap() {
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
// 自定义 查询条件
stringObjectHashMap.put("name", "test0222222");
List<User> users = userMapper.selectByMap(stringObjectHashMap);
users.forEach(System.out::println);
}
分页查询
常用分页方法:
原始的limit 进行 分页
pageHelper 等 第三方插件
MyBatisPlus 也有 相应的配置插件
使用 MyBatisPlus 的分页插件 进行 分页查询
具体步骤:
在 配置类中 添加 PaginationInnerInterceptor
@Configuration
@MapperScan("com.sjh.mybatis_plus.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
注意:有些网上的方法是将 `MybatisPlusInterceptor` 和`OptimisticLockerInnerInterceptor` 分开 单独放在 IOC容器中,增加可读性.
实际测试 表明,在这个版本的 MyBatisPlus 下 并不能正常运行.
进行测试
测试代码:
@Test
void pageTest() {
// 参数1:当前页; 参数2:页面大小(当前页的数据有几条)
Page<User> userPage = new Page<>(2, 5);
// 参数1:page对象; 参数2:wrapper
userMapper.selectPage(userPage, null);
userPage.getRecords().forEach(System.out::println);
}
// 查看 运行的 SQL语句 和 结果
delete 删除操作
通过ID 删除 单个user
@Test
void deleteById() {
int i = userMapper.deleteById(3L);
System.out.println("i = " + i);
}
使用 collection 删除 多个user
@Test
void deleteBatchIds() {
int i = userMapper.deleteBatchIds(Arrays.asList(4, 5, 7));
System.out.println("i = " + i);
}
使用map 按条件删除 特定user
@Test
void deleteByMap() {
HashMap<String, Object> stringObjectHashMap = new HashMap<>();
stringObjectHashMap.put("name", "qhlkf"); // 特定 user
int i = userMapper.deleteByMap(stringObjectHashMap);
System.out.println("i = " + i);
}
逻辑删除
官方文档: https://baomidou.com/pages/6b03c5/
物理删除:从 数据库中 直接移除
逻辑移除:在 数据库汇总 没有被移除,而是 通过一个变量 来 让它失效. deleted = 0 -> deleted = 1
管理员 可以查看 被删除的记录,防止 数据的丢失,类似于 回收站.
逻辑删除 设置方法
1. 在 数据库中 添加一个 deleted字段,默认值 为 0
2. 在 配置文件中 进行配置 逻辑删除,但是 新版本的 MyBatisPlus 默认配置了,所以 这个步骤 不用配置 也可以.
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的 实体字段名 (since 3.3.0, 配置后 可以忽略 不配置 步骤2)
logic-delete-value: 1 # 逻辑已删除值 (默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值 (默认为 0)
3. 实体类中 增加 deleted字段,并增加 注解@TableLogic
@TableLogic
private Integer deleted;
4. 测试类进行测试
测试代码:
@Test
void deleteById() {
int i = userMapper.deleteById(1L);
System.out.println("i = " + i);
}
查看 测试结果,逻辑删除 本质是 update操作
并且可以 观察得到,指定id 删除后的 user 的 deleted字段 变为了1,标记成了 被删除.
性能分析插件
在平时的开发中,会遇到一些慢SQL。慢SQL极大程度影响了运行速度。
MyBatisPlus拥有SQL分析与打印的拓展,依赖 p6spy 组件,完美的输出打印 SQL 及执行时长 3.1.0 以上版本。
步骤:
1. 引入p6spy依赖
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
2. application.yml 配置,修改 driverClass 和 URL
# DataSource Config
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8
3. 在 resources 文件夹中 添加spy.properties,配置内容如下:
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
# driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
4. 测试
使用selectById方法进行测试,可以看到console里面新增了SQL的运行时间记录。如果超过了慢SQL记录标准,运行时将会报错。
条件构造器 Wrapper (重要)
官方文档:https://baomidou.com/pages/10c804/#abstractwrapper
当书写 复杂SQL 时,可以使用 wrapper 进行书写 并 运行.
测试数据表如下:
id | name | age | version | deleted | create_time | update_time | |
---|---|---|---|---|---|---|---|
1 | test01 | 18 | tesst01@qq.com | 4 | 1 | NULL | 2022-1-21 00:12:18 |
2 | test233 | 23 | test233@163.com | 3 | 0 | NULL | 2022-1-21 11:06:38 |
6 | John222 | 1 | John222@126.com | 1 | 0 | 2022-1-21 10:24:11 | 2022-1-21 22:52:27 |
10 | Jack | 18 | jack@qq.com | 1 | 0 | 2022-1-21 11:07:08 | NULL |
11 | Bobby | 24 | bobby@aliyun.com | 1 | 0 | 2022-1-21 11:07:35 | NULL |
12 | Maria | 22 | NULL | 1 | 0 | 2022-1-21 11:07:58 | NULL |
1. 查询 多个符合条件的用户 并 返回List
测试代码:
// 查询 name不为空的用户 并且 邮箱不为空的用户, 年龄大于12岁
@Test
void selectList() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.isNotNull("name")
.isNotNull("email")
.gt("age", 12);
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
// console 日志打印
// 可以看到,需要符合条件的 user 才能被查询出来. 被逻辑删除的数据 尽管符合条件,但也不会 被查询出来
2. 查询 一个数据
// 查询 用户名为Maria的用户
@Test
void test02() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("name", "Maria");
System.out.println(userMapper.selectOne(userQueryWrapper));
}
// 查看 运行结果日志
3. 查询 满足范围的用户
// 查询年龄在20-30岁之间的用户数量
@Test
void test03() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(userQueryWrapper);
System.out.println("count = " + count);
}
// 查看 运行结果日志
4. 模糊查询
// 查询 名字里不含 "e", 邮箱后缀为 "@qq.com" 的用户
@Test
void test04() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.notLike("name", "e")
.likeLeft("email", "@qq.com");
List<Map<String, Object>> maps = userMapper.selectMaps(userQueryWrapper);
maps.forEach(System.out::println);
}
// 注意,likeLeft 表示的是 左边模糊右边确认. 看下面的源码便可明白.
/** LIKE '%值'
* Params: condition - 执行条件
* column: 字段
* value: 值
* Return: children
Children likeRight(boolean condition, R column, Object val)
*/
// 查看 运行结果日志:
5. 子查询
@Test
void test05() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.inSql("id", "select id from user where id < 11");
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
// 查看 运行结果日志:
6. 排序
@Test
void test06() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.orderByDesc("id"); // 根据 id 进行 排序
List<User> users = userMapper.selectList(userQueryWrapper);
users.forEach(System.out::println);
}
// 查看 运行结果日志:
用 IService 和 ServiceImpl 快速构建 Service层 (重要)
上面只讲述了 mapper层面 的方法不用书写,但是按照现在 流行软件架构,还有 Service层 和 Controller层的代码 需要书写.
Controller层 的代码 肯定是得 自己 根据业务需求 来书写了,不用想.
而 Service层,MyBatisPlus 封装了 一系列的方法,不用自己手写. 下文将会实战几种常用的 Service层方法. 完整的 Service CRUD接口 请查看官方文档 https://baomidou.com/pages/49cc81.
下面为 Service CRUD接口 的说明:
1. 通用 Service CRUD 封装 IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
2. 泛型 T 为任意实体对象
3. 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
4. 对象 Wrapper 为 条件构造器
Wrapper在这里又出现了,先不管,文章的后面会详细讲述 Wrapper的 使用方法.
上文 已经构造好了 UserMapper,下面继续 构造Service层 和 Controller层, 通过 网页 来 查看Service层的效果.
1. 构造 UserService接口
创建 service包后 创建 UserService接口,然后 该接口 继承IService接口,结束。简单吧,就是这么简单,基础CRUD的方法 都被 封装到了 IService 中.
public interface UserService extends IService<User> {
}
需要说明的是,IService 必须 指明泛型,泛型 为要 操作的实体类.
2. 构造 UserService 实现类 UserServiceImpl
UserServiceImpl 需要实现 UserService接口, 这是 常规操作. 但是 稍微想想都知道,完成这步后,还需要 自己实现 接口里的方法,这对于 高效率(爱偷懒)的 程序员 是 不友好的.
MyBatisPlus 当然想到了这个问题,所以设计了 IService 的实现类 ServiceImpl.
UserServiceImpl 继承了 ServiceImpl 后,便继承了 大量的基础CRUD方法,就不需要 自己写 常规的 Service层的 CRUD代码 了.
UserServiceImpl 代码如下:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
一样的,ServiceImpl 也需要 输入泛型. 第一个参数 为对应的 Mapper层接口, 第二个参数为 要操作的实体类.
代码书写后,查看 UserServiceImpl的结构, 可以看到 来自ServiceImpl的CRUD方法 都被顺利继承.
3. 书写 Controller层 进行测试
1. 创建 controller包 后, 创建 UserController类
其实 Service层方法 直接在 测试类 进行测试即可,但是写 Controller的原因 在于,从浏览器 直接一条龙到数据库 比较有感觉, 也顺便当做 练练手.
UserController 代码如下:
@Controller
public class UserController {
@Autowired
UserService userService;
@ResponseBody
@GetMapping("/allUser")
public List<User> allUser() {
return userService.list();
}
@PostMapping("/addUser")
public String addUser(User newUser) {
userService.save(newUser);
return "redirect:allUser";
}
}
额外说明:
@ResponseBody 标注返回的 不是路径,而是 直接返回json数据 (说的直白一点,我比较懒,不想写网页).
测试期望:
通过 访问路径 /allUser,期望获取 数据库里 user 的所有数据
通过 表格 进行 post到/addUser路径后,成功存储 一个新的用户 后,重定向到 /allUser 路径,获取所有用户,看新用户 有没有 被成功添加.
2. 用 HTML 写一个 简易的表格, 通过 post方法 测试用户 添加方法
如果 懒惰的同学 可以直接用 postman测试.
HTML代码 如下, 命名为 index.html, 同时放到 resources文件夹内,当做主页 (Springboot基础知识)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/addUser" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
邮箱:<input type="email" name="email"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
3. 测试
启动 springboot项目 后,访问 http://localhost:8080/allUser,成功获得 数据库里的 所有用户数据, 第一个期望 成功实现.
访问 http://localhost:8080/ 到表格 添加页面, 添加 以下数据 并 提交.
可以看到 表格提交后 重定向到了 allUser页面; 用户 狗剩儿 被成功添加,数据库里 也可以看到 狗剩儿被添加, 第二个期望 达成.
代码生成器
能够 不用重复性地 写简单的 mapper层 和 service层代码 已经是 非常优秀了,然而 更懒的程序员 肯定是 不会止步于此的.
很明显的原因在于,实体类,mapper层和service层的简单代码 甚至是 类的创建 本身就是重复性工作,按理来说 有更简单的方法.
确实,MyBatisPlus 有 对应的代码生成器,只要有 数据库,便可以 一键生成 相应的所有基础代码
步骤
1. 添加依赖
添加 代码生成器依赖
和 模板引擎依赖
.
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
2. 创建测试用的数据库
除了之前的 user表
外,我另外创建了一个数据库, 名称为 shop
, 具体字段如下:
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
`version` int(10) DEFAULT '1' COMMENT '乐观锁字段',
`deleted` int(1) DEFAULT '0' COMMENT '逻辑删除',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
3. 书写 代码生成器的 运行代码
创建一个 generator
包,书写 代码自动生成器 代码,具体代码如下, 有非常详细的注释,稍微修改后 便可 直接使用.
public class CodeGenerator {
public static void main(String[] args) {
// 创建一个 代码自动生成器 对象
AutoGenerator autoGenerator = new AutoGenerator();
// 1. 全局配置
// 注意:导入的包为 com.baomidou.mybatisplus.generator.config.GlobalConfig
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
// 文件 输出目录
globalConfig.setOutputDir(projectPath + "/src/main/java");
// 设置 作者名
globalConfig.setAuthor("John");
// 是否打开 输出目录(默认为true)
globalConfig.setOpen(false);
// 是否 覆盖已有文件
globalConfig.setFileOverride(true);
// 去除 生成的Service接口默认的"I"前缀
globalConfig.setServiceName("%sService");
// 指定 生成的主键ID类型,这里一样的,设置为常规的主键自增
globalConfig.setIdType(IdType.AUTO);
// 全局配置 注入 代码生成器
autoGenerator.setGlobalConfig(globalConfig);
// 2. 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
// 设置连接的数据库
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=false&characterEncoding=utf8");
dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123456");
autoGenerator.setDataSource(dataSourceConfig);
// 3. 包的配置
PackageConfig packageConfig = new PackageConfig();
// 设置 模块名称
packageConfig.setModuleName("generated");
// 设置 父包名
packageConfig.setParent("com.sjh");
autoGenerator.setPackageInfo(packageConfig);
// 4. 策略配置
StrategyConfig strategy = new StrategyConfig();
// 需要包含的表明(支持正则表达式)
strategy.setInclude("user", "shop");
// 设置 数据库表 和 字段 映射到 实体的命名策略,这里设置为 蛇形命名 转化为 驼峰式
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 不设置为 lombok模型(默认为false,如果需要,改为true)
strategy.setEntityLombokModel(false);
// 设置 逻辑删除属性
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");
autoGenerator.setStrategy(strategy);
// 运行 自动生成器
autoGenerator.execute();
}
}
可以看到,有四大基本配置:全局配置、数据源配置、包配置和策略配置。
此外,还有一个 模板引擎配置,在此处没有体现。
引擎配置 可以 自己制定模板引擎、生成路径 和 某些代码 是否需要生成,这些并没有必要 进行修改. 如果需要更改的话 查看官方文档 和 源码 即可.
必须要修改的地方有:
globalConfig.setAuthor("作者")设置作者名称;
数据源 必须要 自行设置,此处不做 详细说明;
包的配置,将要生成的代码的根文件夹 和 此文件夹的父文件夹 根据自己的需要 进行设置;
策略设置 是 最灵活的地方,strategy.setInclude("表名称") 用来指明 要生成的代码的数据表来源.
命名策略、自动填充字段设置、逻辑删除属性设置、乐观锁字段设置等,根据 实际情况 自行修改.
运行代码后,所有的基础代码 都生成了,并且有很好的注释 和 代码规范,以后 自己写都懒得写了.