MyBatis-Plus系列推薦閱讀順序:
本文目錄結(jié)構(gòu)
一屈梁、SQL日志開關(guān)
二、常用注解
三殷蛇、代碼生成器
四转锈、分頁查詢
五咙冗、Mybatis-Plus Wrapper
六购裙、自動填充數(shù)據(jù)功能
七淆储、邏輯刪除
八坯台、樂觀鎖
一聂沙、SQL日志開關(guān)
配置文件application.properties
秆麸,增加最后一行,執(zhí)行時會打印出 sql 語句及汉。
spring.application.name=mybatis-plus
# 應(yīng)用服務(wù) WEB 訪問端口
server.port=8080
####數(shù)據(jù)庫連接池###
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)分配兔毒,不需要手動輸入
IdType.NONE 未設(shè)置主鍵
IdType.INPUT 需要自己輸入 主鍵值。
IdType.ASSIGN_ID 系統(tǒng)分配 ID甸箱,用于數(shù)值型數(shù)據(jù)(Long育叁,對應(yīng) mysql 中 BIGINT 類型)。
IdType.ASSIGN_UUID 系統(tǒng)分配 UUID芍殖,用于字符串型數(shù)據(jù)(String豪嗽,對應(yīng) mysql 中 varchar(32) 類型)。
2.3【@TableField】
@TableField 用于定義表的非主鍵字段豌骏。
注:
常用屬性:
value 用于定義非主鍵字段名
exist 用于指明是否為數(shù)據(jù)表的字段龟梦, true 表示是,false 為不是窃躲。
fill 用于指定字段填充策略(FieldFill)计贰。
字段填充策略:(一般用于填充 創(chuàng)建時間、修改時間等字段)
FieldFill.DEFAULT 默認(rèn)不填充
FieldFill.INSERT 插入時填充
FieldFill.UPDATE 更新時填充
FieldFill.INSERT_UPDATE 插入蒂窒、更新時填充躁倒。
2.4【@TableLogic】
@TableLogic 用于定義表的字段進(jìn)行邏輯刪除(非物理刪除)
注:
常用屬性:
value 用于定義未刪除時字段的值
delval 用于定義刪除時字段的值
2.5【@Version】
@Version 用于字段實(shí)現(xiàn)樂觀鎖
三荞怒、代碼生成器
3.1 AutoGenerator 簡介
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity秧秉、Mapper褐桌、Mapper XML、Service象迎、Controller 等各個模塊的代碼荧嵌,極大的提升了開發(fā)效率±剩 與 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)建一個 代碼生成器。用于生成代碼拇舀。
此處不用修改逻族。
// 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);
// 重新生成文件時是否覆蓋拉一,false 表示不覆蓋(可選)
gc.setFileOverride(false);
// 配置主鍵生成策略采盒,此處為 ASSIGN_ID(可選)
gc.setIdType(IdType.ASSIGN_ID);
// 配置日期類型,此處為 ONLY_DATE(可選)
gc.setDateType(DateType.ONLY_DATE);
// 默認(rèn)生成的 service 會有 I 前綴
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
Step3:
配置數(shù)據(jù)源信息蔚润。用于指定 需要生成代碼的 數(shù)據(jù)倉庫磅氨、數(shù)據(jù)表。
setUrl嫡纠、setDriverName烦租、setUsername、setPassword
均需修改除盏。
// Step3:數(shù)據(jù)源配置(需要修改)
DataSourceConfig dsc = new DataSourceConfig();
// 配置數(shù)據(jù)庫 url 地址
dsc.setUrl("jdbc:mysql://localhost:3306/testMyBatisPlus?useUnicode=true&characterEncoding=utf8");
// dsc.setSchemaName("testMyBatisPlus"); // 可以直接在 url 中指定數(shù)據(jù)庫名
// 配置數(shù)據(jù)庫驅(qū)動
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// 配置數(shù)據(jù)庫連接用戶名
dsc.setUsername("root");
// 配置數(shù)據(jù)庫連接密碼
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ù)庫表配置)
StrategyConfig strategy = new StrategyConfig();
// 指定表名(可以同時操作多個表锌杀,使用 , 隔開)(需要修改)
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í)體時去除表前綴
// 此處的表名為 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 等各個模塊的代碼绑嘹,極大的提升了開發(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);
// 重新生成文件時是否覆蓋畅卓,false 表示不覆蓋(可選)
gc.setFileOverride(false);
// 配置主鍵生成策略擅腰,此處為 ASSIGN_ID(可選)
gc.setIdType(IdType.AUTO);
// 配置日期類型,此處為 ONLY_DATE(可選)
gc.setDateType(DateType.ONLY_DATE);
// 默認(rèn)生成的 service 會有 I 前綴
gc.setServiceName("I%sService");
mpg.setGlobalConfig(gc);
// Step3:數(shù)據(jù)源配置(需要修改)
DataSourceConfig dsc = new DataSourceConfig();
// 配置數(shù)據(jù)庫 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ù)庫名
// 配置數(shù)據(jù)庫驅(qū)動
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
// 配置數(shù)據(jù)庫連接用戶名
dsc.setUsername("guo");
// 配置數(shù)據(jù)庫連接密碼
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ù)庫表配置)
StrategyConfig strategy = new StrategyConfig();
// 指定表名(可以同時操作多個表翁潘,使用 , 隔開)(需要修改)
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í)體時去除表前綴
// 此處的表名為 test_mybatis_plus_user,模塊名為 test_mybatis_plus拜马,去除前綴后剩下為 user渗勘。
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
// Step6:執(zhí)行代碼生成操作
mpg.execute();
}
}
3.4 測試生成的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 測試代碼:
@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ù)失敗");
}
}
測試結(jié)果:
控制臺輸出信息:
==> 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==)
四、分頁查詢
4.1 配置攔截器組件
MybatisPlusApplication啟動類添加代碼:
/**
* 分頁插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
4.2 編寫分頁代碼
直接 new 一個 Page 對象豹绪,對象需要傳遞兩個參數(shù)(當(dāng)前頁价淌,每頁顯示的條數(shù))。
調(diào)用 mybatis-plus 提供的分頁查詢方法瞒津,其會將 分頁查詢的數(shù)據(jù)封裝到 Page 對象中蝉衣。
@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)前是第幾頁 默認(rèn)為1
// userPage.setSize(2); //每頁大小
IPage<User> userIPage = userMapper.selectPage(userPage, queryWrapper);
System.out.println("當(dāng)前頁" + userIPage.getCurrent()); //當(dāng)前頁
System.out.println("總頁數(shù)" + userIPage.getPages()); //總頁數(shù)
System.out.println("返回數(shù)據(jù)" + userIPage.getRecords()); //返回數(shù)據(jù)
System.out.println("每頁大小" + userIPage.getSize()); //每頁大小
System.out.println("滿足符合條件的條數(shù)" + userIPage.getTotal()); //滿足符合條件的條數(shù)
System.out.println("下一頁" + userPage.hasNext()); //下一頁
System.out.println("上一頁" + userPage.hasPrevious()); //上一頁
}
運(yùn)行結(jié)果:控制臺System.out.println
代碼部分日志輸出:
當(dāng)前頁2
總頁數(shù)2
返回數(shù)據(jù)[User(id=4, name=Oliver, age=21, email=xds@erbadagang.com), User(id=2, name=xiu, age=20, email=specialized@erbadagang.com)]
每頁大小2
滿足符合條件的條數(shù)4
下一頁false
上一頁true
五、Mybatis-Plus Wrapper
參考上篇文章:MyBatis-Plus 條件構(gòu)造器(Wrapper)
5.1 刪除
/**
* <p>
* 根據(jù)根據(jù) entity 條件巷蚪,刪除記錄,QueryWrapper實(shí)體對象封裝操作類(可以為 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)多條時會報錯
* </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í)體對象
*/
@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í)體對象封裝操作類(可以為 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í)體對象封裝操作類(可以為 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為對應(yīng)的字段值八拱。
控制臺輸出:
==> 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}]
六阵赠、自動填充數(shù)據(jù)功能
添加、修改數(shù)據(jù)時肌稻,每次都會使用相同的方式進(jìn)行填充清蚀。比如: 數(shù)據(jù)的創(chuàng)建時間、修改時間爹谭、操作者等枷邪。
6.1 數(shù)據(jù)庫準(zhǔn)備
Mybatis-plus 支持自動填充這些字段的數(shù)據(jù)。給之前的數(shù)據(jù)表新增3個字段:創(chuàng)建時間诺凡、修改時間东揣、操作人。
SQL語句:
ALTER TABLE `orders_1`.`user`
ADD COLUMN `create_time` datetime(0) COMMENT '創(chuàng)建時間' AFTER `email`,
ADD COLUMN `update_time` datetime(0) COMMENT '修改時間' AFTER `create_time`,
ADD COLUMN `operator` varchar(20) COMMENT '操作人' AFTER `update_time`;
6.2 重新生成代碼
并使用 代碼生成器重新生成代碼绑洛,注意修改生成器配置為可覆蓋老代碼救斑。
// 重新生成文件時是否覆蓋,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)建時間
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 修改時間
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 操作人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String operator;
}
填充策略
FieldFill.INSERT_UPDATE
表示插入和更新都進(jìn)行自動填充。
6.4 自定義MetaObjectHandler
自定義一個類绑蔫,實(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 測試
6.5.1 插入測試
/**
* 測試插入的自動填充數(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);//自動返回插入的id
System.out.println(id);
}
運(yùn)行測試用例,如果報錯: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 更新測試
更新name為英文的trek左敌,age為28。
/**
* 測試更新的自動填充數(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);//自動返回插入的id
System.out.println(id);
}
運(yùn)行測試矫限。
輸出的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
如果入庫的時間跟上面打印的SQL不一致,需要在jdbc連接加入時區(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ù)庫中刪除茵汰,不保留。
- 邏輯刪除指的是修改數(shù)據(jù)的某個字段懈贺,使其表示為已刪除狀態(tài)经窖,而非刪除數(shù)據(jù),保留該數(shù)據(jù)在數(shù)據(jù)庫中梭灿,但是查詢時不顯示該數(shù)據(jù)(查詢時過濾掉該數(shù)據(jù))。
7.1 表結(jié)構(gòu)
給數(shù)據(jù)表增加一個字段:delete_flag冰悠,用于表示該數(shù)據(jù)是否被邏輯刪除堡妒。
SQL語句:
ALTER TABLE `orders_1`.`user`
ADD COLUMN `delete_flag` tinyint(1) COMMENT '邏輯刪除(0 未刪除、1 刪除)' AFTER `operator`;
7.3 使用邏輯刪除溉卓。
可以定義一個自動填充規(guī)則皮迟,初始值為 0。0 表示未刪除桑寨, 1 表示刪除伏尼。
在Entity類新增:
/**
* 邏輯刪除(0 未刪除、1 刪除)
*/
@TableLogic(value = "0", delval = "1")//定義邏輯刪除功能尉尾。
@TableField(fill = FieldFill.INSERT)//定義在insert的時候自動填充功能
private Integer deleteFlag;
@TableLogic定義邏輯刪除功能爆阶,若去除 TableLogic 注解,再執(zhí)行 Delete 時進(jìn)行物理刪除沙咏,直接刪除這條數(shù)據(jù)辨图。
@TableField定義在自動填充功能。
在自動填充規(guī)則MyFillDataMetaObjectHandler
類的insertFill
方法添加:
@Override
public void insertFill(MetaObject metaObject) {
......
this.strictInsertFill(metaObject, "deleteFlag", Integer.class, 0);
}
7.4 測試
新增一條閃電牌自行車數(shù)據(jù):
User user = new User();
user.setId(0l);
user.setName("閃電");
user.setAge(18);
user.setEmail("specialized@erbadagang.cn");
int id = userMapper.insert(user);//自動返回插入的id
delete_flag字段為自動填充代碼定義的默認(rèn)值0肢藐,當(dāng)然也可以使用數(shù)據(jù)庫定義默認(rèn)值故河。
新增數(shù)據(jù)delete_flag值:
刪除數(shù)據(jù):
//這次使用IUserService而不是mapper進(jìn)行測試
@Autowired
private IUserService userService;
/**
* 邏輯刪除測試。
*/
@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í)行測試吆豹,輸出的日志:
==> 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
的操作鱼的,以及查詢時自動加上了WHERE delete_flag=0
的判斷。
表數(shù)據(jù)變化:
八痘煤、樂觀鎖
8.1 基礎(chǔ)知識
(1)首先認(rèn)識一下: 讀問題凑阶、寫問題
操作數(shù)據(jù)庫數(shù)據(jù)時,遇到的最基本問題就是 讀問題與寫問題速勇。
讀問題 指的是從數(shù)據(jù)庫中讀取數(shù)據(jù)時遇到的問題晌砾,比如:臟讀、幻讀烦磁、不可重復(fù)讀养匈。
臟讀哼勇、幻讀、不可重復(fù)讀 參考地址
寫問題 指的是數(shù)據(jù)寫入數(shù)據(jù)庫時遇到的問題呕乎,比如:丟失更新(多個線程同時對某條數(shù)據(jù)更新积担,無論執(zhí)行順序如何,都會丟失其他線程更新的數(shù)據(jù))
(2)如何解決寫問題猬仁?
樂觀鎖帝璧、悲觀鎖就是為了解決 寫問題而存在的。
樂觀鎖:總是假設(shè)最好的情況湿刽,每次讀取數(shù)據(jù)時認(rèn)為數(shù)據(jù)不會被修改(即不加鎖)的烁,當(dāng)進(jìn)行更新操作時,會判斷這條數(shù)據(jù)是否被修改诈闺,未被修改渴庆,則進(jìn)行更新操作。若被修改雅镊,則數(shù)據(jù)更新失敗襟雷,可以對數(shù)據(jù)進(jìn)行重試(重新嘗試修改數(shù)據(jù))。
悲觀鎖:總是假設(shè)最壞的情況仁烹,每次讀取數(shù)據(jù)時認(rèn)為數(shù)據(jù)會被修改(即加鎖)耸弄,當(dāng)進(jìn)行更新操作時,直接更新數(shù)據(jù)卓缰,結(jié)束操作后釋放鎖(此處才可以被其他線程讀燃瞥省)。
(3)樂觀鎖僚饭、悲觀鎖使用場景震叮?
樂觀鎖一般用于讀比較多的場合,盡量減少加鎖的開銷鳍鸵。
悲觀鎖一般用于寫比較多的場合苇瓣,盡量減少 類似 樂觀鎖重試更新引起的性能開銷。
(4)樂觀鎖兩種實(shí)現(xiàn)方式
方式一:通過版本號機(jī)制實(shí)現(xiàn)偿乖。
在數(shù)據(jù)表中增加一個 version 字段击罪。
取數(shù)據(jù)時,獲取該字段贪薪,更新時以該字段為條件進(jìn)行處理(即set version = newVersion where version = oldVersion)媳禁,若 version 相同,則更新成功(給新 version 賦一個值画切,一般加 1)竣稽。若 version 不同,則更新失敗,可以重新嘗試更新操作毫别。
方式二:通過 CAS 算法實(shí)現(xiàn)娃弓。
CAS 為 Compare And Swap 的縮寫,即比較交換岛宦,是一種無鎖算法(即在不加鎖的情況實(shí)現(xiàn)多線程之間的變量同步)台丛。
CAS 操作包含三個操作數(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ù)時值相同,但不確定值是否被修改過)闷煤,比如兩個線程操作同一個變量,線程 A涮瞻、線程B 初始讀取數(shù)據(jù)均為 A鲤拿,后來 線程B 將數(shù)據(jù)修改為 B,然后又修改為 A署咽,此時線程 A 再次讀取到的數(shù)據(jù)依舊是 A近顷,雖然值相同但是中間被修改過,這就是 ABA 問題宁否≈仙可以加一個額外的標(biāo)志位 C,用于表示數(shù)據(jù)是否被修改慕匠。當(dāng)標(biāo)志位 C 與預(yù)期標(biāo)志位相同饱须、且 V == A 時,則更新值 B台谊。
(5)mybatis-plus 實(shí)現(xiàn)樂觀鎖(通過 version 機(jī)制)
實(shí)現(xiàn)思路:
Step1:取出記錄時蓉媳,獲取當(dāng)前version
Step2:更新時,帶上這個version
Step3:執(zhí)行更新時锅铅, set version = newVersion where version = oldVersion
Step4:如果version不對酪呻,就更新失敗
(6)mybatis-plus 代碼實(shí)現(xiàn)樂觀鎖
8.2 MP實(shí)現(xiàn)樂觀鎖
配置樂觀鎖插件。
啟動類MybatisPlusApplication
新增如下代碼(類似分頁插件)盐须,將 OptimisticLockerInterceptor
通過@Bean
交給 Spring 管理玩荠。
/**
* 樂觀鎖插件
* @return 樂觀鎖插件的實(shí)例
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
8.3 定義一個數(shù)據(jù)庫字段 version
ALTER TABLE `orders_1`.`user`
ADD COLUMN `version` int COMMENT '版本號(用于樂觀鎖, 默認(rèn)為 1)' AFTER `delete_flag`;
8.4 實(shí)體類
使用@Version
注解標(biāo)注對應(yīng)的實(shí)體類〗赘裕可以通過@TableField
進(jìn)行數(shù)據(jù)自動填充闷尿。
/**
* 版本號(用于樂觀鎖, 默認(rèn)為 1)
*/
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;
8.5 自動填充規(guī)則
在自動填充規(guī)則MyFillDataMetaObjectHandler
類的insertFill
方法添加:
@Override
public void insertFill(MetaObject metaObject) {
......
//樂觀鎖version初始化值為1
this.strictInsertFill(metaObject, "version", Integer.class, 1);
}
8.6 測試
/**
* 樂觀鎖測試
*/
@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é)果(語句增加了我的注釋):
##插入數(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ù)據(jù)灌旧。
##version的值自動+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
命令下載到本地或者通過瀏覽器方式查看源代碼衡蚂。