MyBatis-Plus系列推薦閱讀順序:
本文目錄結(jié)構(gòu)
一、SQL日志開關(guān)
二、常用注解
三、代碼生成器
四、分頁(yè)查詢
五、Mybatis-Plus Wrapper
六、自動(dòng)填充數(shù)據(jù)功能
七、邏輯刪除
八、樂觀鎖
一、SQL日志開關(guān)
配置文件application.properties,增加最后一行,執(zhí)行時(shí)會(huì)打印出 sql 語(yǔ)句。
spring.application.name=mybatis-plus
# 應(yīng)用服務(wù) WEB 訪問端口
server.port=8080
####數(shù)據(jù)庫(kù)連接池###
spring.datasource.url=jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.username=guo
spring.datasource.password=205010guo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
####輸出sql日志###
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
類似JPA的日志輸出配置:
jpa:
show-sql:true#打印SQL。
二、常用注解
注解說明的官方文檔:https://mybatis.plus/guide/annotation.html
2.1【@TableName 】
@TableName 用于定義表名
注:
常用屬性:
value 用于定義表名
2.2【@TableId】
@TableId 用于定義表的主鍵
注:
常用屬性:
value 用于定義主鍵字段名
type 用于定義主鍵類型(主鍵策略 IdType)
主鍵策略:
IdType.AUTO 主鍵自增,系統(tǒng)分配,不需要手動(dòng)輸入
IdType.NONE 未設(shè)置主鍵
IdType.INPUT 需要自己輸入 主鍵值。
IdType.ASSIGN_ID 系統(tǒng)分配 ID,用于數(shù)值型數(shù)據(jù)(Long,對(duì)應(yīng) mysql 中 BIGINT 類型)。
IdType.ASSIGN_UUID 系統(tǒng)分配 UUID,用于字符串型數(shù)據(jù)(String,對(duì)應(yīng) mysql 中 varchar(32) 類型)。
2.3【@TableField】
@TableField 用于定義表的非主鍵字段。
注:
常用屬性:
value 用于定義非主鍵字段名
exist 用于指明是否為數(shù)據(jù)表的字段, true 表示是,false 為不是。
fill 用于指定字段填充策略(FieldFill)。
字段填充策略:(一般用于填充 創(chuàng)建時(shí)間、修改時(shí)間等字段)
FieldFill.DEFAULT 默認(rèn)不填充
FieldFill.INSERT 插入時(shí)填充
FieldFill.UPDATE 更新時(shí)填充
FieldFill.INSERT_UPDATE 插入、更新時(shí)填充。
2.4【@TableLogic】
@TableLogic 用于定義表的字段進(jìn)行邏輯刪除(非物理刪除)
注:
常用屬性:
value 用于定義未刪除時(shí)字段的值
delval 用于定義刪除時(shí)字段的值
2.5【@Version】
@Version 用于字段實(shí)現(xiàn)樂觀鎖
三、代碼生成器
3.1 AutoGenerator 簡(jiǎn)介
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個(gè)模塊的代碼,極大的提升了開發(fā)效率?! ∨c mybatis 中的 mybatis-generator-core 類似。
3.2 添加依賴
<!-- 代碼生成器 依賴-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mp.version}</version>
</dependency>
<!-- 添加 模板引擎 依賴 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
3.3 生成器代碼分析
Step1:
創(chuàng)建一個(gè) 代碼生成器。用于生成代碼。
此處不用修改。
// Step1:代碼生成器
AutoGenerator mpg = new AutoGenerator();
Step2:
配置全局信息。指定代碼輸出路徑,以及包名、作者等信息。
此處按需添加,projectPath 需要修改,setAuthor 需要修改。
// Step2:全局配置
GlobalConfig gc = new GlobalConfig();
// 填寫代碼生成的目錄(需要修改)
String projectPath = "E:\\myProject\\test\\test_mybatis_plus";
// 拼接出代碼最終輸出的目錄
gc.setOutputDir(projectPath + "/src/main/java");
// 配置開發(fā)者信息(可選)(需要修改)
gc.setAuthor("郭秀志 jbcode@126.com");
// 配置是否打開目錄,false 為不打開(可選)
gc.setOpen(false);
// 實(shí)體屬性 Swagger2 注解,添加 Swagger 依賴,開啟 Swagger2 模式(可選)
//gc.setSwagger2(true);
// 重新生成文件時(shí)是否覆蓋,false 表示不覆蓋(可選)
gc.setFileOverride(false);
// 配置主鍵生成策略,此處為 ASSIGN_ID(可選)
gc.setIdType(IdType.ASSIGN_ID);
// 配置日期類型,此處為 ONLY_DATE(可選)
gc.setDateType(DateType.ONLY_DATE);
// 默認(rèn)生成的 service 會(huì)有 I 前綴
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
Step3:
配置數(shù)據(jù)源信息。用于指定 需要生成代碼的 數(shù)據(jù)倉(cāng)庫(kù)、數(shù)據(jù)表。
setUrl、setDriverName、setUsername、setPassword均需修改。
// Step3:數(shù)據(jù)源配置(需要修改)
DataSourceConfig dsc = new DataSourceConfig();
// 配置數(shù)據(jù)庫(kù) url 地址
dsc.setUrl("jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8");
// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定數(shù)據(jù)庫(kù)名
// 配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// 配置數(shù)據(jù)庫(kù)連接用戶名
dsc.setUsername("root");
// 配置數(shù)據(jù)庫(kù)連接密碼
dsc.setPassword("123456");
mpg.setDataSource(dsc);
Step4:
配置包信息。
setParent、setModuleName均需修改。其余按需求修改.
// Step:4:包配置
PackageConfig pc = new PackageConfig();
// 配置父包名(需要修改)
pc.setParent("com.erbadagang.mybatis.plus");
// 配置模塊名(需要修改)
//pc.setModuleName("mybatis-plus-starter");
// 配置 entity 包名
pc.setEntity("entity");
// 配置 mapper 包名
pc.setMapper("mapper");
// 配置 service 包名
pc.setService("service");
// 配置 controller 包名
pc.setController("controller");
mpg.setPackageInfo(pc);
Step5:
配置數(shù)據(jù)表映射信息。
setInclude 需要修改,其余按實(shí)際開發(fā)修改。
// Step5:策略配置(數(shù)據(jù)庫(kù)表配置)
StrategyConfig strategy = new StrategyConfig();
// 指定表名(可以同時(shí)操作多個(gè)表,使用 , 隔開)(需要修改)
strategy.setInclude("t_user");
// 配置數(shù)據(jù)表與實(shí)體類名之間映射的策略
strategy.setNaming(NamingStrategy.underline_to_camel);
// 配置數(shù)據(jù)表的字段與實(shí)體類的屬性名之間映射的策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 配置 lombok 模式
strategy.setEntityLombokModel(true);
// 配置 rest 風(fēng)格的控制器(@RestController)
strategy.setRestControllerStyle(true);
// 配置駝峰轉(zhuǎn)連字符
strategy.setControllerMappingHyphenStyle(true);
// 配置表前綴,生成實(shí)體時(shí)去除表前綴
// 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus,去除前綴后剩下為 user。
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
表t_user建表SQL:
/*
Navicat Premium Data Transfer
Source Server : 上海
Source Server Type : MySQL
Source Server Version : 50636
Source Host : 101.133.227.13:3306
Source Schema : orders_1
Target Server Type : MySQL
Target Server Version : 50636
File Encoding : 65001
Date: 10/07/2020 16:28:23
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`password` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
`pwd_cipher` varchar(55) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
Step6:
執(zhí)行代碼生成操作。
此處不用修改。
// Step6:執(zhí)行代碼生成操作
mpg.execute();
完整代碼:
package com.erbadagang.mybatis.plus.mybatisplus;
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.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* AutoGenerationTest作用是:生成Mybatis-plus代碼,AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個(gè)模塊的代碼,極大的提升了開發(fā)效率。
*
* @ClassName: AutoGenerationTest
* @author: 郭秀志 jbcode@126.com
* @date: 2020/7/10 15:08
* @Copyright:
*/
@SpringBootTest
public class AutoGenerationTest {
@Test
public void autoGenerate() {
// Step1:代碼生成器
AutoGenerator mpg = new AutoGenerator();
// Step2:全局配置
GlobalConfig gc = new GlobalConfig();
// 填寫代碼生成的目錄(需要修改)
String projectPath = "D:\\dev\\GitRepository\\mybatis-plus-starter";
// 拼接出代碼最終輸出的目錄
gc.setOutputDir(projectPath + "/src/main/java");
// 配置開發(fā)者信息(可選)(需要修改)
gc.setAuthor("郭秀志 jbcode@126.com");
// 配置是否打開目錄,false 為不打開(可選)
gc.setOpen(false);
// 實(shí)體屬性 Swagger2 注解,添加 Swagger 依賴,開啟 Swagger2 模式(可選)
//gc.setSwagger2(true);
// 重新生成文件時(shí)是否覆蓋,false 表示不覆蓋(可選)
gc.setFileOverride(false);
// 配置主鍵生成策略,此處為 ASSIGN_ID(可選)
gc.setIdType(IdType.AUTO);
// 配置日期類型,此處為 ONLY_DATE(可選)
gc.setDateType(DateType.ONLY_DATE);
// 默認(rèn)生成的 service 會(huì)有 I 前綴
gc.setServiceName("I%sService");
mpg.setGlobalConfig(gc);
// Step3:數(shù)據(jù)源配置(需要修改)
DataSourceConfig dsc = new DataSourceConfig();
// 配置數(shù)據(jù)庫(kù) url 地址
dsc.setUrl("jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定數(shù)據(jù)庫(kù)名
// 配置數(shù)據(jù)庫(kù)驅(qū)動(dòng)
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// 配置數(shù)據(jù)庫(kù)連接用戶名
dsc.setUsername("guo");
// 配置數(shù)據(jù)庫(kù)連接密碼
dsc.setPassword("205010guo");
mpg.setDataSource(dsc);
// Step:4:包配置
PackageConfig pc = new PackageConfig();
// 配置父包名(需要修改)
pc.setParent("com.erbadagang.mybatis.plus.mybatisplus");
// 配置模塊名(需要修改)
//pc.setModuleName("mybatis-plus-starter");
// 配置 entity 包名
pc.setEntity("entity");
// 配置 mapper 包名
pc.setMapper("mapper");
// 配置 service 包名
pc.setService("service");
// 配置 controller 包名
pc.setController("controller");
mpg.setPackageInfo(pc);
// Step5:策略配置(數(shù)據(jù)庫(kù)表配置)
StrategyConfig strategy = new StrategyConfig();
// 指定表名(可以同時(shí)操作多個(gè)表,使用 , 隔開)(需要修改)
strategy.setInclude("t_user");//表名t_user
// 配置數(shù)據(jù)表與實(shí)體類名之間映射的策略
strategy.setNaming(NamingStrategy.underline_to_camel);
// 配置數(shù)據(jù)表的字段與實(shí)體類的屬性名之間映射的策略
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 配置 lombok 模式
strategy.setEntityLombokModel(true);
// 配置 rest 風(fēng)格的控制器(@RestController)
strategy.setRestControllerStyle(true);
// 配置駝峰轉(zhuǎn)連字符
strategy.setControllerMappingHyphenStyle(true);
// 配置表前綴,生成實(shí)體時(shí)去除表前綴
// 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus,去除前綴后剩下為 user。
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
// Step6:執(zhí)行代碼生成操作
mpg.execute();
}
}
3.4 測(cè)試生成的service
由于生成的Service接口及實(shí)現(xiàn)類有些問題,需要稍為改造一下:
- Service接口:public interface ITUserService extends IService<TUser> 增加泛型:public interface ITUserService
<TUser>extends IService<TUser> 。 - 實(shí)現(xiàn)類:public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements IService<TUser> 實(shí)現(xiàn)接口由
IService變成ITUserService。
Junit 測(cè)試代碼:
@Autowired
private ITUserService<TUser> tUserService;
@Test
public void testService() {
TUser user = new TUser();
user.setUserName("trek");
user.setPassword("888999");
user.setPwdCipher("ewifwiEFafe==");
if (tUserService.save(user)) {
tUserService.list().forEach(System.out::println);
} else {
System.out.println("添加數(shù)據(jù)失敗");
}
}
測(cè)試結(jié)果:

控制臺(tái)輸出信息:
==> Preparing: INSERT INTO t_user ( user_name, password, pwd_cipher ) VALUES ( ?, ?, ? )
==> Parameters: trek(String), 888999(String), ewifwiEFafe==(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@47acd13b]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e26f1ed] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@633514467 wrapping com.mysql.cj.jdbc.ConnectionImpl@297c9a9b] will not be managed by Spring
==> Preparing: SELECT id,user_name,password,pwd_cipher FROM t_user
==> Parameters:
<== Columns: id, user_name, password, pwd_cipher
<== Row: 1, guo, bwMhZeGXyD98aToKQdXLcw==, null
<== Row: 2, guo, bwMhZeGXyD98aToKQdXLcw==, null
<== Row: 3, guo, 123456, bwMhZeGXyD98aToKQdXLcw==
<== Row: 4, trek, 888999, ewifwiEFafe==
<== Total: 4
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e26f1ed]
TUser(id=1, userName=guo, password=bwMhZeGXyD98aToKQdXLcw==, pwdCipher=null)
TUser(id=2, userName=guo, password=bwMhZeGXyD98aToKQdXLcw==, pwdCipher=null)
TUser(id=3, userName=guo, password=123456, pwdCipher=bwMhZeGXyD98aToKQdXLcw==)
TUser(id=4, userName=trek, password=888999, pwdCipher=ewifwiEFafe==)
四、分頁(yè)查詢
4.1 配置攔截器組件
MybatisPlusApplication啟動(dòng)類添加代碼:
/**
* 分頁(yè)插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
4.2 編寫分頁(yè)代碼
直接 new 一個(gè) Page 對(duì)象,對(duì)象需要傳遞兩個(gè)參數(shù)(當(dāng)前頁(yè),每頁(yè)顯示的條數(shù))。
調(diào)用 mybatis-plus 提供的分頁(yè)查詢方法,其會(huì)將 分頁(yè)查詢的數(shù)據(jù)封裝到 Page 對(duì)象中。
@Test
public void selectPage() {
// 根據(jù)Wrapper 自定義條件查詢
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", "18");
queryWrapper.orderByDesc("age");
Page<User> userPage = new Page<User>(2, 2);
// userPage.setCurrent(2L); //當(dāng)前是第幾頁(yè) 默認(rèn)為1
// userPage.setSize(2); //每頁(yè)大小
IPage<User> userIPage = userMapper.selectPage(userPage, queryWrapper);
System.out.println("當(dāng)前頁(yè)" + userIPage.getCurrent()); //當(dāng)前頁(yè)
System.out.println("總頁(yè)數(shù)" + userIPage.getPages()); //總頁(yè)數(shù)
System.out.println("返回?cái)?shù)據(jù)" + userIPage.getRecords()); //返回?cái)?shù)據(jù)
System.out.println("每頁(yè)大小" + userIPage.getSize()); //每頁(yè)大小
System.out.println("滿足符合條件的條數(shù)" + userIPage.getTotal()); //滿足符合條件的條數(shù)
System.out.println("下一頁(yè)" + userPage.hasNext()); //下一頁(yè)
System.out.println("上一頁(yè)" + userPage.hasPrevious()); //上一頁(yè)
}
運(yùn)行結(jié)果:
控制臺(tái)System.out.println代碼部分日志輸出:
當(dāng)前頁(yè)2
總頁(yè)數(shù)2
返回?cái)?shù)據(jù)[User(id=4, name=Oliver, age=21, email=xds@erbadagang.com), User(id=2, name=xiu, age=20, email=specialized@erbadagang.com)]
每頁(yè)大小2
滿足符合條件的條數(shù)4
下一頁(yè)false
上一頁(yè)true
五、Mybatis-Plus Wrapper
參考上篇文章:MyBatis-Plus 條件構(gòu)造器(Wrapper)
5.1 刪除
/**
* <p>
* 根據(jù)根據(jù) entity 條件,刪除記錄,QueryWrapper實(shí)體對(duì)象封裝操作類(可以為 null)
* 下方獲取到queryWrapper后刪除的查詢條件為name字段為null的and年齡大于等于12的and email字段不為null的
* 同理寫法條件添加的方式就不做過多介紹了。
* </p>
*/
@Test
public void delete() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.isNull("name")
.ge("age", 12)
.isNotNull("email");
int delete = userMapper.delete(queryWrapper);
System.out.println("delete return count = " + delete);
}
SQL輸出:
==> Preparing: DELETE FROM user WHERE (name IS NULL AND age >= ? AND email IS NOT NULL)
==> Parameters: 12(Integer)
<== Updates: 0
5.2 selectOne
/**
* <p>
* 根據(jù) entity 條件,查詢一條記錄,
* 這里和上方刪除構(gòu)造條件一樣,只是seletOne返回的是一條實(shí)體記錄,當(dāng)出現(xiàn)多條時(shí)會(huì)報(bào)錯(cuò)
* </p>
*/
@Test
public void selectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "guo");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
SQL輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE (name = ?)
==> Parameters: guo(String)
<== Columns: id, name, age, email
<== Row: 1, Guo , 18, trek@erbadagang.com
<== Total: 1
5.3 selectCount
/**
* <p>
* 根據(jù) Wrapper 條件,查詢總記錄數(shù)
* </p>
*
* @param queryWrapper 實(shí)體對(duì)象
*/
@Test
public void selectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "guo");
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
SQL輸出:
==> Preparing: SELECT COUNT( 1 ) FROM user WHERE (name = ?)
==> Parameters: guo(String)
<== Columns: COUNT( 1 )
<== Row: 1
<== Total: 1
5.4 selectList
/**
* <p>
* 根據(jù) entity 條件,查詢?nèi)坑涗? * </p>
*
* @param queryWrapper 實(shí)體對(duì)象封裝操作類(可以為 null)為null查詢?nèi)? */
@Test
public void selectListByEntity() {
List<User> list = userMapper.selectList(null);//null為無條件
System.out.println(list);
}
/**
* <p>
* 根據(jù) Wrapper 條件,查詢?nèi)坑涗? * </p>
*
* @param queryWrapper 實(shí)體對(duì)象封裝操作類(可以為 null)
*/
@Test
public void selectListByMapper() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "guo");
List<User> list = userMapper.selectList(queryWrapper);//null為無條件
System.out.println(list);
}
5.5 selectMaps
@Test
public void selectMaps() {
Page<User> page = new Page<User>(1, 5);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(map -> {
System.out.println("name-->" + map.get("name"));
System.out.println("email-->" + map.get("email"));
});
System.out.println(maps);
}
返回類型List<Map<String, Object>>。Map的key為字段名稱,value為對(duì)應(yīng)的字段值。
控制臺(tái)輸出:
==> Preparing: SELECT id,name,age,email FROM user
==> Parameters:
<== Columns: id, name, age, email
<== Row: 1, Guo , 18, trek@erbadagang.com
<== Row: 2, xiu, 20, specialized@erbadagang.com
<== Row: 3, zhi, 28, giant@erbadagang.com
<== Row: 4, Oliver, 88, winspace@erbadagang.com
<== Row: 5, Messi, 24, look@erbadagang.com
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@294aba23]
name-->Guo
email-->trek@erbadagang.com
name-->xiu
email-->specialized@erbadagang.com
name-->zhi
email-->giant@erbadagang.com
name-->Oliver
email-->winspace@erbadagang.com
name-->Messi
email-->look@erbadagang.com
[{name=Guo , id=1, age=18, email=trek@erbadagang.com}, {name=xiu, id=2, age=20, email=specialized@erbadagang.com}, {name=zhi, id=3, age=28, email=giant@erbadagang.com}, {name=Oliver, id=4, age=88, email=winspace@erbadagang.com}, {name=Messi, id=5, age=24, email=look@erbadagang.com}]
六、自動(dòng)填充數(shù)據(jù)功能
添加、修改數(shù)據(jù)時(shí),每次都會(huì)使用相同的方式進(jìn)行填充。比如: 數(shù)據(jù)的創(chuàng)建時(shí)間、修改時(shí)間、操作者等。
6.1 數(shù)據(jù)庫(kù)準(zhǔn)備
Mybatis-plus 支持自動(dòng)填充這些字段的數(shù)據(jù)。給之前的數(shù)據(jù)表新增3個(gè)字段:創(chuàng)建時(shí)間、修改時(shí)間、操作人。
SQL語(yǔ)句:
ALTER TABLE `orders_1`.`user`
ADD COLUMN `create_time` datetime(0) COMMENT '創(chuàng)建時(shí)間' AFTER `email`,
ADD COLUMN `update_time` datetime(0) COMMENT '修改時(shí)間' AFTER `create_time`,
ADD COLUMN `operator` varchar(20) COMMENT '操作人' AFTER `update_time`;
6.2 重新生成代碼
并使用 代碼生成器重新生成代碼,注意修改生成器配置為可覆蓋老代碼。
// 重新生成文件時(shí)是否覆蓋,false 表示不覆蓋(可選)
gc.setFileOverride(true);
6.3 修改entity
使用@TableField注解,標(biāo)注需要進(jìn)行填充的字段。
package com.erbadagang.mybatis.plus.mybatisplus.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
*
* </p>
*
* @author 郭秀志 jbcode@126.com
* @since 2020-07-11
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class User implements Serializable {
private static final long serialVersionUID=1929834928304L;
/**
* 主鍵ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 姓名
*/
private String name;
/**
* 年齡
*/
private Integer age;
/**
* 郵箱
*/
private String email;
/**
* 創(chuàng)建時(shí)間
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 修改時(shí)間
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 操作人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String operator;
}
填充策略
FieldFill.INSERT_UPDATE表示插入和更新都進(jìn)行自動(dòng)填充。
6.4 自定義MetaObjectHandler
自定義一個(gè)類,實(shí)現(xiàn) MetaObjectHandler 接口,并重寫方法。添加 @Component 注解,交給 Spring 去管理。
package com.erbadagang.mybatis.plus.mybatisplus.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @description 自定義的數(shù)據(jù)填充handler,分別寫insert和update的寫入策略。
* @ClassName: MyFillDataMetaObjectHandler
* @author: 郭秀志 jbcode@126.com
* @date: 2020/7/11 9:39
* @Copyright:
*/
@Component
public class MyFillDataMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
this.strictInsertFill(metaObject, "operator", String.class, "梅西愛騎車");
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
this.strictInsertFill(metaObject, "operator", String.class, "梅西愛騎車");
}
}
6.5 測(cè)試
6.5.1 插入測(cè)試
/**
* 測(cè)試插入的自動(dòng)填充數(shù)據(jù)功能。
*/
@Test
public void testAutoFillInsert() {
User user = new User();
user.setId(0l);
user.setName("崔克");
user.setAge(18);
user.setEmail("trek@erbadagang.cn");
int id = userMapper.insert(user);//自動(dòng)返回插入的id
System.out.println(id);
}
運(yùn)行測(cè)試用例,如果報(bào)錯(cuò):Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
需要把id列勾上自增。

