Mybatis-Plus 我來填坑~
簡介:
此Demo 主要應用SpringBoot 來展示Mybatis-Plus 特性, 以及在開發過程中可能應用到的插件的演示。
源碼:https://github.com/wunian7yulian/MybatisPlusDemo
本文:https://wunian7yulian.github.io/MybatisPlusDemo/
-
相同於 MyBatis官方指南 中有了詳細介紹
不同於 實踐演示
-
目的:
主要借此做為突破口, 一是將自我學習成文記錄下來, 二是將Demo 慢慢做成一個自己或者面向大眾的后端腳手架工具。
-
規划:
分享-實踐-填坑-總結-腳手架-分享-實踐......
目錄
一、簡單介紹
官方說明 :
Mybatis-Plus(簡稱MP)是一個Mybatis的增強工具,在 Mybatis 的基礎上只做增強不做改變,為簡化開發而生!
-
潤物無聲
只做增強不做改變,引入它不會對現有工程產生影響。
-
效率至上
只需簡單配置,即可快速進行 CRUD 操作,從而節省大量時間。
-
豐富功能
熱加載、代碼生成、分頁、性能分析等功能一應俱全。
成績:
MyBatis-Plus 榮獲【2018年度開源中國最受歡迎的中國軟件】 TOP5
最新版本:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.0.7.1</version>
</dependency>
開發層面MyBatis-Plus特色
- 代碼生成
- 條件構造器
Mybatis-Plus中的Plus
官方簡介說明 MP 和Mybatis 就像是 游戲中的p1 和p2 一樣 兄弟搭配 干活不累 、
我在使用中明顯感覺到 其實他更像是 馬里奧和蘑菇 吃了蘑菇 我們跳的高度更高了一些。
二、MP的特性
-
無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
-
損耗小:啟動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
-
強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
-
支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
-
內置代碼生成器:采用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
-
內置分頁插件:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之后,寫分頁等同於普通 List 查詢
-
內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
-
支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多種數據庫
-
支持主鍵自動生成:支持多達 4 種主鍵策略(
內含分布式唯一 ID 生成器 - Sequence
),可自由配置,完美解決主鍵問題 -
支持 XML 熱加載:Mapper 對應的 XML 支持熱加載,對於簡單的 CRUD 操作,甚至可以無 XML 啟動
-
支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
-
支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
-
支持關鍵詞自動轉義:支持數據庫關鍵詞(order、key......)自動轉義,還可自定義關鍵詞
-
內置性能分析插件:可輸出 Sql 語句以及其執行時間,建議開發測試時啟用該功能,能快速揪出慢查詢
-
內置 Sql 注入剝離器:支持 Sql 注入剝離,有效預防 Sql 注入攻擊
三、MP框架結構
四、簡單的入門Demo(Mysql)
Demo代碼地址:
https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/simpledemo
Demo 環境:
windows 7
jdk 1.8.0.40
idea Ultimate
maven 3.3.9
初始化:
數據庫:MySql
創建數據庫mp_demo_db
設置字符集 utf-8
DDL:
-- 創建簡單表格
DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT COMMENT '主鍵ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年齡',
`email` varchar(50) DEFAULT NULL COMMENT '郵箱'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DML:
-- 初始化數據
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
工程:
為了方便快捷 選用 SpringBoot 工程作為Demo支撐
第一步、創建工程
輸入項目包名 並添加mysql模塊 創建完畢。
第二步、引入依賴坐標
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
<!--手動添加模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>3.0.7.1</version>
</dependency>
第三步、配置數據源
在 application.yml
配置文件中添加相關配置:
# DataSource Config
spring:
datasource:
# 這里如果有錯誤是因為 maven mysql包 選擇了 runtime 形式的 scope 可以不用管它 繼續下一步就好
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf8
username: root
password: 123456
第四步、添加mybatis掃描位置
@SpringBootApplication
@MapperScan("com.lynwood.mp.simpledemo.mapper")
public class SimpledemoApplication {
public static void main(String[] args) {
SpringApplication.run(SimpledemoApplication.class, args);
}
}
第五步、pojo及mapper
pojo:
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lynwood.mp.simpledemo.model.User;
public interface UserMapper extends BaseMapper<User> {
}
第六步、測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
List<User> userList = userMapper.selectList(null);
Assert.assertEquals(5, userList.size());
userList.forEach(System.out::println);
}
}
運行結果:
User{id=1, name='Jone', age=18, email='test1@baomidou.com'}
User{id=2, name='Jack', age=20, email='test2@baomidou.com'}
User{id=3, name='Tom', age=28, email='test3@baomidou.com'}
User{id=4, name='Sandy', age=21,email='test4@baomidou.com'}
User{id=5, name='Bill', age=24,email='test5@baomidou.com'}
五、核心功能
核心一-簡便之-代碼生成器(AutoGenerator)
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發效率。
Demo代碼地址:
https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/simpledemo
Demo 環境:
同上
工程:
第一步、創建模塊
創建了autogenerator_demo模塊(記得添加mysql模塊 )作為演示代碼生成器功能配置。
第二步、引入依賴坐標
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
<!--手動添加模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.0.6</version>
</dependency>
注意:MP 3.0.3
之后移除了自動模板引擎依賴,需要手動添加對應模板引擎
第三步、編寫代碼生成器
直接復制就行啦!
package com.lynwood.mp.autogenerator_demo;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
//注意引入GlobalConfig 使用 import com.baomidou.mybatisplus.generator.config.*;
public class GeneratorCode {
/**
* 讀取控制台內容
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("請輸入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("請輸入正確的" + tip + "!");
}
public static void main(String[] args) {
// 代碼生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("Lynwood");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 數據源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp_demo_db?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模塊名"));
pc.setParent("com.lynwood.mp.autogenerator_demo");
mpg.setPackageInfo(pc);
// 自定義配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定義輸出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定義配置會被優先輸出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定義輸出文件名
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定義輸出模板
// templateConfig.setEntity();
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass(null);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setSuperControllerClass(null);
strategy.setInclude(scanner("表名"));
strategy.setSuperEntityColumns(null);
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
第四步、運行測試
在控制台輸入:
就可以生成代碼啦!
都包含 :
發現問題: 如果是多模塊項目 生成的文件會直接到了父項目目錄下
原因是:在代碼的全局配置中 String projectPath = System.getProperty("user.dir");
獲取Working Directory時 返回的是項目路徑,並非模塊路徑!
解決方法:
我們可以設定運行參數選項
將 Working Directory 調整為 當前模塊目錄 再次運行就ok了!
-
因為沒有引入mvc 模塊 以至於@Controller 會飄紅 再Demo中就沒有將生成代碼傳入 大可拉取代碼本地使用!
-
相關代碼生成器的配置:官方生成器配置 可配置項過多無法詳細介紹 有相關使用會提及
核心二 - 清晰之-CRUD接口
Demo代碼地址:
https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/mp_crud_demo
Mybatis-Plus 為我們提供了豐富的 增刪改查接口
我們可以分為三類 Mapper的CRUD接口、Service的CRUD接口和mapper層選裝件接口:
確實比較豐富 , 下面會以具有代表性的例子來使用 演示 作為Demo主要內容
工程:
第一步、創建模塊
創建mp_crud_demo模塊 選擇mysql
第二步、引入依賴坐標
因為需要生成表對應Pojo 還需要代碼生成器
然后我們將上面的直接拷貝一下
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
<!--手動添加模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.20</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.0.6</version>
</dependency>
再添加 lombok 依賴(因:生成代碼中的實體默認是使用@Data 等注解的)
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
第三步、生成代碼
- 復制上一Demo模塊生成器代碼
- 更改模塊名稱使之對應
pc.setParent("com.lynwood.mp.mp_crud_demo");
- 設置Working Directory 為當前模塊 防止文件輸出位置錯誤
- 運行獲取代碼
- 刪除不必要用到的controller層
第四步、其他配置需要
-
設定mybatis掃描位置
在
MpCrudDemoApplication
上添加注解:@MapperScan("com.lynwood.mp.mp_crud_demo.*.mapper**")
注意 掃描包的位置!
-
配置數據源:
# DataSource Config spring: datasource: # 這里如果有錯誤是因為 maven mysql包 選擇了 runtime 形式的 scope 可以不用管它 繼續下一步就好 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf-8 username: root password: 123456
第五步、對MP探個究竟!
以userMapper
作為范例
-
查看
UserMapper
:打開
UserMapper.java
源代碼:public interface UserMapper extends BaseMapper<User> { }
不難發現它繼承了
BaseMapper<User>
接口打開
com.baomidou.mybatisplus.core.mapper.BaseMapper<T>
查看當前接口的結構(Structure):
原來是MP 將之前的mybatis里面每個mapper的所有方法 經過泛型進行提煉到了一個BaseMapper 接口中,我們只需要將自己的mapper 繼承此接口且將泛型指定便可獲得強大的CRUD功能!
-
再去查看
UserMapper.xml
文件 :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lynwood.mp.mp_crud_demo.business.mapper.UserMapper">
</mapper>
完全丟棄了Mybatis
中字段映射以及一段段復雜的配置!
發現問題: 我雖然看到了方法 但是並沒有一條SQL 那么它是怎樣做到查詢的呢?
探索:
- 首先 在項目搭建的時候添加了 坐標:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.7.1</version>
</dependency>
我們進入mybatis-plus-boot-starter
的pom 發現他幫我們裝配了對應版本的mybatis-plus
並且依賴了其他 例如: spring-boot-autoconfigure``spring-boot-starter-jdbc
等;
因為了解SpringBoot
中配置入口是一個@Configuration
那么打開mybatis-plus-boot-starter
的jar
包 看到:MybatisPlusAutoConfiguration.java
:
...
public class MybatisPlusAutoConfiguration {
...
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
...
if (this.applicationContext.getBeanNamesForType(ISqlInjector.class, false, false).length > 0) {
ISqlInjector iSqlInjector = (ISqlInjector)this.applicationContext.getBean(ISqlInjector.class);
globalConfig.setSqlInjector(iSqlInjector);
}
factory.setGlobalConfig(globalConfig);
return factory.getObject();
}
...
}
看到它在設置sqlSessionFactory
的時候為我們指定了一個com.baomidou.mybatisplus.core.injector.ISqlInjector.class
作為factory.globalConfig.sqlInjector
(SQL注入器)
然后 我們打開:ISqlInjector
的實現類AbstractSqlInjector
...
public abstract class AbstractSqlInjector implements ISqlInjector {
...
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
...
List<AbstractMethod> methodList = this.getMethodList();
Assert.notEmpty(methodList, "No effective injection method was found.", new Object[0]);
methodList.forEach((m) -> {
m.inject(builderAssistant, mapperClass);
});
...
}
}
methodList
是所有的方法的一個集合 其元素類型都是AbstractMethod.class
類型的, 並且對每個方法都進行了inject(...)
,
那么查看inject()
方法源碼:
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
...
this.injectMappedStatement(mapperClass, modelClass, tableInfo);
}
}
原來最終調用了injectMappedStatement()
方法
然而
public abstract MappedStatement injectMappedStatement(Class<?> var1, Class<?> var2, TableInfo var3);
是抽象的 需要自己的子類去實現的
我們以其中一個 SelectById.class
作為例子查看
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty()), Object.class);
return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo);
}
再去查看 SqlMethod.SELECT_BY_ID;
:
SELECT_BY_ID("selectById", "根據ID 查詢一條數據", "SELECT %s FROM %s WHERE %s=#{ %s}"),
soga~
結束 最后實際上 MP將SQL語句 封裝了固定的模板 com.baomidou.mybatisplus.core.enums.SqlMethod
從而提供給了我們便捷的使用!
第六步、使用它
我們使用非Wrapper的具有代表性的接口作為Demo的演示
Wrapper 在后面會有單獨的Demo演示
Mapper - 簡單的CRUD:
@RunWith(SpringRunner.class)
@SpringBootTest
public class MpCrudDemoApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void simplenessMapperCURD() {
//增加
User addUser = new User();
addUser.setAge(18);
addUser.setEmail("wunian_@hotmail.com");
addUser.setName("Lynwood");
userMapper.insert(addUser); // insert 之后是將id裝配到實體對象里的
System.out.println("add:\n" + addUser);
// User(id=1082883152404103169, name=Lynwood, age=18, email=wunian_@hotmail.com)
// id = 1082883152404103169 之所以這么長是因為 MP底層給我們自己以uuid 的形式添加了 user對象的id屬性
//修改
User updateUser = new User();
updateUser.setId(1082883152404103169L);
updateUser.setName("ok?");
userMapper.updateById(updateUser);
System.out.println("update:\n" + updateUser);
// User(id=1082883152404103169, name=ok?, age=null, email=null)
// 刷新數據庫 更改成功 但是沒有講其他對象進行裝配
//查詢
User selectUser = userMapper.selectById(1082883152404103169L);
System.out.println("select:\n" + selectUser);
//User(id=1082883152404103169, name=ok?, age=18, email=wunian_@hotmail.com)
//刪除
int i = userMapper.deleteById(1082883152404103169L);
if (i==1){
System.out.println("delete:\n" + "刪除成功!");
//刷新庫 刪除成功
}
}
}
發現問題: 雖然MP替我生成了 uuid 作為主鍵,但是還是想用數據庫自增形式主鍵怎么辦?
解決:
MP提供的主鍵策略有:
- AUTO 數據庫ID自增
- INPUT 用戶輸入ID
- ID_WORKER 全局唯一ID,Long類型的主鍵
- ID_WORKER_STR 字符串全局唯一ID
- UUID 全局唯一ID,UUID類型的主鍵
- NONE 該類型為未設置主鍵類型
MP的主鍵策略默認使用的是ID_WORKER
(詳情:https://mybatis.plus/config/#idtype)
在User 中設定 id字段為@TableId(type = IdType.AUTO)
或者 全局設置 使用主鍵策略,在yaml 添加:
mybatis-plus:
global-config:
db-config:
id-type: auto
重要: **因為剛才的測試插入了id為:1082883152404103169
的 數據 我們需要將自增序列首先恢復正常! 否則下一個id為1082883152404103170 看上去也是亂的!! 小心坑哈~ **
delete from user;
alter table user auto_increment= 1;
嘗試 增加操作 輸出:
add:
User(id=1, name=Lynwood, age=18, email=wunian_@hotmail.com)
Mapper - 批量的CRUD接口:
填充測試數據:
DELETE FROM user;
INSERT INTO user ( name, age, email) VALUES
( 'Lynwood',18,'wunian_@hotmail.com')
( 'Jone', 18, 'test1@baomidou.com'),
( 'Jack', 20, 'test2@baomidou.com'),
( 'Tom', 28, 'test3@baomidou.com'),
( 'Sandy', 21, 'test4@baomidou.com'),
( 'Billie', 24, 'test5@baomidou.com');
測試:
@Test
public void batchMapperCURD() {
// 多id 查詢
List<Long> idList = new ArrayList<>();
idList.add(1L);
idList.add(3L);
List<User> userList = userMapper.selectBatchIds(idList);// id的多個查詢
System.out.println("selectBatch:" );
userList.forEach(System.out::println);
//User(id=1, name=Lynwood, age=18, email=wunian_@hotmail.com)
//User(id=3, name=Jack, age=20, email=test2@baomidou.com)
// 多條件 查詢
Map<String,Object> stringObjectMap = new HashMap<>();
stringObjectMap.put("age",18);
stringObjectMap.put("id",2);
List<User> selectByMap = userMapper.selectByMap(stringObjectMap);// 字段-值 鍵值對集合 作為 '且' 關系
System.out.println("selectByMap:" );
selectByMap.forEach(System.out::println);
//User(id=2, name=Jone, age=18, email=test1@baomidou.com)
// 多id 刪除
int deleteCount = userMapper.deleteBatchIds(idList);// id的多個刪除
System.out.println("deleteBatch:\n"+ deleteCount );
// 2
// 多條件 刪除
int deleteCount2 = userMapper.deleteByMap(stringObjectMap);// id的多個查詢
System.out.println("deleteByMap:\n"+ deleteCount2 );
// 1
}
注意使用*ByMap()
方法時 條件是 且關系就ok了!
Mapper - 選裝組件:
查看了MP作者說的:
作者在源碼注釋中是這么寫的....:
/**
* <p> 批量新增數據,自選字段 insert </p>
* <p> 不同的數據庫支持度不一樣!!! 只在 mysql 下測試過!!! 只在 mysql 下測試過!!! 只在 mysql 下測試過!!! </p>
* <p> 除了主鍵是 <strong> 數據庫自增的未測試 </strong> 外理論上都可以使用!!! </p>
* <p> 如果你使用自增有報錯或主鍵值無法回寫到entity,就不要跑來問為什么了,因為我也不知道!!! </p>
* <p>
* 自己的通用 mapper 如下使用:
* int insertBatchSomeColumn(List<T> entityList);
*
* <li> 注意1: 不要加任何注解 !! </li>
* <li> 注意2: 自選字段 insert !!,如果個別字段在 entity 里為 null 但是數據庫中有配置默認值, insert 后數據庫字段是為 null 而不是默認值 </li>
*
* <p>
* 常用的構造入參:
* </p>
*/
┓( ´∀` )┏ ~
然后分析其作用 覺得既然有wrapper的強大條件構造器 決定不再分析 想了解可以點擊:
不過 案例 說明了MP的一些可以自己擴展的一個流程 :
-
第一步: 在
UserMapper
添加方法/** 清空表數據 */ void clearTable();
-
第二步:自定義實現
在於
business
同級下創建mp_injector/methods
目錄並創建類名CLearTable.java
要與添加的方法名 相同!且要實現
com.baomidou.mybatisplus.core.injector.AbstractMethod
完成自定義擴展public class ClearTable extends AbstractMethod { @Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { /* 執行 SQL */ String sql = "delete from " + tableInfo.getTableName(); /* mapper 接口方法名一致 */ String method = "clearTable"; SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addDeleteMappedStatement(mapperClass, method, sqlSource); } }
-
第三步:將自定義實現 添加到MP方法列表
在
mp_injector
下創建MySqlInjector.java
來進行對MP的擴展操作:需要繼承
com.baomidou.mybatisplus.core.injector.DefaultSqlInjector
並且重寫 MP獲取方法列表的方法@Component public class MySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList() { List<AbstractMethod> methodList = super.getMethodList(); //增加了 自定義方法 methodList.add(new ClearTable()); return methodList; } }
-
第四步:測試
@Test public void myInjectorMapperCURD() { userMapper.clearTable(); }
查庫 全部刪除了
-
總結:
不難發現其實際上就是 在上文 對MP探個究竟 中探索到的 所有方法的原理
Service - CRUD:
非Wrapper部分基本與Mapper 接口一致 只是將接口提到了service層.
略~
核心三 - 強大之 -Wrapper條件構造器
Demo代碼地址:
https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/mp_wrapper_demo
工程:
第一 -->第四步 同上一工程步驟
-
注意更改生成器中的 包名模塊名
-
復制yaml 時注意增加 MP主鍵策略配置:
mybatis-plus:
global-config:
db-config:
id-type: auto
第五步、探索Wrapper
備:關於 java8 lambda 表達式 默認為熟悉
Wrapper含義:
版本不同:
因為MP 對外已經有了兩個大的版本 2.x 和3.x版本
在2.x版本中 EntityWrapper
作為Wrapper 的主要繼承實現,
例:
EntityWrapper<User> ew = new EntityWrapper<User>();
ew.setEntity(new User(1));
ew.where("user_name={0}", "'zhangsan'").and("id=1")
.orNew("user_status={0}", "0").or("status=1")
.notLike("user_nickname", "notvalue")
.andNew("new=xx").like("hhh", "ddd")
.andNew("pwd=11").isNotNull("n1,n2").isNull("n3")
.groupBy("x1").groupBy("x2,x3")
.having("x1=11").having("x3=433")
.orderBy("dd").orderBy("d1,d2");
System.out.println(ew.getSqlSegment());
實際上 此包裝實際上是使用的是 數據庫字段 不是Pojo 里面的成員變量
在3.x 升級中對Wrapper進行了改動:
-
全面支持了jdk8的Lambda的使用
-
Wrapper<T>
實現類的改動1.
EntityWrapper<T>
更名為QueryWrapper<T>
2.新增一個實現類UpdateWrapper<T>
用於update
方法 -
BaseMapper<T>
的改動1.去除了
insertAllColumn(T entity)
方法
2.去除了updateAllColumn(T entity)
方法
3.新增update(T entity, Wrapper<T> updateWrapper)
方法
在3.x 中將 所有的操作划分成 查詢QueryWrapper
和UpdateWrapper
並且將其共有的方法做了一層抽離放到了AbstractWrapper
中
3.x Wrapper主要繼承結構:
我們查看四個實現類:QueryWrapper
、UpdateWrapper
、LambdaQueryWrapper
、LambdaUpdateWrapper
的一個總體抽象: AbstractWrapper
它將共有的提升到這一層並做了實現,
其實主要是對SQL語言所有DML語句中公用的一些關鍵字做了統一的接口
AbstractWrapper接口:
AbstractWrapper接口:
UpdateWrapper接口:
第六步、使用接口
##打印SQL日志配置
為了方便查看最后MP封裝轉換的最終SQL,在yml配置文件中添加配置:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
一> 編寫AbstractWrapper測試類:
...
@RunWith(SpringRunner.class)
@SpringBootTest
public class MpWrapperDemoApplicationAbstractWrapperTests {
@Autowired
private UserMapper userMapper;
/**
* 測試 allEq() 等同於 WHERE name = ? AND age = ?
* +
* 使用 selectList() 返回多個指定類型對象 的集合
*
*/
@Test
public void abstractWrapperTest_allEq() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
HashMap<String,Object> param = new HashMap<>();
param.put("name","Lynwood");
param.put("age","18");
userQueryWrapper.allEq(param);
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
* ==> Preparing: SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
* ==> Parameters: Lynwood(String), 18(String)
*/
}
/**
* 測試 eq() 等同於 WHERE name = ?
* +
* 使用selectOne()
* 當返回多條會報錯
*/
@Test
public void abstractWrapperTest_eq() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("name","Lynwood");
User user = userMapper.selectOne(userQueryWrapper);
System.out.println(user);
/**
* 輸出:
* Preparing: SELECT id,name,age,email FROM user WHERE name = ?
* Parameters: Lynwood(String)
*/
}
/** not equals 縮寫 ~~
*
* 測試 ne() 等同於 WHERE age <> ?
* +
* 使用 selectObjs() 返回多個 非指定類型對象 的集合
*
*/
@Test
public void abstractWrapperTest_ne() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.ne("age", 18);
List<Object> objectList = userMapper.selectObjs(userQueryWrapper);
objectList.forEach(System.out::println);
/**
* 輸出:
* ==> Preparing: SELECT id,name,age,email FROM user WHERE age <> ?
* ==> Parameters: 18(Integer)
*/
}
/***************************************************** 多接口連用 * 默認AND 關系*******************************/
/** 測試
* gt() : greater than 等同於 >
* ge() : greater equals 等同於 >=
* lt() : less than 等同於 <
* le() : less equals 等同於 <=
*
* 使用 selectMaps() 返回多個 指定到Map做封裝 的集合
*
*/
@Test
public void abstractWrapperTest_gt_ge_lt_le() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.ge("age", 21);
userQueryWrapper.le("age", 24);
List<Map<String, Object>> mapList = userMapper.selectMaps(userQueryWrapper);
mapList.forEach(System.out::println);
/**
* 輸出:
* ==> Preparing: SELECT id,name,age,email FROM user WHERE age >= ? AND age <= ?
* ==> Parameters: 21(Integer), 24(Integer)
*/
}
/**
* 測試
* between() 等同於 WHERE age between ? and ?
* notBetween() 等同於 WHERE age not between ? and ?
* +
* 使用 selectList() 返回多個 非指定類型對象 的集合
*
*/
@Test
public void abstractWrapperTest_between() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.between("age", 18,24)
.notBetween("id",0,11);// 支持鏈式調用
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE age BETWEEN ? AND ? AND id NOT BETWEEN ? AND ?
==> Parameters: 18(Integer), 24(Integer), 0(Integer), 11(Integer)
*/
}
/**
* 測試
* like() 等同於 LIKE '%?%'
* notLike() 等同於 NOT LIKE '%?%'
* likeLeft() 等同於 LIKE '%?'
* likeRight() 等同於 LIKE '?%'
* +
* 使用 selectCount() 返回查詢到的條數
*/
@Test
public void abstractWrapperTest_like() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.like("email", "test")
.notLike("email","test4")
.likeRight("name","J") // J%
.likeLeft("name","e"); //%e
Integer selectCount = userMapper.selectCount(userQueryWrapper);
System.out.println(selectCount);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE email LIKE ? AND email NOT LIKE ? AND name LIKE ? AND name LIKE ?
==> Parameters: %test%(String), %test4%(String), J%(String), %e(String)
*/
}
/**
* 測試
* null() 等同於 is null
* isNotNull() 等同於 is not null
*
*/
@Test
public void abstractWrapperTest_null() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.isNotNull("name");
Integer selectCount = userMapper.selectCount(userQueryWrapper);
System.out.println(selectCount);
/**
* 輸出:
==> Preparing: SELECT COUNT(1) FROM user WHERE name IS NOT NULL
==> Parameters:
<== Columns: COUNT(1)
*/
}
/**
* 測試
* in() 等同於 IN (?,?)
* notIn() 等同於 NOT IN (?,?)
* inSql() 等同於 IN (sql)
* notInSql() 等同於 NOT IN (sql)
*/
@Test
public void abstractWrapperTest_in() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
List<Long> idLists = new ArrayList<>();
idLists.add(11L);
idLists.add(13L);
userQueryWrapper
// .in("id",idLists);
.inSql("id","select id from user where id<=15");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE id IN (select id from user where id<=15)
==> Parameters:
*/
}
/**
* 測試
* groupBy() .. 支持多參數 等同於 GROUP BY age,name,id
*/
@Test
public void abstractWrapperTest_group() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.groupBy("age","name","id");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user GROUP BY age,name,id
==> Parameters:
*/
}
/**
* 測試
* orderBy() .. 支持多參數 等同於 關於 condition
* orderByDesc() .. 支持多參數 等同於 ORDER BY age DESC , id DESC
* orderByAsc() .. 支持多參數 等同於 ORDER BY age ASC , id ASC
*/
@Test
public void abstractWrapperTest_order() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.orderByAsc("age","id");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user ORDER BY age ASC , id ASC
==> Parameters:
*/
}
/**
* 測試
* having() .. 支持多參數 等同於 HAVING sum(id)> ?
*/
@Test
public void abstractWrapperTest_having() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.groupBy("age")
.having("sum(id)>{0}",20);// 替換里面的參數
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user GROUP BY age HAVING sum(id)>?
==> Parameters: 20(Integer)
*/
}
/**
* 測試
* or() 等同於 or
* and() 等同於 and
*/
@Test
public void abstractWrapperTest_or_and() {
// 簡單 or 和and
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.eq("id",11)
.or() // 默認and()
// .and() // 兩個不能連用
.eq("id",13);
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE id = ? OR id = ?
==> Parameters: 11(Integer), 13(Integer)
*/
// 嵌套的 or 和and
QueryWrapper<User> userQueryWrapper1 = new QueryWrapper<>();
userQueryWrapper1
.eq("id",14)
.or(i -> i.eq("name", "Lynwood").ne("age", "18"));
List<User> userList1 = userMapper.selectList(userQueryWrapper1);
userList1.forEach(System.out::println);
/**輸出:
*
* ==> Preparing: SELECT id,name,age,email FROM user WHERE id = ? OR ( name = ? AND age <> ? )
* ==> Parameters: 14(Integer), Lynwood(String), 18(String)
*/
/**!!!!!!!!!!!! .or(i -> i.eq("name", "Lynwood").ne("age", "18")); 解釋 見下文發現問題*/
}
/**
* 測試
* apply() 動態傳參防止 sql注入 占位符 替換 以值的形式添加
*/
@Test
public void abstractWrapperTest_apply() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.apply("id>{0}",13);
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE id>?
==> Parameters: 13(Integer)
*/
}
/**
* 測試
* last() 在sql 最后追加
*
* 最常用 last("limit 1")
*/
@Test
public void abstractWrapperTest_last() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.last("limit 2");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user limit 2
==> Parameters:
*/
}
/**
* 測試
* exists() 拼接 EXISTS ( sql語句 )
* notExists() 拼接 NOT EXISTS ( sql語句 )
*/
@Test
public void abstractWrapperTest_exists() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.exists("SELECT id,name,age,email FROM user where id= 222");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE EXISTS (SELECT id,name,age,email FROM user where id= 222)
==> Parameters:
<== Total: 0
*/
}
/**
* 測試
* nested() 正常嵌套 (無 or and 模式嵌套)
*/
@Test
public void abstractWrapperTest_nested() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper
.nested(i -> i.eq("name", "Lynwood").eq("age", "18"));
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
/**
* 輸出:
==> Preparing: SELECT id,name,age,email FROM user WHERE ( name = ? AND age = ? )
==> Parameters: Lynwood(String), 18(String)
*/
}
}
發現問題: boolean condition
、 R column
和 Function<This, This> func
三個參數相關問題
說明及使用:
1.關於入參: boolean condition
- 以下出現的第一個入參
boolean condition
表示該條件是否加入最后生成的sql中,默認是true
使用ne()
方法舉例 源碼 :
@Override
public This ne(boolean condition, R column, Object val) {
return addCondition(condition, column, NE, val);
}
測試編寫:
/**
* 測試 boolean condition
*/
@Test
public void abstractWrapperTest_Condition() {
Integer age = new Random().nextInt(25);
System.out.println("年齡: " + age);
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
boolean condition = age<=18;
userQueryWrapper.ne(condition,"name","Lynwood");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
}
生成的隨機數age 小於18歲的 不要讓他查到name="Lynwood"
的信息
再此看來等同於*mapper.xml
配置
WHERE 1=1
<if test="age <= 18">
AND name <> #{name}
</if>
幫我們提入到了每個方法中,並且省去我們關系 是否寫AND
關鍵字或者 必須有 WHERE 1=1
這些難看卻又不必要的配置
2.關於入參: R column
- 以下方法在入參中出現的
R
為泛型,在普通wrapper中是String
,在LambdaWrapper中是函數(例:Entity::getId
,Entity
為實體類,getId
為字段id
的getMethod)- 以下方法入參中的
R column
均表示數據庫字段,當R
為String
時則為數據庫字段名(字段名是數據庫關鍵字的自己用轉義符包裹!)!而不是實體類數據字段名!!!
查看源碼 發現實際上 eq()、ne()...
等需要傳入R column
的方法實際最后都是調用了addCondition(...)
方法
對 R column
參數做了 columnToString(column)
操作
而 AbstractWrapper
實際中的此方法是抽象的:
/**
* 獲取 columnName
*/
protected abstract String columnToString(R column);
而我們再去查看我們使用的 QueryWrapper
與UpdateWrapper
中的具體實現都為:
@Override
protected String columnToString(String column) {
return column;
}
實際上是轉換成了String
類型, 那么為什么使用了泛型呢?
我們再返回去查看他的另一個實現類AbstractLambdaWrapper
中的實現:
@Override
protected String columnToString(SFunction<T, ?> column) {
return getColumn(LambdaUtils.resolve(column));
}
原來這里使用泛型 為了擴展Lambda方式使用!
lambda
- 獲取
LambdaWrapper
在QueryWrapper
中是獲取LambdaQueryWrapper
在UpdateWrapper
中是獲取LambdaUpdateWrapper
測試:
/**
* 測試 R column
*/
@Test
public void abstractWrapperTest_Column() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
// userQueryWrapper.ne("name","Lynwood");
userQueryWrapper.lambda().eq(User::getName,"Lynwood");
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
}
3.關於入參: Function<This, This> func
其實再測試AbstractWrapper中的 or/and 嵌套中已經使用過了
以or()
舉例
需要傳入的是 Function
的實現 而Function
的定義是:
/**
* Represents a function that accepts one argument and produces a result.
* 表示接受一個參數執行生成結果的函數對象。
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
在我們使用過程中 往往是為了嵌套 我們傳入的可能是另一個Wrapper 讓他和當前的Wrapper去鏈接/嵌套起來
那么在:
userQueryWrapper.ne("name","Lynwood")
.or(i -> i.eq("age", "18"));
中 我們實際上傳入了一個匿名的Wrapper對象,那么這個匿名的Wrapper對象怎么符合了Function的要求呢
我們查看這個i
變量 :
他的類型是與調用對象一致的 查看 QueryWrapper<User>
的繼承關系:
實際上我們使用的所有接口都去間接繼承了ISqlSegment
接口 ,而它:
package com.baomidou.mybatisplus.core.conditions;
@FunctionalInterface
public interface ISqlSegment extends Serializable {
是被@FunctionalInterface 標識的 與:
package java.util.function;
@FunctionalInterface
public interface Function<T, R> {
是一樣的, 所以我們使用它可以這樣簡便的使用啦:
測試:
@Test
public void abstractWrapperTest_Func() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.ne("name","Lynwood");
userQueryWrapper.or(new Function<QueryWrapper<User>, QueryWrapper<User>>() {
@Override
public QueryWrapper<User> apply(QueryWrapper<User> userQueryWrapper) {
return userQueryWrapper.eq("age", "18");
}
});
// 等同與
// userQueryWrapper.ne("name","Lynwood")
// .or(i -> i.eq("age", "18"));
List<User> userList = userMapper.selectList(userQueryWrapper);
userList.forEach(System.out::println);
}
二> 編寫QueryWrapper與UpdateWrapper測試類:
其實在上面的所有測試中其返回值都是完全的裝配到了實體對象中,但是有的時候 比如有個字段很大 很消耗內存,同時也用不到,那么我如何把他取消掉.設置返回值為我們需要的幾個字段就好
select()
測試
注意坑 在借用 lambda()
使用 lambda特性時!
/**
* 測試 QueryWrapper.select()
*
*/
@Test
public void abstractWrapperTest_Select() {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
// userQueryWrapper.eq("age",18)
// .select("id","name");
userQueryWrapper.eq("age",18)
.select(User.class,i -> false);//只返回id
List<User> userList = userMapper.selectList(userQueryWrapper);
// SELECT id FROM user WHERE age = ?
userList.forEach(System.out::println);
//前方有坑 注意: 注意!!!!!!!!!!!
//關於LambdaQueryWrapper的使用這樣用是沒問題的
LambdaQueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<User>().lambda();
lambdaQueryWrapper.select(User::getId,User::getAge);
List<User> userList1 = userMapper.selectList(lambdaQueryWrapper);
//SELECT id,age FROM user
userList1.forEach(System.out::println);
// 這樣設置是無效的!!!!
QueryWrapper<User> userQueryWrapper2 = new QueryWrapper<>();
userQueryWrapper2.lambda().select(User::getId,User::getAge);
List<User> userList2 = userMapper.selectList(userQueryWrapper2);
// SELECT id,name,age,email FROM user
userList2.forEach(System.out::println);
}
set() setSql()
測試
/**
* 測試 UpdateWrapper.set()
* 測試 UpdateWrapper.setSql()
*/
@Test
public void abstractWrapperTest_Set() {
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
// 更新id==11 的age 為19
userUpdateWrapper.set("age",19).eq("id",11);
// 附加更新內容 name 設置
User user = new User();
user.setName("Lynwood1");
userMapper.update(user,userUpdateWrapper);
/**
* ==> Preparing: UPDATE user SET name=?, age=? WHERE id = ?
* ==> Parameters: Lynwood1(String), 19(Integer), 11(Integer)
* <== Updates: 1
*/
UpdateWrapper<User> userUpdateWrapper1 = new UpdateWrapper<>();
userUpdateWrapper1.setSql("name='wunian7yulian'").eq("id",11);
// 附加更新內容為空時 不能傳入null 需要傳入空實體對象
userMapper.update(new User(),userUpdateWrapper1);
/**
* ==> Preparing: UPDATE user SET name='wunian7yulian' WHERE id = ?
* ==> Parameters: 11(Integer)
* <== Updates: 1
*/
}
六、插件及擴展
Demo代碼地址:
https://github.com/wunian7yulian/MybatisPlusDemo/tree/master/mp_plugin_demo
初始化:
因為下面演示需要其他字段,所以重新創建一個便於Demo的合適的表格:
-- 創建簡單表格
DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT COMMENT '主鍵ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年齡',
`email` varchar(50) DEFAULT NULL COMMENT '郵箱'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user ( name, age, email) VALUES
('Jone', 18, 'test1@test.com'),
('Jack', 20, 'test2@test.com'),
('Tom', 28, 'test3@test.com'),
('Sandy', 21, 'test4@test.com'),
('Billie', 24, 'test5@test.com'),
('Lynwood', 18, 'wunian_@hotmail.com');
工程:
第一 -->第四步、同上
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf-8
username: root
password: 123456
mybatis-plus:
global-config:
db-config:
id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
第五步、插件使用
分頁插件:
之前做列表或者報表功能時使用最多的是pageHelper
插件做Mybatis的分頁查詢,MP為了方便將分頁做了一個相關的模塊com.baomidou.mybatisplus.extension.plugins.pagination
供我們分頁查詢時去使用.
1、創建一個MP插件的配置類:
在business目錄下創建config目錄 並在該目錄下創建MybatisPlusConfig.java
添加@Configuration
注解聲明
@Configuration
public class MybatisPlusConfig {
}
2、配置分頁插件
@Configuration
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
3、編寫測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class MpPluginDemoApplicationTests {
@Autowired
private UserMapper userMapper;
/**
* 測試分頁插件
*/
@Test
public void test_page() {
IPage<User> userIPage = userMapper.selectPage(
new Page<User>()
.setCurrent(1) //設置當前查詢頁
.setSize(3) // 設置每頁條數
.setDesc("age"),//使用page 進行排序
new QueryWrapper<User>()
.lambda()
.likeRight(User::getEmail, "test")
.select(User::getId, User::getName, User::getAge)
);
List<User> records = userIPage.getRecords();
records.forEach(System.out::println);
/**
* ==> Preparing: SELECT id,name,age FROM user WHERE email LIKE ? ORDER BY age DESC LIMIT ?,?
* ==> Parameters: test%(String), 0(Long), 3(Long)
*/
}
}
完美! 不過因為page
對象在3.0.6
版本還沒有支持lambda方式 page上在設置排序字段時 還是會有一些魔法值出現,不過我們也是可以直接將 排序 寫在wrapper里的. 有強迫症的童鞋等待3.1.0
版本吧.
邏輯刪除插件:
1、yaml添加邏輯值制定配置:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mp_demo_db?characterEncoding=utf-8
username: root
password: 123456
mybatis-plus:
global-config:
db-config:
id-type: auto
logic-delete-value: 1 # 邏輯已刪除值(默認為 1)
logic-not-delete-value: 0 # 邏輯未刪除值(默認為 0)
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2、配置邏輯插件:
在MybatisPlusConfig中添加插件bean
@Configuration
public class MybatisPlusConfig {
...
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
3、標識邏輯字段:
在User
中的 del字段上添加@TableLogic
注解
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
...
/**
* 邏輯刪除 0-未刪除 1-刪除
*/
@TableLogic
private Integer del;
...
}
4、編寫測試
/**
* 測試邏輯刪除插件
*/
@Test
public void test_logic_delete() {
userMapper.delete(new QueryWrapper<User>()
.lambda()
.notLike(User::getEmail, "test")
.select(User::getId, User::getName, User::getAge));
/**
* ==> Preparing: UPDATE user SET del=1 WHERE del=0 AND email NOT LIKE ?
* ==> Parameters: %test%(String)
*/
//那么查詢呢?
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
//發現查詢也幫我們設置了過濾字段 del = 0
/**
* ==> Preparing: SELECT id,name,age,email,del,version FROM user WHERE del=0
* ==> Parameters:
*/
//那么我想查出這個刪除的用戶 更改為 正常呢?
List<Map<String, Object>> mapList = userMapper.selectMaps(
new QueryWrapper<User>()
.lambda()
.eq(User::getDel, 1)
.select(User.class,i -> false)
);
Assert.assertNotNull(mapList);
Assert.assertEquals(mapList.size(),1);
/** select id from user where del = 0 and del = 1
* java.lang.AssertionError:
* Expected :0
* Actual :1
*/
// 失敗了
//
}
詢問MP開發者后 開發者說明 因為大部分情況在刪除之后不會恢復 所以沒有設定相關恢復或者跳過 當前 篩選的 接口
樂觀鎖:
主要適用場景
當要更新一條記錄的時候,希望這條記錄沒有被別人更新
特別說明:
- 支持的數據類型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整數類型下
newVersion = oldVersion + 1
newVersion
會回寫到entity
中- 僅支持
updateById(id)
與update(entity, wrapper)
方法 - 在 update(entity, wrapper) 方法下, wrapper 不能復用!!!
樂觀鎖實現方式:
-
取出記錄時,獲取當前version
-
更新時,帶上這個version
-
執行更新時, set version = newVersion where version = oldVersion
-
如果version不對,就更新失敗
1、配置樂觀鎖插件:
在MybatisPlusConfig
添加:
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
2、添加樂觀鎖版本字段注解:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
...
/**
* 樂觀鎖版本字段
*/
@Version
private Integer version;
}
3、編寫測試類:
/**
* 測試樂觀鎖插件
*/
@Test
public void test_Optimistic_Locking() throws InterruptedException {
User oldUser = userMapper.selectById(1);
oldUser.setAge(19);
System.out.println("模擬中途有人更改"+ "begin....");
Thread.sleep(10000);
System.out.println("模擬中途有人更改"+ "end....");
if(userMapper.updateById(oldUser)==1){
System.out.println("success \n"+ oldUser);
//success
//User(id=1, name=Jone, age=19, email=test1@test.com, del=0, version=2)
}else {
System.out.println("fail \n");
//==> Preparing: UPDATE user SET name=?, age=?, email=?, version=? WHERE id=? AND version=? AND del=0
//==> Parameters: Jone(String), 19(Integer), test1@test.com(String), 3(Integer), 1(Long), 2(Integer)
//<== Updates: 0
//fail
}
}
其他插件 如 熱加載等 如有需要 再做更新
七 、總結
MP優秀於簡化了Mybatis大部分XML配置 將他歸總到起來生成一個強大的Wrapper
現有MP也是有了兩個版本大的版本2.x與3.x ,酌情使用比較合理 3.x雖然有了Lambda的支持但是還不完善,
后續版本的更新應該也會去查漏現版本的缺陷.
對於插件,像樂觀鎖分頁邏輯刪真的是貼心,不過也有相對應場景不適用問題,比如上面說到的邏輯刪除的我還要查是查不到的,還是需要去自定義sql的
我相信 有了mp這種強大的利器,對於一個微型項目 或者微服務 來說真的是爽,寫起來真的是非常有效率
個人--
作為頭一個被我收拾的應用層框架,看到了開發者的用心和初心, 看來排上名並不是那么絕非偶然,也並非那么"功力深厚",只要你有想法開始做,總有一個好的結果.
下一框架尋找中...
參考文檔:
MyBatis-Plus 官方文檔 : https://mp.baomidou.com/
MyBatis-Plus 配置進階 : https://mp.baomidou.com/config/
MyBatis-Plus 代碼生成器配置 : https://mp.baomidou.com/config/generator-config.html
MyBatis-Plus sql注入原理 : https://www.liangzl.com/get-article-detail-19831.html
Mybatis-Plus 使用全解 : https://www.jianshu.com/p/7299cba2adec