Mybatis-plus 常用功能集成
本項目使用 使用 Spring Initializer 初始化的 Spring Boot 工程; 版本:2.3.1
MyBatis-Plus官方地址:MyBatis-Plus 版本:3.1.0
示例項目Github地址:scaffold-project
1、基本使用
-
添加maven依賴
-
mybatis-plus 基礎依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency>
-
添加 代碼生成器 依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.1.0</version> </dependency>
-
添加 模板引擎 依賴 (如果需要自定義代碼生成模板), MyBatis-Plus 支持 Velocity(默認)使用其他自定義模板引擎需要額外配置
<dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency>
-
-
在
application.yml
配置文件中添加基礎配置# 基礎 DataSource Config spring: datasource: druid: url: jdbc:mysql://localhost:3306/scaffold?useUnicode=true&characterEncoding=utf8&useSSL=false username: mysql password: mysql driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 更多配置 # ====================MybatisPlus Config==================== mybatis-plus: # 如果是放在src/main/java目錄下 classpath:/com/yourpackage/*/mapper/*Mapper.xml # 如果是放在resource目錄 classpath*:/mapper/*Mapper.xml mapper-locations: classpath:/com/jasper/scaffold/**/mapper/xml/*Mapper.xml #實體掃描,多個package用逗號或者分號分隔 typeAliasesPackage: com.jasper.scaffolds.*.entity global-config: key-generator: com.baomidou.mybatisplus.incrementer.OracleKeyGenerator #主鍵類型 0:"數據庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID"; id-type: 2 #字段策略 0:"忽略判斷",1:"非 NULL 判斷"),2:"非空判斷" field-strategy: 2 #駝峯下劃線轉換 db-column-underline: true db-config: #mp2.3+ 全局表前綴 mp_ #table-prefix: mp_ #刷新mapper 調試神器 #refresh-mapper: true #數據庫大寫下劃線轉換 #capital-mode: true # 邏輯刪除配置 logic-delete-field: mark # 全局邏輯刪除字段 實體類上有 @TableLogic 則以實體上的爲準,忽略全局 logic-delete-value: 1 logic-not-delete-value: 0 #自定義填充策略接口實現 #meta-object-handler: com.baomidou.springboot.MyMetaObjectHandler configuration: #配置返回數據庫(column下劃線命名&&返回java實體是駝峯命名),自動匹配無需as(沒開啓這個,SQL需要寫as: select user_id as userId) map-underscore-to-camel-case: true cache-enabled: false #配置JdbcTypeForNull, oracle數據庫必須配置 jdbc-type-for-null: 'null' #===================================================================================
-
啓動類中添加
@MapperScan
註解,掃描 Mapper 文件夾@SpringBootApplication @MapperScan("com.jasper.scaffold.*.mapper") public class ScaffoldApplication { public static void main(String[] args) { SpringApplication.run(ScaffoldApplication.class, args); } }
-
測試數據庫
scaffold
Table:User
-- 數據庫: scaffold DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL COMMENT '主鍵ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年齡', `email` varchar(50) DEFAULT NULL COMMENT '郵箱', `cTime` datetime DEFAULT NULL COMMENT '創建時間', `uTime` datetime DEFAULT NULL COMMENT '修改時間', `mark` tinyint(1) DEFAULT NULL COMMENT '數據有效標示位 1無效 0有效', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶表實體';
2、代碼生成器
-
通過
MyBatis-Plus
的代碼生成器AutoGenerator
,通過數據庫表 直接快速生成tity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼 -
單獨創建
MpGenerator
生成類,執行 main 方法生成指定 table 對應的模塊代碼 -
更多屬性及配置請查看:更多配置
// 自定義代碼生成類 demo public class MpGenerator { public static void main(String[] args) { //fixme 生成的java文件路徑 String javaPath = "/xxx/git-hub/Jasper/scaffold/src/main/java"; //fixme 包名 String packgeName = "com.jasper.scaffold.api"; //fixme mysql數據庫配置 String dbusername = "mysql"; String dbpassword = "mysql"; String dbip = "localhost"; String dbname = "scaffold"; // 各種配置 AutoGenerator mpg = new AutoGenerator(); // 設置數據源 mpg.setDataSource(new DataSourceConfig() .setDriverName("com.mysql.jdbc.Driver") // 設置數據庫類型 .setDbType(DbType.MYSQL) .setUsername(dbusername) .setPassword(dbpassword) .setUrl(String.format("jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull", dbip, dbname)) ); // 全局配置 mpg.setGlobalConfig(new GlobalConfig() // 輸出目錄 .setOutputDir(javaPath) // 是否覆蓋 .setFileOverride(true) // 開啓AR模式 .setActiveRecord(true) // XML二級緩存 .setEnableCache(false) // 生成ResultMap .setBaseResultMap(true) // 生成 sql片段 .setBaseColumnList(true) // 自動打開生成後的文件夾 .setOpen(true) // 所有文件的生成者 .setAuthor("codegenerator") .setDateType(DateType.ONLY_DATE) // 自定義文件命名,%s會自動填充表實體類名字 .setMapperName("%sMapper") .setXmlName("%sMapper") .setServiceName("I%sService") .setServiceImplName("%sServiceImpl") .setControllerName("%sController") ); // 策略配置 mpg.setStrategy(new StrategyConfig() // fixme 添加需要生成的表 .setInclude("user") // 實體類使用Lombok .setEntityLombokModel(true) // 表名生成策略,下劃線轉駝峯 .setNaming(NamingStrategy.underline_to_camel) .setRestControllerStyle(true) .setSuperControllerClass("com.jasper.scaffold.common.mvc.BaseController") .setLogicDeleteFieldName("mark") ); // 包配置 mpg.setPackageInfo(new PackageConfig() // 基本包路徑 .setParent(packgeName) // 設置Controller包名 .setController("web") // 設置entity包名 .setEntity("entity") // 設置Mapper包名 .setMapper("mapper") // 設置Service包名 .setService("service") // 設置Service實現類包名 .setServiceImpl("service.impl") // 設置Mapper.xml包名 .setXml("mapper.xml") ); // 如果需要定製 代碼生成模板 可以 自己寫代碼模板 如: 自定義 VO模板 List<FileOutConfig> list = new ArrayList<>(); list.add(new FileOutConfig("/templates/codegenerator/vo.java.vm") { // 自定義vo輸出路徑 @Override public String outputFile(TableInfo tableInfo) { String voPath = javaPath.concat("/").concat(packgeName.replaceAll("[.]", "/")) .concat("/").concat("vo").concat("/") .concat(tableInfo.getEntityName()).concat("VO.java"); log.info("voPath:{}", voPath); return voPath; } }); // list.add(new FileOutConfig("/templates/mapper.xml.vm") { // // 自定義Mapper.xml輸出路徑 // @Override // public String outputFile(TableInfo tableInfo) { // return "/Users/liqingchao/work/xingyun_pmp/project_be/project_be/proj-server/src/main/java/com/jd/jacp/proj/project/mapper/xml/" + tableInfo.getEntityName() + "Mapper.xml"; // } // }); // 注入自定義配置 mpg.setCfg(new InjectionConfig() { @Override public void initMap() { // 注入自定義 Map 對象(注意需要setMap放進去) Map<String, Object> map = new HashMap<>(1); map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp"); this.getConfig().getPackageInfo().put("Vo", mpg.getPackageInfo().getParent() + ".vo"); this.setMap(map); } }.setFileOutConfigList(list)); mpg.execute(); } }
3、CRUD接口
-
Mapper層
CRUD接口簡單示例: 更多接口列表詳情- 通用 CRUD 封裝BaseMapper接口,爲
Mybatis-Plus
啓動時自動解析實體表關係映射轉換爲Mybatis
內部對象注入容器 - 泛型
T
爲任意實體對象 - 參數
Serializable
爲任意類型主鍵Mybatis-Plus
不推薦使用複合主鍵約定每一張表都有自己的唯一id
主鍵 - 對象
Wrapper
爲 條件構造器 - 方法名均爲這四種關鍵字:
Insert
Delete
Update
Select
// 插入一條記錄 int insert(T entity); // 根據 entity 條件,刪除記錄 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 根據 ID 修改 int updateById(@Param(Constants.ENTITY) T entity); // 根據 ID 查詢 T selectById(Serializable id); // 根據 entity 條件,查詢一條記錄 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
- 通用 CRUD 封裝BaseMapper接口,爲
-
Service層
CRUD接口簡單示例:說明:
-
基礎接口列表詳情參見:
-
鏈式查詢
測試// 鏈式查詢 普通 QueryChainWrapper<T> query(); // 鏈式查詢 lambda 式。注意:不支持 Kotlin LambdaQueryChainWrapper<T> lambdaQuery();
@Test void testChainQuery() { // 鏈式查詢 普通 User user = userService.query().eq("name", "jasper").one(); // 鏈式查詢 lambda 式 User user2 = userService.lambdaQuery().eq(User::getName, "jasper").one(); System.out.println(user); System.out.println(user2); }
-
鏈式更新
測試// 鏈式更改 普通 UpdateChainWrapper<T> update(); // 鏈式更改 lambda 式。注意:不支持 Kotlin LambdaUpdateChainWrapper<T> lambdaUpdate();
@Test void testChainUpdate() { // 鏈式更改 普通 String queryVal = "zhangsan"; userService.update().eq("name", queryVal).remove(); // 鏈式更改 lambda 式 User updateEntity = new User().setEmail("[email protected]"); userService.lambdaUpdate().eq(User::getName, "lisi").update(updateEntity); }
-
4、分頁插件
-
在配置類中 開啓分頁插件, 支持額外的分頁配置,如:最大單頁數量限制
@Configuration @EnableTransactionManagement public class MybatisPlusConfig { /** * 分頁插件 * @return */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
-
測試分頁查詢
@Test void testPagination() { Page<User> page = new Page<>(1, 5); page.setDesc("age"); IPage<User> userPage = userMapper.selectPage(page, Wrappers.<User>query().gt("age", 5)); log.error("總條數 -------------> {}", userPage.getTotal()); log.error("當前頁數 -------------> {}", userPage.getCurrent()); log.error("當前每頁顯示數 -------------> {}", userPage.getSize()); List<User> records = userPage.getRecords(); log.error("records: {}", records); }
-
測試
lambda
分頁查詢@Test void lambdaPagination() { Page<User> page = new Page<>(1, 3); IPage<User> result = userMapper.selectPage(page, Wrappers.<User>lambdaQuery().ge(User::getAge, 1).orderByAsc(User::getAge)); log.error("總條數 -------------> {}", result.getTotal()); log.error("當前頁數 -------------> {}", result.getCurrent()); log.error("當前每頁顯示數 -------------> {}", result.getSize()); List<User> records = result.getRecords(); log.error("records: {}", records); }
5、邏輯刪除
mybatis-plus
支持數據的邏輯刪除,當數據庫記錄設計是通過 某個字段來標示 數據是否有效或者是否已刪除,而不是真正的物理刪除時, 可以通過配置,將mybatis-plus
默認的刪除
變成邏輯刪除;並且查詢
方法也會默認帶上邏輯刪除標示,即只查詢 未刪除的數據 (自己擴展的xml不會),當有需求 就是需要查詢邏輯刪除的數據時,可手寫查詢 (既然設計了邏輯刪除,一般不會查詢已刪除的數據)
-
application.yml
加入配置mybatis-plus: global-config: db-config: logic-delete-field: flag #全局邏輯刪除字段值3.3.0開始支持 flag可以修改爲自己的 邏輯標示字段 logic-delete-value: 1 # 邏輯已刪除值(默認爲 1) logic-not-delete-value: 0 # 邏輯未刪除值(默認爲 0)
-
實體類字段上加上
@TableLogic
註解/** * 數據有效標示位 0有效 1無效 */ @TableLogic private Boolean mark;
Tip:
- 字段支持所有數據類型(推薦使用
Integer
,Boolean
,LocalDateTime
) - 如果使用
LocalDateTime
,建議邏輯未刪除值設置爲字符串null
,邏輯刪除值只支持數據庫函數例如now()
- 使用mp自帶方法刪除和查找都會附帶邏輯刪除功能 (自己寫的xml不會)
- 如果實體類上有 @TableLogic 則以實體上的爲準,忽略全局。 即先查找註解再查找全局,都沒有則此表沒有邏輯刪除
- 字段支持所有數據類型(推薦使用
6、通用枚舉
在一些情況下,我們希望實體的字段是固定的幾個值,比如:User用戶表
的性別(Gender)
,我們希望是固定的Male
,Female
我們可能在設計數據庫字段時,採用了int(1)
:
- 1: 男
- 2: 女
如果實體使用了Integer
,那麼無法保證新增時傳入的參數是1/2;此種場景就可以用通用枚舉來處理
-
在
application.yml
中添加 配置枚舉類掃描路徑mybatis-plus: # 支持統配符 * 或者 ; 分割 type-enums-package: com.jasper.scaffold.api.entity.enums ...
-
先定義一個性別的枚舉類
@Getter public enum GenderEnum { MALE(1, "男"), FEMALE(2, "女"); private final int code; // 數據庫中 存儲的爲 code private final String descp; GenderEnum(int code, String descp){ this.code = code; this.descp = descp; } }
-
定義用戶實體時,使用枚舉類型定義 性別
/** * 性別 */ private GenderEnum gender; // 省略其他。。
-
添加枚舉配置
-
方式1: 使用
@EnumValue
註解枚舉屬性@EnumValue //標記數據庫存的值是code private final int code;
-
方式2:枚舉屬性,實現
IEnum 接口
public enum GenderEnum implements IEnum<Integer>{ MALE(1, "男"), FEMALE(2, "女"); private final int code; // 數據庫中 存儲的爲 code private final String descp; @Override public Integer getValue() { return this.code; } GenderEnum(int code, String descp){ this.code = code; this.descp = descp; } }
-
-
測試枚舉使用
@Test void testEnum() { User user = new User() .setGender(GenderEnum.MALE) .setAge(12) .setEmail("[email protected]") .setName("Name_y") .setMark(false); // false 未刪除 // 插入 userMapper.insert(user); // 查詢 List<User> userList = userService.lambdaQuery().eq(User::getGender, GenderEnum.MALE).list(); log.error("用戶數:{}", userList.size()); } // 用戶數:2
7、自動填充功能
當我們在插入、修改數據庫記錄時,想要填充某個字段的值時:比如 User
表的 cTime
、uTime
字段,當新增數據時,自動設置 cTime
、uTime
爲當前時間,當更新修改數據時,自動修改uTime
爲當前時間,這個時候就可以使用自動填充功能
-
實現元對象處理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
@Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { log.debug("start insert fill ...."); // 起始版本 3.3.0(推薦使用) // this.strictInsertFill(metaObject, "cTime", Date.class, new Date()); this.setFieldValByName("cTime", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { log.debug("start update fill ...."); // 起始版本 3.3.0(推薦使用) // this.strictUpdateFill(metaObject, "uTime", Date.class, new Date()); this.setFieldValByName("uTime", new Date(), metaObject); } }
-
註解填充字段
@TableField(.. fill = FieldFill.INSERT)
/** * 創建時間 */ @TableField(value = "cTime", fill = FieldFill.INSERT) private Date cTime; /** * 修改時間 */ @TableField(value = "uTime", fill = FieldFill.UPDATE) private Date uTime;
Tip:
-
字段必須聲明
TableField
註解,屬性fill
選擇對應策略,該聲明告知Mybatis-Plus
需要預留注入SQL
字段 -
填充處理器
MyMetaObjectHandler
在 Spring Boot 中需要聲明@Component
或@Bean
注入 -
要想根據註解
FieldFill.xxx
和字段名
以及字段類型
來區分必須使用父類的strictInsertFill
或者strictUpdateFill
方法 -
不需要根據任何來區分可以使用父類的
fillStrategy
方法 -
字段類型是
LocalDateTime
時, 集成druid數據源,使用3.1.0之前版本沒問題,升級mp到3.1.1+後,運行時報錯:java.sql.SQLFeatureNotSupportedException
-
8、SQL性能分析
可以通過自帶的SQL性能分析插件,自動打印出執行的SQL以及執行時間,方便排查問題以及優化性能較慢的SQL,可以設置Profile
只在 dev、test
環境下啓動
-
在
MybatisPlusConfig
啓動SQL性能分析插件/** * SQL 性能分析插件 * @return */ @Bean @Profile({"dev", "test"}) public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); }
-
自動分析執行SQL
Time:39 ms - ID:com.jasper.scaffold.api.mapper.UserMapper.selectList Execute SQL:SELECT id,name,gender,age,email,cTime,uTime,mark FROM user WHERE gender = 1
9、多數據源
dynamic-datasource-spring-boot-starter
是一個基於springboot的快速集成多數據源的啓動器,可實現快速切換多個數據源: 詳情
-
引入Maven包
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>${version}</version> </dependency>
-
配置多個數據源
spring: datasource: dynamic: primary: master #設置默認的數據源或者數據源組,默認值即爲master strict: false #設置嚴格模式,默認false不啓動. 啓動後在未匹配到指定數據源時候回拋出異常,不啓動會使用默認數據源. datasource: master: url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave_1: url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave_2: url: ENC(xxxxx) # 內置加密,使用請查看詳細文檔 username: ENC(xxxxx) password: ENC(xxxxx) driver-class-name: com.mysql.jdbc.Driver schema: db/schema.sql # 配置則生效,自動初始化表結構 data: db/data.sql # 配置則生效,自動初始化數據 continue-on-error: true # 默認true,初始化失敗是否繼續 separator: ";" # sql默認分號分隔符 #......省略 #以上會配置一個默認庫master,一個組slave下有兩個子庫slave_1,slave_2
-
使用 @DS 切換數據源
@DS 可以註解在方法上和類上,同時存在方法註解優先於類上註解。建議只註解在service實現上
-
示例
@Service @DS("slave") public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; public List<Map<String, Object>> selectAll() { return jdbcTemplate.queryForList("select * from user"); } @Override @DS("slave_1") public List<Map<String, Object>> selectByCondition() { return jdbcTemplate.queryForList("select * from user where age >10"); } }