輸出的SQL信息:
==> Preparing: INSERT INTO user ( name, age, email, create_time, update_time, operator ) VALUES ( ?, ?, ?, ?, ?, ? )
==> Parameters: 崔克(String), 18(Integer), trek@erbadagang.cn(String), 2020-07-11 09:57:43.386(Timestamp), 2020-07-11 09:57:43.388(Timestamp), 梅西愛騎車(String)
<== Updates: 1

6.5.2 更新測(cè)試
更新name為英文的trek,age為28。
/**
* 測(cè)試更新的自動(dòng)填充數(shù)據(jù)功能。
*/
@Test
public void testAutoFillUpdate() {
User user = new User();
user.setId(7l);
user.setName("trek");
user.setAge(28);
user.setEmail("trek@erbadagang.cn");
int id = userMapper.updateById(user);//自動(dòng)返回插入的id
System.out.println(id);
}
運(yùn)行測(cè)試。
輸出的SQL信息,只更新了update_time沒更新create_time字段:
==> Preparing: UPDATE user SET name=?, age=?, email=?, update_time=?, operator=? WHERE id=?
==> Parameters: trek(String), 28(Integer), trek@erbadagang.cn(String), 2020-07-11 10:05:30.249(Timestamp), 梅西愛騎車(String), 7(Long)
<== Updates: 1
如果入庫(kù)的時(shí)間跟上面打印的SQL不一致,需要在jdbc連接加入時(shí)區(qū)設(shè)置:
jdbc:mysql://101.133.227.13:3306/orders_1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
七、邏輯刪除
刪除數(shù)據(jù),可以通過物理刪除,也可以通過邏輯刪除。
- 物理刪除指的是直接將數(shù)據(jù)從數(shù)據(jù)庫(kù)中刪除,不保留。
- 邏輯刪除指的是修改數(shù)據(jù)的某個(gè)字段,使其表示為已刪除狀態(tài),而非刪除數(shù)據(jù),保留該數(shù)據(jù)在數(shù)據(jù)庫(kù)中,但是查詢時(shí)不顯示該數(shù)據(jù)(查詢時(shí)過濾掉該數(shù)據(jù))。
7.1 表結(jié)構(gòu)
給數(shù)據(jù)表增加一個(gè)字段:delete_flag,用于表示該數(shù)據(jù)是否被邏輯刪除。
SQL語(yǔ)句:
ALTER TABLE `orders_1`.`user`
ADD COLUMN `delete_flag` tinyint(1) COMMENT '邏輯刪除(0 未刪除、1 刪除)' AFTER `operator`;
7.3 使用邏輯刪除。
可以定義一個(gè)自動(dòng)填充規(guī)則,初始值為 0。0 表示未刪除, 1 表示刪除。
在Entity類新增:
/**
* 邏輯刪除(0 未刪除、1 刪除)
*/
@TableLogic(value = "0", delval = "1")//定義邏輯刪除功能。
@TableField(fill = FieldFill.INSERT)//定義在insert的時(shí)候自動(dòng)填充功能
private Integer deleteFlag;
@TableLogic定義邏輯刪除功能,若去除 TableLogic 注解,再執(zhí)行 Delete 時(shí)進(jìn)行物理刪除,直接刪除這條數(shù)據(jù)。
@TableField定義在自動(dòng)填充功能。
在自動(dòng)填充規(guī)則MyFillDataMetaObjectHandler類的insertFill方法添加:
@Override
public void insertFill(MetaObject metaObject) {
......
this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0);
}
7.4 測(cè)試
新增一條閃電牌自行車數(shù)據(jù):
User user = new User();
user.setId(0l);
user.setName("閃電");
user.setAge(18);
user.setEmail("specialized@erbadagang.cn");
int id = userMapper.insert(user);//自動(dòng)返回插入的id
delete_flag字段為自動(dòng)填充代碼定義的默認(rèn)值0,當(dāng)然也可以使用數(shù)據(jù)庫(kù)定義默認(rèn)值。

