MybatiPlus學習
- 一、MybatiPlus簡介
- 1.1 入門案例
- 1.2 mybatisPlus概述
- 1.3 總結
- 二、標準數據層開發
- 2.1 標準的CRUD使用
- 2.2 新增
- 2.3 刪除
- 2.4 修改
- 2.5 根據Id查詢
- 2.6 查詢全部
- 2.7 Lombok
- 2.8 分頁功能
- 三、DQL控制
- 3.1 條件查詢方式
- 3.1.1 構建條件查詢
- 3.1.2 多條件查詢
- 3.1.3 null值判定
- 3.2 查詢投影
- 3.2.1 查詢指定字段
- 3.2.2 聚合查詢
- 3.2.3 分組查詢
- 3.3 查詢條件設定
- 3.3.1 等值查詢
- 3.3.2 范圍查詢
- 3.3.3 模糊查詢
- 3.3.4 排序查詢
- 3.4 字段映射與表名映射
- 四、DQL控制
- 4.1 id生成策略控制
- 4.1.1 AUTO
- 4.1.2 INPUT
- 4.1.3 ASSIGN_ID
- 4.1.4 ASSIGN_UUID
- 4.1.5 ID生成策略對比
- 4.1.6 簡化配置
- 4.2 多記錄操作
- 4.3 邏輯刪除
- 4.4 樂觀鎖
- 4.4.1 實現思路
- 4.4.2 實現步驟
- 五、快速開發
- 5.1 代碼生成器實現
- 5.2 MP中Service的CRUD
- 六、復習
一、MybatiPlus簡介
1.1 入門案例
- MyBatisPlus(簡稱MP)是基于MyBatis框架基礎上開發的增強型工具,旨在簡化開發,提高效率
- 開發方式
- 基于MyBatis使用MyBatisPlus
- 基于Spring使用MyBatisPlus
- 基于SpringBoot使用MyBatisPlus(重點)
現在直接使用SpringBoot來構建項目,官網的快速開始也是直接用的SpringBoot
-
步驟一:創建數據庫和表
CREATE TABLE user ( id bigint(20) primary key auto_increment, name varchar(32) not null, password varchar(32) not null, age int(3) not null , tel varchar(32) not null ); insert into user values(1,'Tom','tom',3,'18866668888'); insert into user values(2,'Jerry','jerry',4,'16688886666'); insert into user values(3,'Jock','123456',41,'18812345678'); insert into user values(4,'略略略','nigger',15,'4006184000');
-
步驟二:創建SpringBoot工程
只需要勾選MySQL,不用勾選MyBatis了 -
步驟三:補全依賴
導入德魯伊和MyBatisPlus的坐標<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>
-
步驟四:編寫數據庫連接四要素
還是將application的后綴名改為yml,以后配置都是用yml來配置
注意要設置一下時區,不然可能會報錯(指高版本的mysql)spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTCusername: rootpassword: YOUSONOFABTICH.## mybatis的日志信息l mybatis-plus: configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
步驟五:根據數據表來創建對應的模型類
注意id是Long類型,至于為什么是Long,接著往下看public class User { private Long id; private String name; private String password; private Integer age; private String tel;@Override public String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", password='" + password + '\'' +", age=" + age +", tel='" + tel + '\'' +'}'; }public Long getId() {return id; }public void setId(Long id) {this.id = id; }public String getName() {return name; }public void setName(String name) {this.name = name; }public String getPassword() {return password; }public void setPassword(String password) {this.password = password; }public Integer getAge() {return age; }public void setAge(Integer age) {this.age = age; }public String getTel() {return tel; }public void setTel(String tel) {this.tel = tel;} }
-
步驟六:創建dao接口
@Mapper public interface UserDao extends BaseMapper<User>{ }
這樣寫就完事兒了,
只需要在類上方加一個@Mapper注解
,同時繼承BaseMapper<>
,泛型寫創建的模型類的類型
然后這樣就能完成單表的CRUD了
-
步驟七:測試
懶死,以后連簡單的CRUD都不用寫了
SpringBoot的測試類也是簡單的一批,只需要一個@SpringBootTest
注解就能完成(創建SpringBoot工程的時候已經幫我們自動弄好了)
測試類里需要什么東西就用@Autowired
自動裝配,測試方法上用@Test注解
@SpringBootTest class MybatisplusApplicationTests {@Autowired private UserDao userDao;@Test void contextLoads() {List<User> users = userDao.selectList(null);for (User b : users) {System.out.println(b);}} }
selectList() 方法的參數為 MP 內置的條件封裝器 Wrapper,所以不填寫就是無任何條件
1.2 mybatisPlus概述
MyBatisPlus的官網
因為域名被搶注了,但是粉絲也捐贈了一個 https://mybatis.plus
域名
MP旨在成為MyBatis的最好搭檔,而不是替換掉MyBatis,從名稱上來看也是這個意思,一個MyBatis的plus版本,在原有的MyBatis上做增強,其底層仍然是MyBatis的東西,所以我們當然也可以在MP中寫MyBatis的內容
對于MP的深入學習,可以多看看官方文檔,鍛煉自己自學的能力,畢竟不是所有知識都有像這樣的網課,更多的還是自己看文檔,挖源碼。
MP的特性:
- 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
- 損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
- 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
- 支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
- 支持主鍵自動生成:支持多達 4 種主鍵策略(內含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
- 支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
- 內置分頁插件:基于 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之后,寫分頁等同于普通 List 查詢
- 分頁插件支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數據庫
- 內置性能分析插件:可輸出 SQL 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
- 內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
1.3 總結
SpringBoot集成MyBatisPlus非常的簡單,只需要導入MyBatisPlus的坐標,然后令dao類繼承BaseMapper
,寫上泛型,類上方加@Mapper
注解
可能存在的疑問:
- 我甚至都沒寫在哪個表里查,為什么能自動識別是在我剛剛創建的表里查?
- 注意我們創建的表,和對應的模型類,是同一個名,默認情況是在同名的表中查找
- 那我要是表明和模型類的名不一樣,那咋整?
- 在模型類的上方加上
@TableName注解
- 例如數據表叫
tb_use
r但數據類叫User
,那么就在User類上加@TableName("tb_user")注解
- 例如數據表叫
- 在模型類的上方加上
二、標準數據層開發
2.1 標準的CRUD使用
功能 | 自定義接口 | MP接口 |
---|---|---|
新增 | boolean save(T t) | int insert(T t) |
刪除 | boolean delete(int id) | int deleteById(Serializable id) |
修改 | boolean update(T t) | int updateById(T t) |
根據id查詢 | T getById(int id) | T selectById(Serializable id) |
查詢全部 | List getAll() | List selectList() |
分頁查詢 | PageInfo getAll(int page,int size) | IPage selectPage(IPage page) |
按條件查詢 | List getAll(Condition condition) | IPage selectPage(WrapperqueryWrapper) |
2.2 新增
int insert(T t)
參數類型是泛型,也就是我們當初繼承BaseMapper
的時候,填的泛型,返回值是int類型,0代表添加失敗,1代表添加成功
@Test
void testInsert(){User user = new User();user.setName("magua");user.setAge(23);user.setTel("4005129421");user.setPassword("MUSICIAN");userDao.insert(user);
}
隨便寫一個User的數據,運行程序,然后去數據庫看看新增是否成功
1572364408896622593 magua MUSICIAN 23 4005129421
這個主鍵自增id看著有點奇怪,但現在你知道為什么要將id設為long類型了吧
2.3 刪除
int deleteByIds(
//Serializable id
)
-
參數類型為什么是一個
序列化類Serializable
- 通過查看String的源碼,你會發現String實現了Serializable接口,而且Number類也實現了Serializable接口
- Number類又是Float,Double,Long等類的父類
- 那現在能作為主鍵的數據類型,都已經是Serializable類型的子類了
- MP使用Serializable類型當做參數類型,就好比我們用Object類型來接收所有類型一樣
-
返回值類型是int
- 數據刪除成功返回1
- 未刪除數據返回0。
-
那下面我們就來刪除剛剛添加的數據,注意末尾加個L
@Test
void testDelete(){userDao.deleteById(1572364408896622593L);
}
2.4 修改
int updateById(T t);
- T:泛型,需要修改的數據內容,注意因為是根據ID進行修改,所以傳入的對象中需要有ID屬性值
- int:返回值
- 修改成功后返回1
- 未修改數據返回0
@Test
void testUpdate(){User user = new User();user.setId(1L);user.setName("Alen");userDao.updateById(user);
}
修改功能只修改指定的字段,未指定的字段保持原樣,與比較之前方便許多,之前修改的話,要加很多判斷語句是否為空。
2.5 根據Id查詢
T selectById (Serializable id)
T selectById (Serializable id)
@Test
void testSelectById(){User user = userDao.selectById(1);System.out.println(user);
}
2.6 查詢全部
List<T> selectList(Wrapper<T> queryWrapper)
Wrapper:用來構建條件查詢的條件,目前我們沒有可直接傳為Null
@Test
void testSelectAll() {List<User> users = userDao.selectList(null);for (User u : users) {System.out.println(u);}
}
方法都測試完了,那你們有沒有想過,這些方法都是誰提供的呢?
- 想都不用想,肯定是我們當初繼承的BaseMapper
2.7 Lombok
-
代碼寫到這,我們發現之前的dao接口,都不用我們自己寫了,只需要
繼承BaseMapper
,用他提供的方法就好了 -
但是現在我還想偷點懶,畢竟懶是第一生產力,之前我們手寫模型類的時候,創建好對應的屬性,然后用IDEA的
Alt+Insert
快捷鍵,快速生成get和set方法,toSring,各種構造器(有需要的話)等 -
項目做這么久,寫模型類都給我寫煩了,有沒有更簡單的方式呢?
- 答案當然是有的,可以使用Lombok,一個Java類庫,提供了一組注解,來簡化我們的POJO模型類開發
具體步驟如下
-
步驟一:添加Lombok依賴
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><!--<version>1.18.12</version>--> </dependency>
版本不用寫,SpringBoot中已經管理了lombok的版本,
-
步驟二:在模型類上添加注解
Lombok常見的注解有:@Setter
:為模型類的屬性提供setter方法@Getter
:為模型類的屬性提供getter方法@ToString
:為模型類的屬性提供toString方法@EqualsAndHashCode
:為模型類的屬性提供equals和hashcode方法@Setter @Getter @ToString @EqualsAndHashCode public class User { private Long id; private String name; private String password; private Integer age; private String tel; }
@Data
:是個組合注解,包含上面的注解的功能@NoArgsConstructor
:提供一個無參構造函數@AllArgsConstructor
:提供一個包含所有參數的構造函數
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private String password; private Integer age; private String tel; }
說明:Lombok只是簡化模型類的編寫,我們之前的方法也能用
例如你有特殊的構造器需求,只想要name和password這兩個參數,那么可以手寫一個
public User(String name, String password) {this.name = name;this.password = password;
}
2.8 分頁功能
基礎的增刪改查功能就完成了,現在我們來進行分頁功能的學習
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper)
- IPage用來構建分頁查詢條件
- Wrapper:用來構建條件查詢的條件,暫時沒有條件可以傳一個null
- 返回值IPage是什么意思,后面我們會說明
具體的使用步驟如下
-
步驟一:調用方法傳入參數獲取返回值
@Test void testSelectPage() { IPage<User> page = new Page<>(1, 3); userDao.selectPage(page, null); System.out.println("當前頁碼" + page.getCurrent()); System.out.println("本頁條數" + page.getSize()); System.out.println("總頁數" + page.getPages()); System.out.println("總條數" + page.getTotal()); System.out.println(page.getRecords()); }
-
步驟二:設置分頁攔截器
public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //1.設置一個MP的攔截器MybatisPlusInterceptor myInterceptor = new MybatisPlusInterceptor();//2.添加具體的攔截器(小的)myInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return myInterceptor;} }
-
步驟三:運行測試程序
開啟日志可以查看其運行的整個sql語句
三、DQL控制
增刪改查四個操作中,查詢是非常重要的也是非常復雜的操作,這部分我們主要學習的內容有:
3.1 條件查詢方式
MP將復雜的SQL查詢語句都做了封裝,使用編程的方式來完成查詢條件的組合
之前我們在寫CRUD時,都看到了一個Wrapper類,我們當初都是賦一個null值,但其實這個類就是用來查詢的
3.1.1 構建條件查詢
-
QueryWrapper
小于用lt
,大于用gt
回想之前我們在html頁面中,如果需要用到小于號或者大于號,需要用對應的html實體來替換
小于
號的實體是<
,大于
號的實體是>
@Test void testQueryWrapper(){ QueryWrapper<User> qw = new QueryWrapper<>(); //條件為 age字段小于18 qw.lt("age",18); List<User> userList = userDao.selectList(qw); System.out.println(userList); }
這種方法有個弊端,那就是字段名是字符串類型,沒有提示信息和自動補全,如果寫錯了,那就查不出來
-
QueryWrapper
的基礎上,使用lambda
@Test void testQueryWrapper(){ QueryWrapper<User> qw = new QueryWrapper<>(); qw.lambda().lt(User::getAge,18); List<User> userList = userDao.selectList(qw); System.out.println(userList); }
User::getAget
,為lambda表達式中的,類名::方法名
-
LambdaQueryWrapper
方式二解決了方式一的弊端,但是要多些一個lambda(),那方式三就來解決方式二的弊端,使用LambdaQueryWrapper,就可以不寫lambda()@Test void testQueryWrapper(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.lt(User::getAge,18);List<User> userList = userDao.selectList(lqw);System.out.println(userList); }
3.1.2 多條件查詢
上面三種都是單條件的查詢,那我們現在想進行多條件的查詢,該如何編寫代碼呢?
需求:查詢表中年齡在10~30歲的用戶信息
@Test
void testQueryWrapper(){LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();//大于10lqw.gt(User::getAge,10);//小于30lqw.lt(User::getAge,30);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
}
構建多條件的時候,我們還可以使用鏈式編程
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.gt(User::getAge, 10).lt(User::getAge, 30);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
}
- 可能存在的疑問
- MP怎么就知道你這倆條件是AND的關系呢,那我要是想用OR的關系,該咋整
- 解答
- 默認就是AND的關系,如果需要OR關系,用or()鏈接就可以了
lqw.gt(User::getAge, 10).or().lt(User::getAge, 30);
需求:查詢年齡小于10,或者年齡大于30的用戶信息
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);List<User> userList = userDao.selectList(lqw);System.out.println(userList);
}
3.1.3 null值判定
-
在做條件查詢的時候,一般都會有很多條件供用戶查詢
-
這些條件用戶可以選擇用,也可以選擇不用
-
之前我們是通過動態SQL來實現的
<select id="selectByPageAndCondition" resultMap="brandResultMap"> select * from tb_brand <where><if test="brand.brandName != null and brand.brandName != '' ">and brand_name like #{brand.brandName}</if><if test="brand.companyName != null and brand.companyName != '' ">and company_name like #{brand.companyName}</if><if test="brand.status != null">and status = #{brand.status}</if> </where> limit #{begin} , #{size} </select>
-
那現在我們來試試在MP里怎么寫
需求:查詢數據庫表中,根據輸入年齡范圍來查詢符合條件的記錄
用戶在輸入值的時候, ?如果只輸入第一個框,說明要查詢大于該年齡的用戶
?如果只輸入第二個框,說明要查詢小于該年齡的用戶
如果兩個框都輸入了,說明要查詢年齡在兩個范圍之間的用戶
-
問題一:后臺如果想接收前端的兩個數據,該如何接收?
-
我們可以使用兩個簡單數據類型,也可以使用一個模型類,但是User類中目前只有一個age屬性
@TableName("tb_user") @Data public class User {private Long id;private String name;private String password;private Integer age;private String tel; }
-
使用一個age屬性,如何去接收頁面上的兩個值呢?這個時候我們有兩個解決方案
- 方案一:添加屬性age2,這種做法可以但是會影響到原模型類的屬性內容
- 方案二:新建一個模型類,讓其繼承User類,并在其中添加age2屬性,UserQuery在擁有User屬性后同時添加了age2屬性。
@Data @TableName("tb_user") public class UserQuery extends User{private Integer age2; }
- 環境準備好后,我們來實現下剛才的需求:
@Test void testQueryWrapper() { LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>(); UserQuery uq = new UserQuery(); uq.setAge(10); uq.setAge2(30); if (null != uq.getAge()) {lqw.gt(User::getAge, uq.getAge()); } if (null != uq.getAge2()) {lqw.lt(User::getAge, uq.getAge2()); } for (User user : userDao.selectList(lqw)) {System.out.println(user);} }
- 上面的寫法可以完成條件為非空的判斷,但是問題很明顯,如果條件多的話,每個條件都需要判斷,代碼量就比較大,來看MP給我們提供的簡化方式
lt
還有一個重載的方法,當condition為true時,添加條件,為false時,不添加條件
public Children lt(boolean condition, R column, Object val) {return this.addCondition(condition, column, SqlKeyword.LT, val); }
-
-
故我們可以把if的判斷操作,放到lt和gt方法中當做參數來寫
@Test void testQueryWrapper() { LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>(); UserQuery uq = new UserQuery(); uq.setAge(10); uq.setAge2(30); lqw.gt(null != uq.getAge(), User::getAge, uq.getAge()).lt(null != uq.getAge2(), User::getAge, uq.getAge2()); for (User user : userDao.selectList(lqw)) {System.out.println(user);} }
3.2 查詢投影
3.2.1 查詢指定字段
目前我們在查詢數據的時候,什么都沒有做默認就是查詢表中所有字段的內容,我們所說的查詢投影即不查詢所有字段,只查詢出指定內容的數據。
具體如何來實現?
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();lqw.select(User::getName,User::getName);for (User user : userDao.selectList(lqw)) {System.out.println(user);}
}
select(…)
方法用來設置查詢的字段列,可以設置多個
lqw.select(User::getName,User::getName);
如果使用的不是lambda,就需要手動指定字段
@Test
void testQueryWrapper() {QueryWrapper<User> qw = new QueryWrapper<>();qw.select("name", "age");for (User user : userDao.selectList(qw)) {System.out.println(user);}
}
3.2.2 聚合查詢
需求:聚合函數查詢,完成count、max、min、avg、sum的使用
- count:總記錄數
- max:最大值
- min:最小值
- avg:平均值
- sum:求和
-
count
@Test void testQueryWrapper() { QueryWrapper<User> qw = new QueryWrapper<>(); qw.select("count(*) as count"); for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);} }
-
max
@Test void testQueryWrapper() { QueryWrapper<User> qw = new QueryWrapper<>(); qw.select("max(age) as maxAge"); for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);} }
-
min
@Test void testQueryWrapper() { QueryWrapper<User> qw = new QueryWrapper<>(); qw.select("min(age) as minAge"); for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);} }
-
avg
@Test void testQueryWrapper() { QueryWrapper<User> qw = new QueryWrapper<>(); qw.select("avg(age) as avgAge"); for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap); } }
-
sum
@Test void testQueryWrapper() { QueryWrapper<User> qw = new QueryWrapper<>(); qw.select("sum(age) as sumAge"); for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);} }
3.2.3 分組查詢
@Test
void testQueryWrapper() {QueryWrapper<User> qw = new QueryWrapper<>();qw.select("max(age) as maxAge");qw.groupBy("tel");for (Map<String, Object> selectMap : userDao.selectMaps(qw)) {System.out.println(selectMap);}
}
注意:
- 聚合與分組查詢,無法使用lambda表達式來完成
- MP只是對MyBatis的增強,如果MP實現不了,我們可以直接在DAO接口中使用MyBatis的方式實現
3.3 查詢條件設定
前面我們只使用了lt()和gt(),除了這兩個方法外,MP還封裝了很多條件對應的方法
- 范圍匹配(> 、 = 、between)
- 模糊匹配(like)
- 空判定(null)
- 包含性匹配(in)
- 分組(group)
- 排序(order)
- ……
3.3.1 等值查詢
需求:根據用戶名和密碼查詢用戶信息
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.eq(User::getName,"Seto").eq(User::getPassword,"MUSICIAN");User user = userDao.selectOne(qw);System.out.println(user);
}
-
eq(): 相當于 =,對應的sql語句為
SELECT * FROM tb_user WHERE name = 'seto' AND password = 'MUSICIAN';
-
selectList:查詢結果為多個或者單個
-
selectOne:查詢結果為單個
3.3.2 范圍查詢
需求:對年齡進行范圍查詢,使用lt()、le()、gt()、ge()、between()進行范圍查詢
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.between(User::getAge,10,30);List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}
gt()
:大于(>)ge()
:大于等于(>=)lt()
:小于(<)lte()
:小于等于(<=)between()
:between ? and ?
3.3.3 模糊查詢
需求:查詢表中name屬性的值以J開頭的用戶信息,使用like進行模糊查詢
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.likeRight(User::getName,"J");List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}
like()
:前后加百分號,如 %J%,相當于包含J的namelikeLeft()
:前面加百分號,如 %J,相當于J結尾的namelikeRight()
:后面加百分號,如 J%,相當于J開頭的name
需求:查詢表中name屬性的值包含e的用戶信息,使用like進行模糊查詢
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();qw.like(User::getName,"e");List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}
3.3.4 排序查詢
需求:查詢所有數據,然后按照age降序
@Test
void testQueryWrapper() {LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();/*** condition :條件,返回boolean,當condition為true,進行排序,如果為false,則不排序* isAsc:是否為升序,true為升序,false為降序* columns:需要操作的列*/qw.orderBy(true,false,User::getAge);List<User> users = userDao.selectList(qw);for (User u : users) {System.out.println(u);}
}
遇到想用的功能,先自己用一個試試,方法名和形參名都很見名知意,遇到不確定的用法,再去官方文檔查閱資料
3.4 字段映射與表名映射
我們做查詢的時候,數據表中的字段名與模型類中的屬性名一致,查詢的時候沒有問題,那么問題就來了
-
問題一:表字段與模型類編碼屬性不一致
- 當表的列名和模型類的屬性名發生不一致,就會導致數據封裝不到模型對象,這個時候就需要其中一方做出修改,那如果前提是兩邊都不能改又該如何解決?
- MP給我們提供了一個注解
@TableField
,使用該注解可以實現模型類屬性名和表的列名之間的映射關系 - 例如表中密碼字段為
pwd
,而模型類屬性名為password
,那我們就可以用@TableField
注解來實現他們之間的映射關系
-
問題二:編碼中添加了數據庫中未定義的屬性
- 當模型類中多了一個數據庫表不存在的字段,就會導致生成的sql語句中在select的時候查詢了數據庫不存在的字段,程序運行就會報錯,錯誤信息為:
Unknown column '多出來的字段名稱' in 'field list'
- 具體的解決方案用到的還是
@TableField
注解,它有一個屬性叫exist,設置該字段是否在數據庫表中存在,如果設置為false則不存在,生成sql語句查詢的時候,就不會再查詢該字段了。
- 當模型類中多了一個數據庫表不存在的字段,就會導致生成的sql語句中在select的時候查詢了數據庫不存在的字段,程序運行就會報錯,錯誤信息為:
-
問題三:采用默認查詢開放了更多的字段查看權限
- 查詢表中所有的列的數據,就可能把一些敏感數據查詢到返回給前端,這個時候我們就需要限制哪些字段默認不要進行查詢。解決方案是
@TableField注解
的一個屬性叫select
,該屬性設置默認是否需要查詢該字段的值,true(默認值)表示默認查詢該字段,false表示默認不查詢該字段。 - 例如像密碼這種的敏感字段,不應該查詢出來作為JSON返回給前端,不安全
- 查詢表中所有的列的數據,就可能把一些敏感數據查詢到返回給前端,這個時候我們就需要限制哪些字段默認不要進行查詢。解決方案是
知識點:@TableField
名稱 | @TableField |
---|---|
類型 | 屬性注解 |
位置 | 模型類屬性定義上方 |
作用 | 設置當前屬性對應的數據庫表中的字段關系 |
相關屬性 | value(默認) :設置數據庫表字段名稱 exist :設置屬性在數據庫表字段中是否存在,默認為true,此屬性不能與value合并使用 select :設置屬性是否參與查詢,此屬性與select()映射配置不沖突 |
- 問題四:表名與編碼開發設計不同步
- 這個問題其實我們在一開始就解決過了,現在再來回顧一遍
- 該問題主要是表的名稱和模型類的名稱不一致,導致查詢失敗,這個時候通常會報如下錯誤信息Table ‘databaseName.tableNaem’ doesn’t exist
- 解決方案是使用MP提供的另外一個注解@TableName來設置表與模型類之間的對應關系。
知識點:@TableName
名稱 | @TableName |
---|---|
類型 | 類注解 |
位置 | 模型類定義上方 |
作用 | 設置當前類對應于數據庫表關系 |
相關屬性 | value(默認):設置數據庫表名稱 |
四、DQL控制
4.1 id生成策略控制
前面我們在新增數據的時候,主鍵ID是一個很長的Long類型,我們現在想要主鍵按照數據表字段進行自增長,在解決這個問題之前,我們先來分析一下ID的生成策略
-
不同的表,應用不同的id生成策略
- 日志:自增(1 2 3 4)
- 購物訂單:特殊規則(線下購物發票,下次可以留意一下)
- 外賣訂單:關聯地區日期等信息(這個我熟,舉個例子10 04 20220921 13 14,例如10表示北京市,04表示朝陽區,20220921表示日期等)
- 關系表:可以省略ID
……
-
不同的業務采用的ID生成方式應該是不一樣的,那么在MP中都提供了哪些主鍵生成策略,以及我們該如何進行選擇?
- 在這里我們又需要用到MP的一個注解叫
@TableId
- 在這里我們又需要用到MP的一個注解叫
知識點:@TableId
名稱 | @TableId |
---|---|
類型 | 屬性注解 |
位置 | 模型類中用于表示主鍵的屬性定義上方 |
作用 | 設置當前類中主鍵屬性的生成策略 |
相關屬性 | value(默認):設置數據庫表主鍵名稱type:設置主鍵屬性的生成策略,值查照IdType的枚舉值 |
4.1.1 AUTO
-
步驟一:設置生成策略為AUTO
@TableName("tb_user") @Data public class User { @TableId(type = IdType.AUTO) private Long id; private String name; private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; }
-
步驟二:設置自動增量為5,將4之后的數據都刪掉,防止影響我們的結果
-
步驟三:運行新增方法
@Test void testInsert(){ User user = new User(); user.setName("Helsing"); user.setAge(531); user.setPassword("HELL_SING"); user.setTel("4006669999"); userDao.insert(user); }
會發現,新增成功,并且主鍵id也是從5開始
我們進入源碼來看看還有什么生成策略
public enum IdType {AUTO(0),NONE(1),INPUT(2),ASSIGN_ID(3),ASSIGN_UUID(4),/** @deprecated */@DeprecatedID_WORKER(3),/** @deprecated */@DeprecatedID_WORKER_STR(3),/** @deprecated */@DeprecatedUUID(4);private final int key;private IdType(int key) {this.key = key;}public int getKey() {return this.key;}
}
- NONE: 不設置id生成策略
- INPUT:用戶手工輸入id
- ASSIGN_ID:雪花算法生成id(可兼容數值型與字符串型)
- ASSIGN_UUID:以UUID生成算法作為id生成策略
- 其他的幾個策略均已過時,都將被ASSIGN_ID和ASSIGN_UUID代替掉。
拓展: 分布式ID是什么?
- 當數據量足夠大的時候,一臺數據庫服務器存儲不下,這個時候就需要多臺數據庫服務器進行存儲
- 比如訂單表就有可能被存儲在不同的服務器上
- 如果用數據庫表的自增主鍵,因為在兩臺服務器上所以會出現沖突
- 這個時候就需要一個全局唯一ID,這個ID就是分布式ID。
4.1.2 INPUT
-
步驟一:將ID生成策略改為INPUT
@TableName("tb_user") @Data public class User { @TableId(type = IdType.INPUT) private Long id; private String name; private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; }
-
步驟二:運行新增方法
注意這里需要手動設置ID了@Test void testInsert(){ User user = new User(); user.setId(6L); user.setName("Helsing"); user.setAge(531); user.setPassword("HELL_SING"); user.setTel("4006669999"); userDao.insert(user); }
查看數據庫,ID確實是我們設置的值
4.1.3 ASSIGN_ID
-
步驟一:設置生成策略為ASSIGN_ID
@TableName("tb_user") @Data public class User { @TableId(type = IdType.ASSIGN_ID) private Long id; private String name; private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; }
-
步驟二:運行新增方法
這里就不要手動設置ID了@Test void testInsert(){ User user = new User(); user.setName("Helsing"); user.setAge(531); user.setPassword("HELL_SING"); user.setTel("4006669999"); userDao.insert(user); }
查看結果,生成的ID就是一個Long類型的數據,生成ID時,使用的是雪花算法
雪花算法(SnowFlake),是Twitter官方給出的算法實現 是用Scala寫的。其生成的結果是一個64bit大小整數
- 1bit,不用,因為二進制中最高位是符號位,1表示負數,0表示正數。生成的id一般都是用整數,所以最高位固定為0。
- 41bit-時間戳,用來記錄時間戳,毫秒級
- 10bit-工作機器id,用來記錄工作機器id,其中高位5bit是數據中心ID其取值范圍0-31,低位5bit是工作節點ID其取值范圍0-31,兩個組合起來最多可以容納1024個節點
- 序列號占用12bit,每個節點每毫秒0開始不斷累加,最多可以累加到4095,一共可以產生4096個ID
4.1.4 ASSIGN_UUID
-
步驟一:設置生成策略為ASSIGN_UUID
@TableName("tb_user") @Data public class User { @TableId(type = IdType.ASSIGN_UUID) private String id; private String name; private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; }
-
步驟二:修改表的主鍵類型
主鍵類型設置為varchar,長度要大于32,因為UUID生成的主鍵為32位,如果長度小的話就會導致插入失敗。 -
步驟三:運行新增方法
@Test void testInsert(){ User user = new User(); user.setName("Helsing"); user.setAge(531); user.setPassword("HELL_SING"); user.setTel("4006669999"); userDao.insert(user); }
4.1.5 ID生成策略對比
介紹完了這些主鍵ID的生成策略,那么以后我們開發用哪個呢?
- NONE:不設置ID生成策略,MP不自動生成,約定于INPUT,所以這兩種方式都需要用戶手動設置(SET方法),但是手動設置的第一個問題就是容易出錯,加了相同的ID造成主鍵沖突,為了保證主鍵不沖突就得做很多判定,實現起來較為復雜
- AUTO:數據庫ID自增,這種策略適合在數據庫服務器只有一臺的情況下使用,不可作為分布式ID使用
- ASSIGN_UUID:可以在分布式的情況下使用,而且能夠保證ID唯一,但是聲稱的主鍵是32位的字符串,長度過長占用空間,而且不能排序,查詢性能也慢
- ASSIGN_ID:可以在分布式的情況下使用,生成的是Long類型的數字,可以排序,性能也高,但是生成的策略與服務器時間有關,如果修改了系統時間,也有可能出現重復的主鍵
- 綜上所述,每一種主鍵的策略都有自己的優缺點,根據自己的項目業務需求的實際情況來使用,才是最明智的選擇
4.1.6 簡化配置
-
模型類主鍵策略設置
如果要在項目中的每一個模型類上都需要使用相同的生成策略,比如你有Book表,User表,Student表,Score表等好多個表,如果你每一個表的主鍵生成策略都是ASSIGN_ID,那我們就可以用yml配置文件來簡化開發,不用在每一個表的id上都加上@TableId(type = IdType.ASSIGN_ID)mybatis-plus:global-config:db-config:id-type: assign_id
-
數據庫表與模型類的映射關系
MP會默認將模型類的類名名首字母小寫作為表名使用,假如數據庫表的名稱都以tb_開頭,那么我們就需要將所有的模型類上添加@TableName("tb_TABLENAME")
,這樣做很繁瑣,有沒有更簡單的方式呢?- 我們可以在配置文件中設置表的前綴
mybatis-plus:global-config:db-config:id-type: assign_idtable-prefix: tb_
設置表的前綴內容,這樣MP就會拿 tb_加上模型類的首字母小寫,就剛好組裝成數據庫的表名(前提是你的表名得規范命名,別瞎起花里胡哨的名)。將User類的@TableName注解去掉,再次運行新增方法
@Data
public class User {@TableId(type = IdType.ASSIGN_ID)private Long id;private String name;private String password;private Integer age;private String tel;
}
4.2 多記錄操作
需求:根據傳入的id集合將數據庫表中的數據刪除掉。
- deleteBatchIds
@Test void testDeleteByIds(){ ArrayList<Long> list = new ArrayList<>(); list.add(1572543345085964289L); list.add(1572554951983460354L); list.add(1572555035978534913L); userDao.deleteBatchIds(list); }
執行成功后,數據庫表中的數據就會按照指定的id進行刪除。上面三個數據是我之前新增插入的,可以隨便換成數據庫中有的id
需求:根據傳入的ID集合查詢用戶信息
- selectBatchIds
@Test
void testSelectByIds() {ArrayList<Long> list = new ArrayList<>();list.add(1L);list.add(2L);list.add(3L);for (User user : userDao.selectBatchIds(list)) {System.out.println(user);}
}
4.3 邏輯刪除
邏輯刪除是刪除操作中比較重要的一部分,先來講個案例
-
這是一個員工和其所辦理的合同表,一個員工可以辦理多張合同表
-
員工ID為1的張業績,辦理了三個合同,但是她現在想離職跳槽了,我們需要將員工表中的數據進行刪除,執行DELETE操作
-
如果表在設計的時候有主外鍵關系,那么同時也要將合同表中的張業績的數據刪掉
-
后來公司要統計今年的總業績,發現這數據咋對不上呢,業績這么少,原因是張業績辦理的合同信息被刪掉了
-
如果只刪除員工,卻不刪除員工對應的合同表數據,那么合同的員工編號對應的員工信息不存在,那么就會產生垃圾數據,出現無主合同,根本不知道有張業績這個人的存在
-
經過我們的分析之后,我們不應該將表中的數據刪除掉,得留著,但是又得把離職的人和在職的人區分開,這樣就解決了上述問題
-
區分的方式,就是在員工表中添加一列數據
deleted
,如果為0說明在職員工,如果離職則將其改完1,(0和1所代表的含義是可以自定義的)
所以對于刪除操作業務問題來說有:
- 物理刪除:業務數據從數據庫中丟棄,執行的是delete操作
- 邏輯刪除:為數據設置是否可用狀態字段,刪除時設置狀態字段為不可用狀態,數據保留在數據庫中,執行的是update操作
MP中邏輯刪除具體該如何實現?
-
步驟一:修改數據庫表,
添加deleted列
字段名任意,類型int,長度1,默認值0(個人習慣,你隨便)
-
步驟二:實體類添加屬性
還得修改對應的pojo類,增加delete屬性(屬性名也任意,對不上用@TableField
來添加映射關系
標識新增的字段為邏輯刪除字段,使用@TableLogic
//表名前綴和id生成策略在yml配置文件寫了 @Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; //新增delete屬性 //value為正常數據的值(在職),delval為刪除數據的值(離職) @TableLogic(value = "0",delval = "1") private Integer deleted; }
-
步驟三:運行刪除方法
@Test void testLogicDelete(){userDao.deleteById(1); }
從測試結果來看,邏輯刪除最后走的是update操作,執行的是UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0
,會將指定的字段修改成刪除狀態對應的值。
- 思考:邏輯刪除,對查詢有沒有影響呢?
- 執行查詢操作
@Test void testSelectAll() { for (User user : userDao.selectList(null)) {System.out.println(user);} }
從日志中可以看到執行的SQL語句如下,WHERE條件中,規定只查詢deleted字段為0的數據
SELECT id,name,password,age,tel,deleted FROM tb_user WHERE deleted=0
輸出結果當然也沒有ID為1的數據了
如果還是想把已經刪除的數據都查詢出來該如何實現呢?
@Mapper
public interface UserDao extends BaseMapper<User> {//查詢所有數據包含已經被刪除的數據@Select("select * from tb_user")public List<User> selectAll();
}
- 如果每個表都要有邏輯刪除,那么就需要在每個模型類的屬性上添加
@TableLogic注解
,如何優化?- 在配置文件中添加全局配置,如下:
mybatis-plus:global-config:db-config:## 邏輯刪除字段名logic-delete-field: deleted## 邏輯刪除字面值:未刪除為0logic-not-delete-value: 0## 邏輯刪除字面值:刪除為1logic-delete-value: 1
使用yml配置文件配置了之后,就不需要
在模型類上用@TableLogic注解
了
介紹完邏輯刪除,邏輯刪除的本質為修改操作。如果加了邏輯刪除字段,查詢數據時也會自動帶上邏輯刪除字段。
執行的SQL語句為:
UPDATE tb_user SET deleted=1 WHERE id=? AND deleted=0
知識點:@TableLogic
名稱 | @TableLogic |
---|---|
類型 | 屬性注解 |
位置 | 模型類中用于表示刪除字段的屬性定義上方 |
作用 | 標識該字段為進行邏輯刪除的字段 |
相關屬性 | value:邏輯未刪除值;delval:邏輯刪除值 |
4.4 樂觀鎖
在學樂觀鎖之前,我們還是先由一個案例來引入
-
業務并發現象帶來的問題:秒殺
- 加入有100個商品在售,為了保證每個商品只能被一個人購買,如何保證不會超買或者重復賣
- 對于這一類的問題,其實有很多的解決方案可以使用
- 第一個最先想到的就是鎖,鎖在一臺服務器中是可以解決的,但是如果在多臺服務器下就沒辦法控制,比如12306有兩臺服務器,再進行賣票,在兩臺服務器上都添加鎖的話,那也有可能會在同一時刻有兩個線程在賣票,還是會出現并發問題
- 我們接下來介紹的這種方式就是針對于小型企業的解決方案,因為數據庫本身的性能就是個瓶頸,如果對其并發超過2000以上的就需要考慮其他解決方案了
簡單來說,樂觀鎖主要解決的問題是,當要更新一條記錄的時候,希望這條記錄沒有被別人更新
4.4.1 實現思路
- 數據庫表中添加
version
字段,比如默認值給個1 - 第一個線程要修改數據之前,取出記錄時,獲取當前的version=1
- 第二個線程要修改數據之前,取出記錄時,獲取當前的version=1
第一個線程執行更新時- set version = newVersion where version = oldVersion
- newVersion = version + 1
- oldVersion = version
- set version = newVersion where version = oldVersion
- 第二個線程執行更新時
set version = newVersion where version = oldVersion- newVersion = version + 1
- oldVersion = version
- 假如這兩個線程都來更新數據,第一個和第二個線程都可能先執行
- 假如第一個線程先執行更新,會將version改為2
- 那么第二個線程再更新的時候,set version = 2 where version = 1,此時數據庫表的version已經是2了,所以第二個線程修改失敗
- 假如第二個線程先執行更新,會將version改為2
- 那么第一個線程再更新的時候,set version = 2 where version = 1,此時數據庫表的version已經是2了,所以第一個線程修改失敗
- 假如第一個線程先執行更新,會將version改為2
4.4.2 實現步驟
-
步驟一:數據庫表添加列
加一列version,長度給個11,默認值設為1 -
步驟二:在模型類中添加對應的屬性
@Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; @TableLogic(value = "0", delval = "1") private Integer deleted; @Version private Integer version; }
-
步驟三:添加樂觀鎖攔截器
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor() {//1.定義Mp攔截器MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();//2.添加樂觀鎖攔截器mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mpInterceptor;} }
-
步驟四:執行更新操作
@Test void testUpdate(){ //1. 先通過要修改的數據id將當前數據查詢出來 User user = userDao.selectById(1L); //2. 修改屬性 user.setName("Person"); userDao.updateById(user); }
我們傳遞的是1(oldVersion),MP會將1進行加1,變成2,然后更新回到數據庫中(newVersion)
大概分析完樂觀鎖的實現步驟以后,我們來模擬一種加鎖的情況,看看能不能實現多個人修改同一個數據的時候,只能有一個人修改成功。
@Test
void testUpdate() {User userA = userDao.selectById(1L); //version=1User userB = userDao.selectById(1L); //version=1userB.setName("Jackson");userDao.updateById(userB); //B修改完了之后,version=2userA.setName("Person");//A拿到的version是1,但現在的version已經是2了,那么A在執行 UPDATE ... WHERE version = 1時,就必然會失敗userDao.updateById(userA);
}
至此,樂觀鎖的實現就已經完成了
五、快速開發
官方文檔地址
通過觀察我們之前寫的代碼,會發現其中有很多重復的內容,于是MP抽取了這些重復的地方,做成了一個模板供我們使用
要想完成代碼自動生成,我們需要有以下內容:
- 模板: MyBatisPlus提供,可以自己提供,但是麻煩,不建議
- 數據庫相關配置:讀取數據庫獲取表和字段信息
- 開發者自定義配置:手工配置,比如ID生成策略
5.1 代碼生成器實現
-
步驟一:創建一個Maven項目
-
步驟二:導入對應的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.blog</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>
-
步驟三:編寫引導類
@SpringBootApplication public class Mybatisplus04GeneratorApplication { public static void main(String[] args) {SpringApplication.run(Mybatisplus04GeneratorApplication.class, args); }}
-
步驟四:創建代碼生成類
public class CodeGenerator { public static void main(String[] args) {//1.獲取代碼生成器的對象AutoGenerator autoGenerator = new AutoGenerator();//設置數據庫相關配置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("YOURPASSWORD");autoGenerator.setDataSource(dataSource);//設置全局配置GlobalConfig globalConfig = new GlobalConfig();globalConfig.setOutputDir(System.getProperty("user.dir")+"/項目名/src/main/java"); //設置代碼生成位置globalConfig.setOpen(false); //設置生成完畢后是否打開生成代碼所在的目錄globalConfig.setAuthor("heima"); //設置作者globalConfig.setFileOverride(true); //設置是否覆蓋原始生成的文件globalConfig.setMapperName("%sDao"); //設置數據層接口名,%s為占位符,指代模塊名稱globalConfig.setIdType(IdType.ASSIGN_ID); //設置Id生成策略autoGenerator.setGlobalConfig(globalConfig);//設置包名相關配置PackageConfig packageInfo = new PackageConfig();packageInfo.setParent("com.aaa"); //設置生成的包名,與代碼所在位置不沖突,二者疊加組成完整路徑packageInfo.setEntity("domain"); //設置實體類包名packageInfo.setMapper("dao"); //設置數據層包名autoGenerator.setPackageInfo(packageInfo);//策略設置StrategyConfig strategyConfig = new StrategyConfig();strategyConfig.setInclude("tb_user"); //設置當前參與生成的表名,參數為可變參數strategyConfig.setTablePrefix("tb_"); //設置數據庫表的前綴名稱,模塊名 = 數據庫表名 - 前綴名 例如: User = tb_user - tb_strategyConfig.setRestControllerStyle(true); //設置是否啟用Rest風格strategyConfig.setVersionFieldName("version"); //設置樂觀鎖字段名strategyConfig.setLogicDeleteFieldName("deleted"); //設置邏輯刪除字段名strategyConfig.setEntityLombokModel(true); //設置是否啟用lombokautoGenerator.setStrategy(strategyConfig);//2.執行生成操作autoGenerator.execute();} }
對于代碼生成器中的代碼內容,我們可以直接從官方文檔中獲取代碼進行修改,
- 步驟五:運行程序
運行成功后,會在當前項目中生成很多代碼,代碼包含controller
,service
,mapper
和entity
等
至此代碼生成器就已經完成工作,我們能快速根據數據庫表來創建對應的類,簡化我們的代碼開發。
初期還是不建議直接使用代碼生成器,還是多自己手寫幾遍比較好
5.2 MP中Service的CRUD
回顧我們之前業務層代碼的編寫,編寫接口和對應的實現類:
回顧我們之前業務層代碼的編寫,編寫接口和對應的實現類:
public interface UserService{}@Service
public class UserServiceImpl implements UserService{}
接口和實現類有了以后,需要在接口和實現類中聲明方法
public interface UserService{public List<User> findAll();
}@Service
public class UserServiceImpl implements UserService{@Autowiredprivate UserDao userDao;public List<User> findAll(){return userDao.selectList(null);}
}
MP看到上面的代碼以后就說這些方法也是比較固定和通用的,那我來幫你抽取下,所以MP提供了一個Service接口和實現類,分別是:IService和ServiceImpl,后者是對前者的一個具體實現。
以后我們自己寫的Service就可以進行如下修改:
public interface UserService extends IService<User>{}@Service
public class UserServiceImpl extends ServiceImpl<UserDao, User> implements UserService{}
修改以后的好處是,MP已經幫我們把業務層的一些基礎的增刪改查都已經實現了,可以直接進行使用。
編寫測試類進行測試:
@SpringBootTest
class Mybatisplus04GeneratorApplicationTests {private IUserService userService;@Testvoid testFindAll() {List<User> list = userService.list();System.out.println(list);}
}
六、復習
之前如果要寫動態SQL查詢,需要用XML配置文件,用<where>
,<if>
標簽來自動去除and連接詞啥的。
<select id="selectByCondition" resultMap="brandResultMap">select *from tb_brand<where><if test="brand.brandName != null and brand.brandName != '' ">and brand_name like #{brand.brandName}</if><if test="brand.companyName != null and brand.companyName != '' ">and company_name like #{brand.companyName}</if><if test="brand.status != null">and status = #{brand.status}</if></where>limit #{begin} , #{size}
</select>
學完MyBatisPlus之后,我們可以不用XML配置文件,就用MP也能寫動態SQL,用Wrapper類。
- 針對圖書類別和名稱做的一個動態SQL就長這個樣子
@Override
public List<Book> getByCondition(String type,String name) {LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();lqw.like(!(type == null || "".equals(type)), Book::getType, type).like(!(name == null || "".equals(name)),Book::getName, name);return bookDao.selectList(lqw);
}
MP里的and不用顯示聲明,而且還可以很簡單的幫我們完成模糊查詢,當判斷條件為false時,則不會進行SQL語句的拼接。而且也不需要創建文件,寫配置
不過復雜的SQL語句還是要用XML寫的,用MP寫的話,可讀性不