1,DML編程控制
查詢相關(guān)的操作我們已經(jīng)介紹完了,緊接著我們需要對另外三個(gè),增刪改進(jìn)行內(nèi)容的講解。挨個(gè)來說明下,首先是新增(insert)中的內(nèi)容。
1. id生成策略控制
前面我們在新增的時(shí)候留了一個(gè)問題,就是新增成功后,主鍵ID是一個(gè)很長串的內(nèi)容,我們更想要的是按照數(shù)據(jù)庫表字段進(jìn)行自增長,在解決這個(gè)問題之前,我們先來分析下ID該如何選擇:
- 不同的表應(yīng)用不同的id生成策略
- 日志:自增(1,2,3,4,……)
- 購物訂單:特殊規(guī)則(FQ23948AK3843)
- 外賣單:關(guān)聯(lián)地區(qū)日期等信息(10 04 20200314 34 91)
- 關(guān)系表:可省略id
- ……
不同的業(yè)務(wù)采用的ID生成方式應(yīng)該是不一樣的,那么在MP中都提供了哪些主鍵生成策略,以及我們該如何進(jìn)行選擇?
在這里我們又需要用到MP的一個(gè)注解叫@TableId
知識點(diǎn)1:@TableId
名稱 | @TableId |
---|---|
類型 | 屬性注解 |
位置 | 模型類中用于表示主鍵的屬性定義上方 |
作用 | 設(shè)置當(dāng)前類中主鍵屬性的生成策略 |
相關(guān)屬性 | value(默認(rèn)):設(shè)置數(shù)據(jù)庫表主鍵名稱 type:設(shè)置主鍵屬性的生成策略,值查照IdType的枚舉值 |
1. 環(huán)境構(gòu)建
在構(gòu)建條件查詢之前,我們先來準(zhǔn)備下環(huán)境
-
創(chuàng)建一個(gè)SpringBoot項(xiàng)目
-
pom.xml中添加對應(yīng)的依賴
<?xml version="1.0" encoding="UTF-8"?> <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.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.itheima</groupId> <artifactId>mybatisplus_03_dml</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
-
編寫UserDao接口
@Mapper public interface UserDao extends BaseMapper<User> { }
-
編寫模型類
@Data public class User { private Long id; private String name; @TableField(value="pwd",select=false) private String password; private Integer age; private String tel; @TableField(exist=false) private Integer online; }
-
編寫引導(dǎo)類
@SpringBootApplication public class Mybatisplus03DqlApplication { public static void main(String[] args) { SpringApplication.run(Mybatisplus03DqlApplication.class, args); } }
-
編寫配置文件
# dataSource spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: root # mp日志 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
編寫測試類
@SpringBootTest class Mybatisplus02DqlApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll(){ List<User> userList = userDao.selectList(null); System.out.println(userList); } }
-
測試
@SpringBootTest class Mybatisplus03DqlApplicationTests { @Autowired private UserDao userDao; }
-
最終創(chuàng)建的項(xiàng)目結(jié)構(gòu)為:
2. 代碼演示
AUTO策略
步驟1:設(shè)置生成策略為AUTO
@Data
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟2:刪除測試數(shù)據(jù)并修改自增值
- 刪除測試數(shù)據(jù),讓當(dāng)前id呈現(xiàn)遞增
- 因?yàn)橹吧芍麈IID的值比較長,會把MySQL的自動增長的值變的很大,所以需要將其調(diào)整為目前最新的id值。
步驟3:運(yùn)行新增方法
@Test
void insert() {
User user = new User();
user.setName("熱愛編程的小白白");
user.setPassword("root");
user.setAge(22);
user.setTel("1768543416");
userDao.insert(user);
}
會發(fā)現(xiàn),新增成功,并且主鍵id也是從5開始
經(jīng)過這三步的演示,會發(fā)現(xiàn)AUTO
的作用是使用數(shù)據(jù)庫ID自增,在使用該策略的時(shí)候一定要確保對應(yīng)的數(shù)據(jù)庫表設(shè)置了ID主鍵自增,否則無效。
接下來,我們可以進(jìn)入源碼查看下ID的生成策略有哪些?
打開源碼后,你會發(fā)現(xiàn)并沒有看到中文注釋,這就需要我們點(diǎn)擊右上角的Download Sources
,會自動幫你把這個(gè)類的java文件下載下來,我們就能看到具體的注釋內(nèi)容。因?yàn)檫@個(gè)技術(shù)是國人制作的,所以他代碼中的注釋還是比較容易看懂的。
當(dāng)把源碼下載完后,就可以看到如下內(nèi)容:
從源碼中可以看到,除了AUTO這個(gè)策略以外,還有如下幾種生成策略:
- NONE: 不設(shè)置id生成策略
- INPUT:用戶手工輸入id
- ASSIGN_ID:雪花算法生成id(可兼容數(shù)值型與字符串型)
- ASSIGN_UUID:以UUID生成算法作為id生成策略
- 其他的幾個(gè)策略均已過時(shí),都將被ASSIGN_ID和ASSIGN_UUID代替掉。
拓展:
分布式ID是什么?
- 當(dāng)數(shù)據(jù)量足夠大的時(shí)候,一臺數(shù)據(jù)庫服務(wù)器存儲不下,這個(gè)時(shí)候就需要多臺數(shù)據(jù)庫服務(wù)器進(jìn)行存儲
- 比如訂單表就有可能被存儲在不同的服務(wù)器上
- 如果用數(shù)據(jù)庫表的自增主鍵,因?yàn)樵趦膳_服務(wù)器上所以會出現(xiàn)沖突
- 這個(gè)時(shí)候就需要一個(gè)全局唯一ID,這個(gè)ID就是分布式ID。
INPUT策略
步驟1:設(shè)置生成策略為INPUT
@Data
public class User {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
注意: 這種ID生成策略,需要將表的自增策略刪除掉
步驟2:添加數(shù)據(jù)手動設(shè)置ID
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
//設(shè)置主鍵ID的值
user.setId(666L);
user.setName("加油!");
user.setPassword("沖沖沖");
user.setAge(12);
user.setTel("18754434333");
userDao.insert(user);
}
}
步驟3:運(yùn)行新增方法
如果沒有設(shè)置主鍵ID的值,則會報(bào)錯,錯誤提示就是主鍵ID沒有給值:
如果設(shè)置了主鍵ID,則數(shù)據(jù)添加成功,如下:
ASSIGN_ID策略
步驟1:設(shè)置生成策略為ASSIGN_ID
@Data
public class User {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟2:添加數(shù)據(jù)不設(shè)置ID
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testSave(){
User user = new User();
user.setName("張三");
user.setPassword("123466");
user.setAge(32);
user.setTel("125607778989");
userDao.insert(user);
}
}
注意: 這種生成策略,不需要手動設(shè)置ID,如果手動設(shè)置ID,則會使用自己設(shè)置的值。
步驟3:運(yùn)行新增方法
生成的ID就是一個(gè)Long類型的數(shù)據(jù)。
ASSIGN_UUID策略
步驟1:設(shè)置生成策略為ASSIGN_UUID
使用uuid需要注意的是,主鍵的類型不能是Long,而應(yīng)該改成String類型
@Data
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
}
步驟2:修改表的主鍵類型
主鍵類型設(shè)置為varchar,長度要大于32,因?yàn)閁UID生成的主鍵為32位,如果長度小的話就會導(dǎo)致插入失敗。
步驟3:添加數(shù)據(jù)不設(shè)置ID
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void insert() {
User user = new User();
user.setName("張三");
user.setPassword("123466");
user.setAge(32);
user.setTel("125607778989");
userDao.insert(user);
}
}
步驟4:運(yùn)行新增方法
接下來我們來聊一聊雪花算法:
雪花算法(SnowFlake),是Twitter官方給出的算法實(shí)現(xiàn) 是用Scala寫的。其生成的結(jié)果是一個(gè)64bit大小整數(shù),它的結(jié)構(gòu)如下圖:
- 1bit,不用,因?yàn)槎M(jìn)制中最高位是符號位,1表示負(fù)數(shù),0表示正數(shù)。生成的id一般都是用整數(shù),所以最高位固定為0。
- 41bit-時(shí)間戳,用來記錄時(shí)間戳,毫秒級
- 10bit-工作機(jī)器id,用來記錄工作機(jī)器id,其中高位5bit是數(shù)據(jù)中心ID其取值范圍0-31,低位5bit是工作節(jié)點(diǎn)ID其取值范圍0-31,兩個(gè)組合起來最多可以容納1024個(gè)節(jié)點(diǎn)
- 序列號占用12bit,每個(gè)節(jié)點(diǎn)每毫秒0開始不斷累加,最多可以累加到4095,一共可以產(chǎn)生4096個(gè)ID
3. ID生成策略對比
介紹了這些主鍵ID的生成策略,我們以后該用哪個(gè)呢?
- NONE: 不設(shè)置id生成策略,MP不自動生成,約等于INPUT,所以這兩種方式都需要用戶手動設(shè)置,但是手動設(shè)置第一個(gè)問題是容易出現(xiàn)相同的ID造成主鍵沖突,為了保證主鍵不沖突就需要做很多判定,實(shí)現(xiàn)起來比較復(fù)雜
- AUTO:數(shù)據(jù)庫ID自增,這種策略適合在數(shù)據(jù)庫服務(wù)器只有1臺的情況下使用,不可作為分布式ID使用
- ASSIGN_UUID:可以在分布式的情況下使用,而且能夠保證唯一,但是生成的主鍵是32位的字符串,長度過長占用空間而且還不能排序,查詢性能也慢
- ASSIGN_ID:可以在分布式的情況下使用,生成的是Long類型的數(shù)字,可以排序性能也高,但是生成的策略和服務(wù)器時(shí)間有關(guān),如果修改了系統(tǒng)時(shí)間就有可能導(dǎo)致出現(xiàn)重復(fù)主鍵
- 綜上所述,每一種主鍵策略都有自己的優(yōu)缺點(diǎn),根據(jù)自己項(xiàng)目業(yè)務(wù)的實(shí)際情況來選擇使用才是最明智的選擇。
4. 簡化配置
前面我們已經(jīng)完成了表關(guān)系映射、數(shù)據(jù)庫主鍵策略的設(shè)置,接下來對于這兩個(gè)內(nèi)容的使用,我們再講下他們的簡化配置:
模型類主鍵策略設(shè)置
確實(shí)是稍微有點(diǎn)繁瑣,我們能不能在某一處進(jìn)行配置,就能讓所有的模型類都可以使用該主鍵ID策略呢?
答案是肯定有,我們只需要在配置文件中添加如下內(nèi)容:
mybatis-plus:
global-config:
db-config:
id-type: assign_id
配置完成后,每個(gè)模型類的主鍵ID策略都將成為assign_id.
數(shù)據(jù)庫表與模型類的映射關(guān)系
MP會默認(rèn)將模型類的類名名首字母小寫作為表名使用,假如數(shù)據(jù)庫表的名稱都以tbl_
開頭,那么我們就需要將所有的模型類上添加@TableName
,如:
配置起來還是比較繁瑣,簡化方式為在配置文件中配置如下內(nèi)容:
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
設(shè)置表的前綴內(nèi)容,這樣MP就會拿 tbl_
加上模型類的首字母小寫,就剛好組裝成數(shù)據(jù)庫的表名。
2. 多記錄操作
先來看下問題:
之前添加了很多商品到購物車,過了幾天發(fā)現(xiàn)這些東西又不想要了,該怎么辦呢?
很簡單刪除掉,但是一個(gè)個(gè)刪除的話還是比較慢和費(fèi)事的,所以一般會給用戶一個(gè)批量操作,也就是前面有一個(gè)復(fù)選框,用戶一次可以勾選多個(gè)也可以進(jìn)行全選,然后刪一次就可以將購物車清空,這個(gè)就需要用到批量刪除
的操作了。
具體該如何實(shí)現(xiàn)多條刪除,我們找找對應(yīng)的API方法
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
翻譯方法的字面意思為:刪除(根據(jù)ID 批量刪除),參數(shù)是一個(gè)集合,可以存放多個(gè)id值。
需求:根據(jù)傳入的id集合將數(shù)據(jù)庫表中的數(shù)據(jù)刪除掉。
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
//刪除指定多條數(shù)據(jù)
List<Long> list = new ArrayList<>();
list.add(1402551342481838081L);
list.add(1402553134049501186L);
list.add(1402553619611430913L);
userDao.deleteBatchIds(list);
}
}
執(zhí)行成功后,數(shù)據(jù)庫表中的數(shù)據(jù)就會按照指定的id進(jìn)行刪除。
除了按照id集合進(jìn)行批量刪除,也可以按照id集合進(jìn)行批量查詢,還是先來看下API
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
方法名稱翻譯為:查詢(根據(jù)ID 批量查詢),參數(shù)是一個(gè)集合,可以存放多個(gè)id值。
需求:根據(jù)傳入的ID集合查詢用戶信息
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testGetByIds(){
//查詢指定多條數(shù)據(jù)
List<Long> list = new ArrayList<>();
list.add(1L);
list.add(3L);
list.add(4L);
userDao.selectBatchIds(list);
}
}
查詢結(jié)果就會按照指定傳入的id值進(jìn)行查詢
3. 邏輯刪除
接下來要講解是刪除中比較重要的一個(gè)操作,邏輯刪除,先來分析下問題:
-
這是一個(gè)員工和其所簽的合同表,關(guān)系是一個(gè)員工可以簽多個(gè)合同,是一個(gè)一(員工)對多(合同)的表
-
員工ID為1的張業(yè)績,總共簽了三個(gè)合同,如果此時(shí)他離職了,我們需要將員工表中的數(shù)據(jù)進(jìn)行刪除,會執(zhí)行delete操作
-
如果表在設(shè)計(jì)的時(shí)候有主外鍵關(guān)系,那么同時(shí)也得將合同表中的前三條數(shù)據(jù)也刪除掉
-
后期要統(tǒng)計(jì)所簽合同的總金額,就會發(fā)現(xiàn)對不上,原因是已經(jīng)將員工1簽的合同信息刪除掉了
-
如果只刪除員工不刪除合同表數(shù)據(jù),那么合同的員工編號對應(yīng)的員工信息不存在,那么就會出現(xiàn)垃圾數(shù)據(jù),就會出現(xiàn)無主合同,根本不知道有張業(yè)績這個(gè)人的存在
-
所以經(jīng)過分析,我們不應(yīng)該將表中的數(shù)據(jù)刪除掉,而是需要進(jìn)行保留,但是又得把離職的人和在職的人進(jìn)行區(qū)分,這樣就解決了上述問題,如:
- 區(qū)分的方式,就是在員工表中添加一列數(shù)據(jù)
deleted
,如果為0說明在職員工,如果離職則將其改完1,(0和1所代表的含義是可以自定義的)
所以對于刪除操作業(yè)務(wù)問題來說有:
- 物理刪除:業(yè)務(wù)數(shù)據(jù)從數(shù)據(jù)庫中丟棄,執(zhí)行的是delete操作
- 邏輯刪除:為數(shù)據(jù)設(shè)置是否可用狀態(tài)字段,刪除時(shí)設(shè)置狀態(tài)字段為不可用狀態(tài),數(shù)據(jù)保留在數(shù)據(jù)庫中,執(zhí)行的是update操作
MP中邏輯刪除具體該如何實(shí)現(xiàn)?
步驟1:修改數(shù)據(jù)庫表添加deleted
列
字段名可以任意,內(nèi)容也可以自定義,比如0
代表正常,1
代表刪除,可以在添加列的同時(shí)設(shè)置其默認(rèn)值為0
正常。
步驟2:實(shí)體類添加屬性
(1)添加與數(shù)據(jù)庫表的列對應(yīng)的一個(gè)屬性名,名稱可以任意,如果和數(shù)據(jù)表列名對不上,可以使用@TableField進(jìn)行關(guān)系映射,如果一致,則會自動對應(yīng)。
(2)標(biāo)識新增的字段為邏輯刪除字段,使用@TableLogic
@Data
//@TableName("tbl_user") 可以不寫是因?yàn)榕渲昧巳峙渲?/span>
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
@TableLogic(value="0",delval="1")
//value為正常數(shù)據(jù)的值,delval為刪除數(shù)據(jù)的值
private Integer deleted;
}
步驟3:運(yùn)行刪除方法
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testDelete(){
userDao.deleteById(1L);
}
}
從測試結(jié)果來看,邏輯刪除最后走的是update操作,會將指定的字段修改成刪除狀態(tài)對應(yīng)的值。
思考
邏輯刪除,對查詢有沒有影響呢?
-
執(zhí)行查詢操作
@SpringBootTest class Mybatisplus03DqlApplicationTests { @Autowired private UserDao userDao; @Test void testFind(){ System.out.println(userDao.selectList(null)); } }
運(yùn)行測試,會發(fā)現(xiàn)打印出來的sql語句中會多一個(gè)查詢條件,如:
可想而知,MP的邏輯刪除會將所有的查詢都添加一個(gè)未被刪除的條件,也就是已經(jīng)被刪除的數(shù)據(jù)是不應(yīng)該被查詢出來的。
-
如果還是想把已經(jīng)刪除的數(shù)據(jù)都查詢出來該如何實(shí)現(xiàn)呢?
@Mapper public interface UserDao extends BaseMapper<User> { //查詢所有數(shù)據(jù)包含已經(jīng)被刪除的數(shù)據(jù) @Select("select * from tbl_user") public List<User> selectAll(); }
-
如果每個(gè)表都要有邏輯刪除,那么就需要在每個(gè)模型類的屬性上添加
@TableLogic
注解,如何優(yōu)化?在配置文件中添加全局配置,如下:
mybatis-plus: global-config: db-config: # 邏輯刪除字段名 logic-delete-field: deleted # 邏輯刪除字面值:未刪除為0 logic-not-delete-value: 0 # 邏輯刪除字面值:刪除為1 logic-delete-value: 1
介紹完邏輯刪除,邏輯刪除的本質(zhì)為:
邏輯刪除的本質(zhì)其實(shí)是修改操作。如果加了邏輯刪除字段,查詢數(shù)據(jù)時(shí)也會自動帶上邏輯刪除字段。
執(zhí)行的SQL語句為:
UPDATE tbl_user SET deleted=1 where id = ? AND deleted=0
執(zhí)行數(shù)據(jù)結(jié)果為:
知識點(diǎn)1:@TableLogic
名稱 | @TableLogic |
---|---|
類型 | 屬性注解 |
位置 | 模型類中用于表示刪除字段的屬性定義上方 |
作用 | 標(biāo)識該字段為進(jìn)行邏輯刪除的字段 |
相關(guān)屬性 | value:邏輯未刪除值 delval:邏輯刪除值 |
4. 樂觀鎖
1. 概念
在講解樂觀鎖之前,我們還是先來分析下問題:
業(yè)務(wù)并發(fā)現(xiàn)象帶來的問題:秒殺
- 假如有100個(gè)商品或者票在出售,為了能保證每個(gè)商品或者票只能被一個(gè)人購買,如何保證不會出現(xiàn)超買或者重復(fù)賣
- 對于這一類問題,其實(shí)有很多的解決方案可以使用
- 第一個(gè)最先想到的就是鎖,鎖在一臺服務(wù)器中是可以解決的,但是如果在多臺服務(wù)器下鎖就沒有辦法控制,比如12306有兩臺服務(wù)器在進(jìn)行賣票,在兩臺服務(wù)器上都添加鎖的話,那也有可能會導(dǎo)致在同一時(shí)刻有兩個(gè)線程在進(jìn)行賣票,還是會出現(xiàn)并發(fā)問題
- 我們接下來介紹的這種方式是針對于小型企業(yè)的解決方案,因?yàn)閿?shù)據(jù)庫本身的性能就是個(gè)瓶頸,如果對其并發(fā)量超過2000以上的就需要考慮其他的解決方案了。
簡單來說,樂觀鎖主要解決的問題是當(dāng)要更新一條記錄的時(shí)候,希望這條記錄沒有被別人更新。
2. 實(shí)現(xiàn)思路
樂觀鎖的實(shí)現(xiàn)方式:
- 數(shù)據(jù)庫表中添加version列,比如默認(rèn)值給1
- 第一個(gè)線程要修改數(shù)據(jù)之前,取出記錄時(shí),獲取當(dāng)前數(shù)據(jù)庫中的version=1
- 第二個(gè)線程要修改數(shù)據(jù)之前,取出記錄時(shí),獲取當(dāng)前數(shù)據(jù)庫中的version=1
- 第一個(gè)線程執(zhí)行更新時(shí),set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 第二個(gè)線程執(zhí)行更新時(shí),set version = newVersion where version = oldVersion
- newVersion = version+1 [2]
- oldVersion = version [1]
- 假如這兩個(gè)線程都來更新數(shù)據(jù),第一個(gè)和第二個(gè)線程都可能先執(zhí)行
- 假如第一個(gè)線程先執(zhí)行更新,會把version改為2,
- 第二個(gè)線程再更新的時(shí)候,set version = 2 where version = 1,此時(shí)數(shù)據(jù)庫表的數(shù)據(jù)version已經(jīng)為2,所以第二個(gè)線程會修改失敗
- 假如第二個(gè)線程先執(zhí)行更新,會把version改為2,
- 第一個(gè)線程再更新的時(shí)候,set version = 2 where version = 1,此時(shí)數(shù)據(jù)庫表的數(shù)據(jù)version已經(jīng)為2,所以第一個(gè)線程會修改失敗
- 不管誰先執(zhí)行都會確保只能有一個(gè)線程更新數(shù)據(jù),這就是MP提供的樂觀鎖的實(shí)現(xiàn)原理分析。
上面所說的步驟具體該如何實(shí)現(xiàn)呢?
3. 實(shí)現(xiàn)步驟
分析完步驟后,具體的實(shí)現(xiàn)步驟如下:
步驟1:數(shù)據(jù)庫表添加列
列名可以任意,比如使用version
,給列設(shè)置默認(rèn)值為1
步驟2:在模型類中添加對應(yīng)的屬性
根據(jù)添加的字段列名,在模型類中添加對應(yīng)的屬性值
@Data
//@TableName("tbl_user") 可以不寫是因?yàn)榕渲昧巳峙渲?/span>
public class User {
@TableId(type = IdType.ASSIGN_UUID)
private String id;
private String name;
@TableField(value="pwd",select=false)
private String password;
private Integer age;
private String tel;
@TableField(exist=false)
private Integer online;
private Integer deleted;
@Version
private Integer version;
}
步驟3:添加樂觀鎖的攔截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定義Mp攔截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加樂觀鎖攔截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
步驟4:執(zhí)行更新操作
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666");
userDao.updateById(user);
}
}
你會發(fā)現(xiàn),這次修改并沒有更新version字段,原因是沒有攜帶version數(shù)據(jù)。
添加version數(shù)據(jù)
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId("3");
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);
}
}
你會發(fā)現(xiàn),我們傳遞的是1,MP會將1進(jìn)行加1,然后,更新回到數(shù)據(jù)庫表中。
所以要想實(shí)現(xiàn)樂觀鎖,首先第一步應(yīng)該是拿到表中的version,然后拿version當(dāng)條件在將version加1更新回到數(shù)據(jù)庫表中,所以我們在查詢的時(shí)候,需要對其進(jìn)行查詢
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通過要修改的數(shù)據(jù)id將當(dāng)前數(shù)據(jù)查詢出來
User user = userDao.selectById(3L);
//2.將要修改的屬性逐一設(shè)置進(jìn)去
user.setName("Jock888");
userDao.updateById(user);
}
}
大概分析完樂觀鎖的實(shí)現(xiàn)步驟以后,我們來模擬一種加鎖的情況,看看能不能實(shí)現(xiàn)多個(gè)人修改同一個(gè)數(shù)據(jù)的時(shí)候,只能有一個(gè)人修改成功。
@SpringBootTest
class Mybatisplus03DqlApplicationTests {
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通過要修改的數(shù)據(jù)id將當(dāng)前數(shù)據(jù)查詢出來
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?條件還成立嗎?
}
}
運(yùn)行程序,分析結(jié)果:
樂觀鎖就已經(jīng)實(shí)現(xiàn)完成了,如果對于上面的這些步驟記不住咋辦呢?
參考官方文檔來實(shí)現(xiàn):
https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor
2,快速開發(fā)
1. 代碼生成器原理分析
造句:
我們可以往空白內(nèi)容進(jìn)行填詞造句,比如:
再比如:
觀察我們之前寫的代碼,會發(fā)現(xiàn)其中也會有很多重復(fù)內(nèi)容,比如:
那我們就想,如果我想做一個(gè)Book模塊的開發(fā),是不是只需要將紅色部分的內(nèi)容全部更換成Book
即可,如:
所以我們會發(fā)現(xiàn),做任何模塊的開發(fā),對于這段代碼,基本上都是對紅色部分的調(diào)整,所以我們把去掉紅色內(nèi)容的東西稱之為模板,紅色部分稱之為參數(shù),以后只需要傳入不同的參數(shù),就可以根據(jù)模板創(chuàng)建出不同模塊的dao代碼。
除了Dao可以抽取模塊,其實(shí)我們常見的類都可以進(jìn)行抽取,只要他們有公共部分即可。再來看下模型類的模板:
- ① 可以根據(jù)數(shù)據(jù)庫表的表名來填充
- ② 可以根據(jù)用戶的配置來生成ID生成策略
- ③到⑨可以根據(jù)數(shù)據(jù)庫表字段名稱來填充
所以只要我們知道是對哪張表進(jìn)行代碼生成,這些內(nèi)容我們都可以進(jìn)行填充。
分析完后,我們會發(fā)現(xiàn),要想完成代碼自動生成,我們需要有以下內(nèi)容:
- 模板: MyBatisPlus提供,可以自己提供,但是麻煩,不建議
- 數(shù)據(jù)庫相關(guān)配置:讀取數(shù)據(jù)庫獲取表和字段信息
- 開發(fā)者自定義配置:手工配置,比如ID生成策略
2. 代碼生成器實(shí)現(xiàn)
步驟1:創(chuàng)建一個(gè)Maven項(xiàng)目
代碼2:導(dǎo)入對應(yīng)的jar包
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
</parent>
<groupId>com.itheima</groupId>
<artifactId>mybatisplus_04_generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring webmvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatisplus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--代碼生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!--velocity模板引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
步驟3:編寫引導(dǎo)類
package com.itheima;
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;
public class CodeGenerator {
public static void main(String[] args) {
//1.獲取代碼生成器的對象
AutoGenerator autoGenerator = new AutoGenerator();
//設(shè)置數(shù)據(jù)庫相關(guān)配置
DataSourceConfig dataSource = new DataSourceConfig();
dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
autoGenerator.setDataSource(dataSource);
//設(shè)置全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java"); //設(shè)置代碼生成位置
globalConfig.setOpen(false); //設(shè)置生成完畢后是否打開生成代碼所在的目錄
globalConfig.setAuthor("熱愛編程的小白白"); //設(shè)置作者
globalConfig.setFileOverride(true); //設(shè)置是否覆蓋原始生成的文件
globalConfig.setMapperName("%sDao"); //設(shè)置數(shù)據(jù)層接口名,%s為占位符,指代模塊名稱
globalConfig.setIdType(IdType.AUTO); //設(shè)置Id生成策略
autoGenerator.setGlobalConfig(globalConfig);
//設(shè)置包名相關(guān)配置
PackageConfig packageInfo = new PackageConfig();
packageInfo.setParent("com.aaa"); //設(shè)置生成的包名,與代碼所在位置不沖突,二者疊加組成完整路徑
packageInfo.setEntity("domain"); //設(shè)置實(shí)體類包名
packageInfo.setMapper("dao"); //設(shè)置數(shù)據(jù)層包名
autoGenerator.setPackageInfo(packageInfo);
//策略設(shè)置
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setInclude("user"); //設(shè)置當(dāng)前參與生成的表名,參數(shù)為可變參數(shù)
// strategyConfig.setTablePrefix("tbl_"); //設(shè)置數(shù)據(jù)庫表的前綴名稱,模塊名 = 數(shù)據(jù)庫表名 - 前綴名 例如: User = tbl_user - tbl_
strategyConfig.setRestControllerStyle(true); //設(shè)置是否啟用Rest風(fēng)格
strategyConfig.setVersionFieldName("version"); //設(shè)置樂觀鎖字段名
strategyConfig.setLogicDeleteFieldName("deleted"); //設(shè)置邏輯刪除字段名
strategyConfig.setEntityLombokModel(true); //設(shè)置是否啟用lombok
autoGenerator.setStrategy(strategyConfig);
//2.執(zhí)行生成操作
autoGenerator.execute();
}
}
對于代碼生成器中的代碼內(nèi)容,我們可以直接從官方文檔中獲取代碼進(jìn)行修改,
https://mp.baomidou.com/guide/generator.html
步驟5:運(yùn)行程序
運(yùn)行成功后,會在當(dāng)前項(xiàng)目中生成很多代碼,代碼包含controller
,service
,mapper
和entity
至此代碼生成器就已經(jīng)完成工作,我們能快速根據(jù)數(shù)據(jù)庫表來創(chuàng)建對應(yīng)的類,簡化我們的代碼開發(fā)。
3. MP中Service的CRUD
回顧我們之前業(yè)務(wù)層代碼的編寫,編寫接口和對應(yīng)的實(shí)現(xiàn)類:
public interface UserService{
}
@Service
public class UserServiceImpl implements UserService{
}
接口和實(shí)現(xiàn)類有了以后,需要在接口和實(shí)現(xiàn)類中聲明方法
public interface UserService{
public List<User> findAll();
}
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
public List<User> findAll(){
return userDao.selectList(null);
}
}
MP看到上面的代碼以后就說這些方法也是比較固定和通用的,那我來幫你抽取下,所以MP提供了一個(gè)Service接口和實(shí)現(xiàn)類,分別是:IService
和ServiceImpl
,后者是對前者的一個(gè)具體實(shí)現(xiàn)。
以后我們自己寫的Service就可以進(jìn)行如下修改:
public interface UserService extends IService<User>{
}
@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{
}
修改以后的好處是,MP已經(jīng)幫我們把業(yè)務(wù)層的一些基礎(chǔ)的增刪改查都已經(jīng)實(shí)現(xiàn)了,可以直接進(jìn)行使用。
編寫測試類進(jìn)行測試:
@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {
private IUserService userService;
@Test
void testFindAll() {
List<User> list = userService.list();
System.out.println(list);
}
}
注意: 項(xiàng)目中對于MyBatis的環(huán)境是沒有進(jìn)行配置,如果想要運(yùn)行,需要提取將配置文件中的內(nèi)容進(jìn)行完善后在運(yùn)行。
思考:在MP封裝的Service層都有哪些方法可以用?
查看官方文檔:https://mp.baomidou.com/guide/crud-interface.html
,這些提供的方法大家可以參考官方文檔進(jìn)行學(xué)習(xí)使用,方法的名稱可能有些變化,但是方法對應(yīng)的參數(shù)和返回值基本類似。
筆記來自:黑馬程序員SSM框架教程
3,圖書推薦
Java虛擬機(jī)核心技術(shù)一本通:通過實(shí)戰(zhàn)案例+執(zhí)行效果圖+核心代碼,剖析探索JVM核心底層原理,強(qiáng)化推動JVM優(yōu)化落地,手把手教你吃透Java虛擬機(jī)深層原理!
? 系統(tǒng):全書內(nèi)容層層遞進(jìn),深入淺出,手把手教你吃透JVM虛擬機(jī)核心技術(shù)
? 深入:剖析探索JVM核心底層原理,強(qiáng)化推動JVM優(yōu)化落地
? 實(shí)戰(zhàn):原理與實(shí)踐相結(jié)合,懂理論,能落地,實(shí)戰(zhàn)化案例精準(zhǔn)定位技術(shù)細(xì)節(jié)
? 資源:附贈全書案例源代碼,知其然更知其所以然,快速上手不用愁
本書主要以 Java 虛擬機(jī)的基本特性及運(yùn)行原理為中心,深入淺出地分析 JVM 的組成結(jié)構(gòu)和底層實(shí)現(xiàn),介紹了很多性能調(diào)優(yōu)的方案和工具的使用方法。最后還擴(kuò)展介紹了 JMM 內(nèi)存模型的實(shí)現(xiàn)原理和 Java 編譯器的優(yōu)化機(jī)制,讓讀者不僅可以學(xué)習(xí) JVM 的核心技術(shù)知識,還能夯實(shí) JVM 調(diào)優(yōu)及代碼優(yōu)化的技術(shù)功底。
本書適合已具有一定 Java 編程基礎(chǔ)的開發(fā)人員、項(xiàng)目經(jīng)理、架構(gòu)師及性能調(diào)優(yōu)工程師參考閱讀,同時(shí),本書還可以作為廣大職業(yè)院校、計(jì)算機(jī)培訓(xùn)班相關(guān)專業(yè)的教學(xué)參考用書。文章來源:http://www.zghlxwxcb.cn/news/detail-542590.html
? 《深入淺出Java虛擬機(jī):JVM原理與實(shí)戰(zhàn)》免費(fèi)包郵送4本!
?活動時(shí)間:截止到 2023-07-14 20:00:00
? 抽獎方式:利用程序進(jìn)行抽獎。
?參與方式:關(guān)注博主、點(diǎn)贊、收藏、評論區(qū)評論 “ 人生苦短,我愛java”
?本次活動一共贈書4本,評論區(qū)抽取4位小伙伴免費(fèi)送出!!
? 活動截止時(shí)間: 2023-07-14 20:00:00
?中獎名單?
vⅤ_Leon
等一下就回家℡
南 陽
小劉在C站文章來源地址http://www.zghlxwxcb.cn/news/detail-542590.html
到了這里,關(guān)于【MyBatis-Plus】DML編程控制 代碼生成器(文末贈書)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!