新增數(shù)據(jù)delete_flag值:

刪除數(shù)據(jù):
//這次使用IUserService而不是mapper進(jìn)行測(cè)試
@Autowired
private IUserService userService;
/**
* 邏輯刪除測(cè)試。
*/
@Test
public void testDelete() {
if (userService.removeById(8)) {
System.out.println("刪除數(shù)據(jù)成功");
userService.list().forEach(System.out::println);
} else {
System.out.println("刪除數(shù)據(jù)失敗");
}
}
執(zhí)行測(cè)試,輸出的日志:
==> Preparing: UPDATE user SET delete_flag=1 WHERE id=? AND delete_flag=0
==> Parameters: 8(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@22bdb1d0]
刪除數(shù)據(jù)成功
==> Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag FROM user WHERE delete_flag=0
==> Parameters:
<== Total: 0
可以看到更新
delete_flag=1的操作,以及查詢時(shí)自動(dòng)加上了WHERE delete_flag=0的判斷。
表數(shù)據(jù)變化:

八、樂觀鎖
8.1 基礎(chǔ)知識(shí)
(1)首先認(rèn)識(shí)一下: 讀問題、寫問題
操作數(shù)據(jù)庫(kù)數(shù)據(jù)時(shí),遇到的最基本問題就是 讀問題與寫問題。
讀問題 指的是從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù)時(shí)遇到的問題,比如:臟讀、幻讀、不可重復(fù)讀。
臟讀、幻讀、不可重復(fù)讀 參考地址
寫問題 指的是數(shù)據(jù)寫入數(shù)據(jù)庫(kù)時(shí)遇到的問題,比如:丟失更新(多個(gè)線程同時(shí)對(duì)某條數(shù)據(jù)更新,無論執(zhí)行順序如何,都會(huì)丟失其他線程更新的數(shù)據(jù))
(2)如何解決寫問題?
樂觀鎖、悲觀鎖就是為了解決 寫問題而存在的。
樂觀鎖:總是假設(shè)最好的情況,每次讀取數(shù)據(jù)時(shí)認(rèn)為數(shù)據(jù)不會(huì)被修改(即不加鎖),當(dāng)進(jìn)行更新操作時(shí),會(huì)判斷這條數(shù)據(jù)是否被修改,未被修改,則進(jìn)行更新操作。若被修改,則數(shù)據(jù)更新失敗,可以對(duì)數(shù)據(jù)進(jìn)行重試(重新嘗試修改數(shù)據(jù))。
悲觀鎖:總是假設(shè)最壞的情況,每次讀取數(shù)據(jù)時(shí)認(rèn)為數(shù)據(jù)會(huì)被修改(即加鎖),當(dāng)進(jìn)行更新操作時(shí),直接更新數(shù)據(jù),結(jié)束操作后釋放鎖(此處才可以被其他線程讀?。?。
(3)樂觀鎖、悲觀鎖使用場(chǎng)景?
樂觀鎖一般用于讀比較多的場(chǎng)合,盡量減少加鎖的開銷。
悲觀鎖一般用于寫比較多的場(chǎng)合,盡量減少 類似 樂觀鎖重試更新引起的性能開銷。
(4)樂觀鎖兩種實(shí)現(xiàn)方式
方式一:通過版本號(hào)機(jī)制實(shí)現(xiàn)。
在數(shù)據(jù)表中增加一個(gè) version 字段。
取數(shù)據(jù)時(shí),獲取該字段,更新時(shí)以該字段為條件進(jìn)行處理(即set version = newVersion where version = oldVersion),若 version 相同,則更新成功(給新 version 賦一個(gè)值,一般加 1)。若 version 不同,則更新失敗,可以重新嘗試更新操作。
方式二:通過 CAS 算法實(shí)現(xiàn)。
CAS 為 Compare And Swap 的縮寫,即比較交換,是一種無鎖算法(即在不加鎖的情況實(shí)現(xiàn)多線程之間的變量同步)。
CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存值(V)、預(yù)期原值(A)和新值(B)。如果內(nèi)存地址里面的值 V 和 A 的值是一樣的,那么就將內(nèi)存里面的值更新成B。若 V 與 A 不一致,則不執(zhí)行任何操作(可以通過自旋操作,不斷嘗試修改數(shù)據(jù)直至成功修改)。即 V == A ? V = B : V = V。
CAS 可能導(dǎo)致 ABA 問題(兩次讀取數(shù)據(jù)時(shí)值相同,但不確定值是否被修改過),比如兩個(gè)線程操作同一個(gè)變量,線程 A、線程B 初始讀取數(shù)據(jù)均為 A,后來 線程B 將數(shù)據(jù)修改為 B,然后又修改為 A,此時(shí)線程 A 再次讀取到的數(shù)據(jù)依舊是 A,雖然值相同但是中間被修改過,這就是 ABA 問題??梢约右粋€(gè)額外的標(biāo)志位 C,用于表示數(shù)據(jù)是否被修改。當(dāng)標(biāo)志位 C 與預(yù)期標(biāo)志位相同、且 V == A 時(shí),則更新值 B。
(5)mybatis-plus 實(shí)現(xiàn)樂觀鎖(通過 version 機(jī)制)
實(shí)現(xiàn)思路:
Step1:取出記錄時(shí),獲取當(dāng)前version
Step2:更新時(shí),帶上這個(gè)version
Step3:執(zhí)行更新時(shí), set version = newVersion where version = oldVersion
Step4:如果version不對(duì),就更新失敗
(6)mybatis-plus 代碼實(shí)現(xiàn)樂觀鎖
8.2 MP實(shí)現(xiàn)樂觀鎖
配置樂觀鎖插件。
啟動(dòng)類MybatisPlusApplication新增如下代碼(類似分頁(yè)插件),將 OptimisticLockerInterceptor通過@Bean交給 Spring 管理。
/**
* 樂觀鎖插件
* @return 樂觀鎖插件的實(shí)例
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
8.3 定義一個(gè)數(shù)據(jù)庫(kù)字段 version
ALTER TABLE `orders_1`.`user`
ADD COLUMN `version` int COMMENT '版本號(hào)(用于樂觀鎖, 默認(rèn)為 1)' AFTER `delete_flag`;
8.4 實(shí)體類
使用@Version注解標(biāo)注對(duì)應(yīng)的實(shí)體類??梢酝ㄟ^@TableField進(jìn)行數(shù)據(jù)自動(dòng)填充。
/**
* 版本號(hào)(用于樂觀鎖, 默認(rèn)為 1)
*/
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
8.5 自動(dòng)填充規(guī)則
在自動(dòng)填充規(guī)則MyFillDataMetaObjectHandler類的insertFill方法添加:
@Override
public void insertFill(MetaObject metaObject) {
......
//樂觀鎖version初始化值為1
this.strictInsertFill(metaObject, "version", Integer.class, 1);
}
8.6 測(cè)試
/**
* 樂觀鎖測(cè)試
*/
@Test
public void testVersion() {
User user = new User();
user.setName("Look");
user.setAge(8);
user.setEmail("look@erbadagang.cn");
userService.save(user);//新增數(shù)據(jù)
userService.list().forEach(System.out::println);//查詢數(shù)據(jù)
user.setName("梅花");
userService.update(user, null);//修改數(shù)據(jù)
userService.list().forEach(System.out::println);//查詢數(shù)據(jù)
}
運(yùn)行結(jié)果(語(yǔ)句增加了我的注釋):
##插入數(shù)據(jù),version=1
==> Preparing: INSERT INTO user ( name, age, email, create_time, update_time, operator, delete_flag, version ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )
==> Parameters: Look(String), 8(Integer), look@erbadagang.cn(String), 2020-07-11 12:03:50.712(Timestamp), 2020-07-11 12:03:50.715(Timestamp), 梅西愛騎車(String), 0(Integer), 1(Integer)
<== Updates: 1
##查詢數(shù)據(jù),讀取的version為1
==> Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag,version FROM user WHERE delete_flag=0
==> Parameters:
<== Columns: id, name, age, email, create_time, update_time, operator, delete_flag, version
<== Row: 9, Look, 8, look@erbadagang.cn, 2020-07-11 12:03:51, 2020-07-11 12:03:51, 梅西愛騎車, 0, 1
<== Total: 1
##更新數(shù)據(jù),條件是version=1如果此時(shí)被其他程序更新了,這里條件不滿足不會(huì)更新數(shù)據(jù)。
##version的值自動(dòng)+1,現(xiàn)在是2。
==> Preparing: UPDATE user SET name=?, age=?, email=?, create_time=?, update_time=?, operator=?, version=? WHERE delete_flag=0 AND (version = ?)
==> Parameters: 梅花(String), 8(Integer), look@erbadagang.cn(String), 2020-07-11 12:03:50.712(Timestamp), 2020-07-11 12:03:50.715(Timestamp), 梅西愛騎車(String), 2(Integer), 1(Integer)
<== Updates: 1
##再次查詢version為2。
==> Preparing: SELECT id,name,age,email,create_time,update_time,operator,delete_flag,version FROM user WHERE delete_flag=0
==> Parameters:
<== Columns: id, name, age, email, create_time, update_time, operator, delete_flag, version
<== Row: 9, 梅花, 8, look@erbadagang.cn, 2020-07-11 12:03:51, 2020-07-11 12:03:51, 梅西愛騎車, 0, 2
<== Total: 1
##查詢出來的最新數(shù)據(jù),delete_flag=0
User(id=9, name=梅花, age=8, email=look@erbadagang.cn, createTime=Sat Jul 11 12:03:51 CST 2020, updateTime=Sat Jul 11 12:03:51 CST 2020, operator=梅西愛騎車, deleteFlag=0, version=2)
底線
本文源代碼使用 Apache License 2.0開源許可協(xié)議,可從Gitee代碼地址通過git clone命令下載到本地或者通過瀏覽器方式查看源代碼。