Mybatis-Plus 详解
生活随笔
收集整理的這篇文章主要介紹了
Mybatis-Plus 详解
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1. Mybatis-Plus概念
1.1 Mybatis-Plus介紹
官?: https://mybatis.plus/ 或 https://mp.baomidou.com/ Mybatis-Plus 介紹 MyBatis-Plus (簡稱 MP )是?個 MyBatis 的增強?具,在 MyBatis 的基礎上只做增強不做改變,-為簡化開發、提?效率??。 愿景 我們的愿景是成為 MyBatis 最好的搭檔,就像 魂?羅 中的 1P 、 2P ,基友搭配,效率翻倍。1.2 特性
- ?侵?:只做增強不做改變,引?它不會對現有?程產?影響,如絲般順滑
- 損耗?:啟動即會?動注?基本 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 架構
1.4 作者
Mybatis-Plus 是由 baomidou (苞??)組織開發并且開源的,?前該組織?概有 30 ?左右。 碼云地址: https://gitee.com/organizations/baomidou2. Mybatis-Plus快速??
2.1 安裝
全新的 MyBatis-Plus 3.0 版本基于 JDK8 ,提供了 lambda 形式的調?,所以安裝集成 MP3.0 要求 如下:- JDK 8+
- Maven or Gradle
2.2 創建數據庫以及表
創建 User 表,其表結構如下: -- 創建測試表 DROP TABLE IF EXISTS tb_user; CREATE TABLE user (id BIGINT(20) NOT NULL COMMENT '主鍵ID',name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',age INT(11) NULL DEFAULT NULL COMMENT '年齡',email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',PRIMARY KEY (id) ); -- 插?測試數據 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');2.3 創建?程
?導?依賴:
<dependencies><!-- mybatis-plus插件依賴 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId><version>3.1.1</version></dependency><!--Mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><!--連接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.11</version></dependency><!--簡化bean代碼的?具包--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.6.4</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins></build>2.4 Mybatis + MP
下?演示,通過純 Mybatis 與 Mybatis-Plus 整合。 創建? Module <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>lagou-mybatis-plus</artifactId><groupId>com.lagou.mp</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>lagou-mybatis-plus-simple</artifactId> </project> log4j.properties : log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n Mybatis 實現查詢 User 第?步,編寫 mybatis-config.xml ?件 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><properties resource="jdbc.properties"></properties><!--environments: 運?環境--><environments default="development"><environment id="development"><!--當前的事務事務管理器是JDBC--><transactionManager type="JDBC"></transactionManager><!--數據源信息 POOLED:使?mybatis的連接池--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--引?映射配置?件--><mappers><mapper resource="mapper/UserMapper.xml"></mapper></mappers> </configuration> 第?步,編寫 User 實體對象:(這?使? lombok 進?了進化 bean 操作) @Data // getter setter @toString @NoArgsConstructor @AllArgsConstructor public class User {private Long id;private String name;private Integer age;private String email; } 第三步,編寫 UserMapper 接?: public interface UserMapper {List<User> findAll(); } 第四步,編寫 UserMapper.xml ?件: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lagou.mapper.UserMapper"><!-- 查詢所有 --><select id="findAll" resultType="com.lagou.pojo.User">select * from user</select> </mapper> 第五步,編寫 TestMybatis 測試?例: public class MPTest {@Testpublic void test1() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> all = mapper.findAll();for (User user : all) {System.out.println(user);}} } 測試結果: 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=Billie, age=24, email=test5@baomidou.com) 注:如果實體類名稱和表名稱不?致,可以在實體類上添加注解 @TableName(" 指定數據庫表名 ") Mybatis+MP 實現查詢 User 第?步,將 UserMapper 繼承 BaseMapper ,將擁有了 BaseMapper 中的所有?法: import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lagou.pojo.User; public interface UserMapper extends BaseMapper<User> { } 第?步,使? MP 中的 MybatisSqlSessionFactoryBuilder 進程構建: @Test public void test2() throws IOException {InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");//這?使?的是MP中的MybatisSqlSessionFactoryBuilderSqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(resourceAsStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);// 可以調?BaseMapper中定義的?法List<User> all = mapper.selectList(null);for (User user : all) {System.out.println(user);} } 測試: 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=Billie, age=24, email=test5@baomidou.com) 注:如果實體類名稱和表名稱不?致,可以在實體類上添加注解 @TableName(" 指定數據庫表名 ") 簡單說明:- 由于使?了 MybatisSqlSessionFactoryBuilder進?了構建,繼承的BaseMapper中的?法就載?到了SqlSession中,所以就可以直接使?相關的?法;
- 如圖
2.5 Spring + Mybatis + MP
引?了 Spring 框架,數據源、構建等?作就交給了 Spring 管理。 創建? Module <?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>lagou-mybatis-plus</artifactId><groupId>com.lagou.mp</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>lagou-mybatis-plus-spring</artifactId><properties><spring.version>5.1.6.RELEASE</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency></dependencies> 實現查詢 User 第?步,編寫 jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?serverTimezone=GMT%2B8&useSSL=false jdbc.username=root jdbc.password=root 第?步,編寫 applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--引?properties--><context:property-placeholder location="classpath:jdbc.properties"/><!--dataSource--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><!--這?使?MP提供的sqlSessionFactory,完成spring與mp的整合--><bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean" ><property name="dataSource" ref="dataSource"/></bean><!--掃描mapper接?,使?的依然是mybatis原?的掃描器--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.lagou.mapper"/></bean> </beans> 第三步,編寫 User 對象以及 UserMapper 接?: @Data @NoArgsConstructor @AllArgsConstructor public class User {private Long id;private String name;private Integer age;private String email; } public interface UserMapper extends BaseMapper<User> {List<User> findAll(); } 第四步,編寫測試?例: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class TestSpringMP {@Autowiredprivate UserMapper userMapper;@Testpublic void test2() throws IOException {List<User> users = this.userMapper.selectList(null);for (User user : users) {System.out.println(user);}}2.6 SpringBoot + Mybatis + MP
使? SpringBoot 將進?步的簡化 MP 的整合,需要注意的是,由于使? SpringBoot 需要繼承 parent ,所以需要重新創建?程,并不是創建?Module 。 創建?程 導?依賴 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions></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><optional>true</optional></dependency><!--mybatis-plus的springboot?持--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.1</version></dependency><!--mysql驅動--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build> log4j.properties : log4j.rootLogger=DEBUG,A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n編寫application.properties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=tr ue&useSSL=false spring.datasource.username=root spring.datasource.password=root編寫pojo
@Data @NoArgsConstructor @AllArgsConstructor public class User {private Long id;private String name;private Integer age;private String email; } 編寫 mapper public interface UserMapper extends BaseMapper<User> { } 編寫啟動類 package com.lagou.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @MapperScan("com.lagou.mp.mapper") //設置mapper接?的掃描包 @SpringBootApplication public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);} }編寫測試?例
package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelect() {List<User> userList = userMapper.selectList(null);for (User user : userList) {System.out.println(user);}} } 測試: 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=Billie, age=24, email=test5@baomidou.com)3. 通?CRUD
通過前?的學習,我們了解到通過繼承 BaseMapper 就可以獲取到各種各樣的單表操作,接下來我們將詳細講解這些操作。3.1 插?操作
?法定義 /*** 插??條記錄** @param entity 實體對象.*/int insert(T entity); 測試?例 @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testInsert(){User user = new User();user.setAge(18);user.setEmail("test@lagou.cn");user.setName("?慕");//返回的result是受影響的?數,并不是?增后的idint result = userMapper.insert(user);System.out.println(result);System.out.println(user.getId());} } 測試 1 1318744682116739074 可以看到,數據已經寫?到了數據庫,但是, id 的值不正確,我們期望的是數據庫?增?,實際是 MP ? 成了 id 的值寫?到了數據庫。 如何設置 id 的?成策略呢? MP ?持的 id 策略: package com.baomidou.mybatisplus.annotation; import lombok.Getter; /** * ?成ID類型枚舉類 * * @author hubin * @since 2015-11-10 */ @Getter public enum IdType {/*** 數據庫ID?增*/AUTO(0),/*** 該類型為未設置主鍵類型*/NONE(1),/*** ?戶輸?ID* <p>該類型可以通過??注冊?動填充插件進?填充</p>*/INPUT(2),/* 以下3種類型、只有當插?對象ID 為空,才?動填充。 *//*** 全局唯?ID (idWorker)*/ID_WORKER(3),/*** 全局唯?ID (UUID)*/UUID(4),/*** 字符串全局唯?ID (idWorker 的字符串表示)*/ID_WORKER_STR(5);private final int key;IdType(int key) {this.key = key;} } 修改 User 對象: package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor @TableName("tb_user") public class User {@TableId(type = IdType.AUTO) //指定id類型為?增?private Long id;private String userName;private String password;private String name;private Integer age;private String email; } 數據插?成功: @TableField 在 MP 中通過 @TableField 注解可以指定字段的?些屬性,常常解決的問題有 2 個: 1 、對象中的屬性名和字段名不?致的問題(?駝峰) 2 、對象中的屬性字段在表中不存在的問題 使?:?其他?法,如?字段不加?查詢字段:
?效果:
3.2 更新操作
在 MP 中,更新操作有 2 種,?種是根據 id 更新,另?種是根據條件更新。 根據 id 更新 ?法定義: /*** 根據 ID 修改** @param entity 實體對象*/int updateById(@Param(Constants.ENTITY) T entity); 測試: @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testUpdateById() {User user = new User();user.setId(6L); //主鍵user.setAge(21); //更新的字段//根據id更新,更新不為null的字段this.userMapper.updateById(user);} } 結果: 根據條件更新 ?法定義: /*** 根據 whereEntity 條件,更新記錄** @param entity 實體對象 (set 條件值,可以為 null)* @param updateWrapper 實體對象封裝操作類(可以為 null,??的 entity ?于?成 where 語句)*/int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import net.minidev.json.writer.UpdaterMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testUpdate() {User user = new User();user.setAge(22); //更新的字段//更新的條件QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("id", 6);//執?更新操作int result = this.userMapper.update(user, wrapper);System.out.println("result = " + result);} } 或者,通過 UpdateWrapper 進?更新: @Testpublic void testUpdate() {//更新的條件以及字段UpdateWrapper<User> wrapper = new UpdateWrapper<>();wrapper.eq("id", 6).set("age", 23);//執?更新操作int result = this.userMapper.update(null, wrapper);System.out.println("result = " + result);} 測試結果: [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] == > Preparing: UPDATE tb_user SET age = ? WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] == > Parameters: 23 (Integer), 6 (Integer) [main] [com.lagou.mp.mapper.UserMapper.update]-[DEBUG] < == Updates: 1 均可達到更新的效果。 關于 wrapper 更多的?法后?會詳細講解。3.3 刪除操作
deleteById ?法定義: /** * 根據 ID 刪除 * * @param id 主鍵ID */ int deleteById(Serializable id); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testDeleteById() {//執?刪除操作int result = this.userMapper.deleteById(6L);System.out.println("result = " + result);} }結果:
[main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] == > Preparing: DELETE FROM tb_user WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] == > Parameters: 6 (Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] < == Updates: 1 數據被刪除。 deleteByMap ?法定義: /*** 根據 columnMap 條件,刪除記錄** @param columnMap 表字段 map 對象*/int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testDeleteByMap() {Map<String, Object> columnMap = new HashMap<>();columnMap.put("age",21);columnMap.put("name","?慕");//將columnMap中的元素設置為刪除的條件,多個之間為and關系int result = this.userMapper.deleteByMap(columnMap);System.out.println("result = " + result);} }結果:
[main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] == > Preparing: DELETE FROM tb_user WHERE name = ? AND age = ? [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] == > Parameters: ? 慕 (String), 21 (Integer) [main] [com.lagou.mp.mapper.UserMapper.deleteByMap]-[DEBUG] < == Updates: 0 delete ?法定義: /** * 根據 entity 條件,刪除記錄 * * @param wrapper 實體對象封裝操作類(可以為 null) */ int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; 結果: 3.3.4、deleteBatchIds ?法定義: import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testDeleteByMap() {User user = new User();user.setAge(20);user.setName("?慕");//將實體對象進?包裝,包裝為操作條件QueryWrapper<User> wrapper = new QueryWrapper<>(user);int result = this.userMapper.delete(wrapper);System.out.println("result = " + result);} }結果:
[main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] == > Preparing: DELETE FROM tb_user WHERE name = ? AND age = ? [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] == > Parameters: ?慕 (String), 20 (Integer) [main] [com.lagou.mp.mapper.UserMapper.delete]-[DEBUG] < == Updates: 03.3.4、deleteBatchIds
?法定義: /*** 刪除(根據ID 批量刪除)** @param idList 主鍵ID列表(不能為 null 以及 empty)*/int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testDeleteByMap() {//根據id集合批量刪除int result = this.userMapper.deleteBatchIds(Arrays.asList(1L,10L,20L));System.out.println("result = " + result);} } 結果: [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] == > Preparing: DELETE FROM tb_user WHERE id IN ( ? , ? , ? ) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] == > Parameters: 1 (Long), 10 (Long), 20 (Long) [main] [com.lagou.mp.mapper.UserMapper.deleteBatchIds]-[DEBUG] < == Updates: 13.4 查詢操作
MP 提供了多種查詢操作,包括根據 id 查詢、批量查詢、查詢單條數據、查詢列表、分?查詢等操作。3.4.1、selectById
?法定義: /*** 根據 ID 查詢** @param id 主鍵ID*/T selectById(Serializable id); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectById() {//根據id查詢數據User user = this.userMapper.selectById(2L);System.out.println("result = " + user);} } 結果: result = User (id = 2 , name = Jack, age = 20 , email = test2@baomidou.com)3.4.2、selectBatchIds
?法定義: /** * 查詢(根據ID 批量查詢) * * @param idList 主鍵ID列表(不能為 null 以及 empty) */ List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectBatchIds() {//根據id集合批量查詢List<User> users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L));for (User user : users) {System.out.println(user);}} } 結果: User (id = 2 , name = Jack, age = 20 , email = test2@baomidou.com) User (id = 3 , name = Tom, age = 28 , email = test3@baomidou.com)3.4.3、selectOne
?法定義: /** * 根據 entity 條件,查詢?條記錄 * * @param queryWrapper 實體對象封裝操作類(可以為 null) */ T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectOne() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.eq("name", "jack");//根據條件查詢?條數據,如果結果超過?條會報錯User user = this.userMapper.selectOne(wrapper);System.out.println(user);} } 結果: User (id = 2 , name = Jack, age = 20 , email = test2@baomidou.com)3.4.4、selectCount
?法定義: /** * 根據 Wrapper 條件,查詢總記錄數 * * @param queryWrapper 實體對象封裝操作類(可以為 null) */ Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectCount() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據條數Integer count = this.userMapper.selectCount(wrapper);System.out.println("count = " + count);} } 結果: count = 23.4.5、selectList
?法定義: /** * 根據 entity 條件,查詢全部記錄 * * @param queryWrapper 實體對象封裝操作類(可以為 null) */ List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectList() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據List<User> users = this.userMapper.selectList(wrapper);for (User user : users) {System.out.println("user = " + user);}} } 結果: user = User (id = 3 , name = Tom, age = 28 , email = test3@baomidou.com) user = User (id = 5 , name = Billie, age = 24 , email = test5@baomidou.com)3.4.6、selectPage
?法定義: /** * 根據 entity 條件,查詢全部記錄(并翻?) * * @param page 分?查詢條件(可以為 RowBounds.DEFAULT) * @param queryWrapper 實體對象封裝操作類(可以為 null) */ IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); 配置分?插件: package com.lagou.mp; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration @MapperScan("com.lagou.mp.mapper") //設置mapper接?的掃描包 public class MybatisPlusConfig {/*** 分?插件*/@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();} } 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectPage() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 20); //年齡?于20歲Page<User> page = new Page<>(1,1);//根據條件查詢數據IPage<User> iPage = this.userMapper.selectPage(page, wrapper);System.out.println("數據總條數:" + iPage.getTotal());System.out.println("總?數:" + iPage.getPages());List<User> users = iPage.getRecords();for (User user : users) {System.out.println("user = " + user);}} }結果:
數據總條數: 4 總?數: 4 user = User (id = 3 , name = Tom, age = 28 , email = test3@baomidou.com)3.5 SQL注?的原理
前?我們已經知道, MP 在啟動后會將 BaseMapper 中的?系列的?法注冊到 meppedStatements 中, 那么究竟是如何注?的呢?流程?是怎么樣的?下?我們將?起來分析下。 在 MP 中, ISqlInjector 負責 SQL 的注??作,它是?個接?, AbstractSqlInjector 是它的實現類,實現關系如下: 在 AbstractSqlInjector 中,主要是由 inspectInject() ?法進?注?的,如下: @Override public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {Class<?> modelClass = extractModelClass(mapperClass);if (modelClass != null) {String className = mapperClass.toString();Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());if (!mapperRegistryCache.contains(className)) {List<AbstractMethod> methodList = this.getMethodList();if (CollectionUtils.isNotEmpty(methodList)) {TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);// 循環注??定義?法methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));} else {logger.debug(mapperClass.toString() + ", No effective injection method was found.");}mapperRegistryCache.add(className);}} } 在實現?法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是關鍵,循環遍歷?法,進?注?。 最終調?抽象?法 injectMappedStatement 進?真正的注?: /*** 注??定義 MappedStatement** @param mapperClass mapper 接?* @param modelClass mapper 泛型* @param tableInfo 數據庫表反射信息* @return MappedStatement*/public abstract MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo); 查看該?法的實現: 以 SelectById 為例查看: public class SelectById extends AbstractMethod {@Overridepublic MappedStatement injectMappedStatement(Class<?> mapperClass, Class<? > modelClass, TableInfo tableInfo) {SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID;SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),sqlSelectColumns(tableInfo, false),tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),tableInfo.getLogicDeleteSql(true, false)), Object.class);return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo);} } 可以看到,?成了 SqlSource 對象,再將 SQL 通過 addSelectMappedStatement ?法添加到meppedStatements中。4. 配置
在 MP 中有?量的配置,其中有?部分是 Mybatis 原?的配置,另?部分是 MP 的配置,詳情: https://m ybatis.plus/config/ 下?我們對常?的配置做講解。4.1、基本配置
4.1.1、configLocation
MyBatis 配置?件位置,如果有單獨的 MyBatis 配置,請將其路徑配置到 configLocation 中。 MyBatis Configuration 的具體內容請參考 MyBatis 官??檔 Spring Boot : mybatis-plus.config-location = classpath:mybatis-config.xmlSpring MVC:
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean" ><property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>4.1.2、mapperLocations
MyBatis Mapper 所對應的 XML ?件位置,如果您在 Mapper 中有?定義?法( XML 中有?定義實現),需要進?該配置,告訴 Mapper 所對應的 XML ?件位置。 Spring Boot : mybatis-plus.mapper-locations = classpath*:mybatis/*.xml Spring MVC : <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean" ><property name="mapperLocations" value="classpath*:mybatis/*.xml"/> </bean> Maven 多模塊項?的掃描路徑需以 classpath*: 開頭 (即加載多個 jar 包下的 XML ?件) 測試: UserMapper.xml : <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.lagou.mp.mapper.UserMapper"><select id="findById" resultType="com.lagou.mp.pojo.User">select * from tb_user where id = #{id}</select> </mapper> package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface UserMapper extends BaseMapper<User> {User findById(Long id); } 測試?例: package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectPage() {User user = this.userMapper.findById(2L);System.out.println(user);} } 運?結果:4.1.3、typeAliasesPackage ?
MyBaits 別名包掃描路徑,通過該屬性可以給包中的類注冊別名,注冊后在 Mapper 對應的 XML ?件中可以直接使?類名,?不?使?全限定的類名(即 XML 中調?的時候不?包含包名)。 Spring Boot : mybatis-plus.type-aliases-package = com.lagou.mp.pojo Spring MVC : <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean" ><property name="typeAliasesPackage" value="com.baomidou.mybatisplus.samples.quickstart.entity"/> </bean>4.2、進階配置
本部分( Configuration )的配置?都為 MyBatis 原??持的配置,這意味著您可以通過 MyBatis XML配置?件的形式進?配置。4.2.1、mapUnderscoreToCamelCase
- 類型: boolean
- 默認值: true
4.2.2、cacheEnabled
- 類型: boolean
- 默認值: true
4.3、DB 策略配置
4.3.1、idType
- 類型: com.baomidou.mybatisplus.annotation.IdType
- 默認值: ID_WORKER
4.3.2、tablePrefix
- 類型: String
- 默認值: null
5. 條件構造器
在 MP 中, Wrapper 接?的實現類關系如下: 可以看到, AbstractWrapper 和 AbstractChainWrapper 是重點實現,接下來我們重點學習 AbstractWrapper 以及其?類。 說明 : QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的?類 ?于?成 sql 的 where 條件 , entity 屬性也?于?成 sql 的 where 條件 注意 : entity ?成的 where 條件與 使?各個 api ?成的 where 條件 沒有任何關聯?為 官??檔地址: https://mybatis.plus/guide/wrapper.html5.1、allEq
5.1.1、說明
allEq(Map<R, V> params) allEq(Map<R, V> params, boolean null2IsNull) allEq(boolean condition, Map<R, V> params, boolean null2IsNull)- 全部eq(或個別isNull)
5.1.2、測試?例
package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.List; import java.util.Map;@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testWrapper() {QueryWrapper<User> wrapper = new QueryWrapper<>();//設置條件Map<String,Object> params = new HashMap<>();params.put("name", "jack");params.put("age", "20"); // wrapper.allEq(params);//SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ? // wrapper.allEq(params,false); //SELECT * FROM tb_user WHERE name = ? AND age = ? // wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")) ,params);//SELECT * FROM tb_user WHERE name = ? AND age = ?List<User> users = this.userMapper.selectList(wrapper);for (User user : users) {System.out.println(user);}} }5.2、基本?較操作
- eq
- 等于 =
- ne
- 不等于 <>
- gt
- ?于 >
- ge
- ?于等于 >=
- lt
- ?于 <
- le
- ?于等于 <=
- between
- BETWEEN 值 1 AND 值 2
- notBetween
- NOT BETWEEN 值 1 AND 值 2
- in
- 字段 IN (value.get(0), value.get(1), ...)
- notIn
- 字段 NOT IN (v0, v1, ...)
5.3、模糊查詢
- like
- LIKE '%值%'
- 例: like("name", "王") ---> name like '%王%'
- notLike
- NOT LIKE '%值%'
- 例: notLike("name", "王") ---> name not like '%王%'
- likeLeft
- LIKE '%值'
- 例: likeLeft("name", "王") ---> name like '%王'
- likeRight
- LIKE '值%'
- 例: likeRight("name", "王") ---> name like '王%'
5.4、排序
- orderBy
- 排序:ORDER BY 字段, ...
- 例: orderBy(true, true, "id", "name") ---> order by id ASC,name ASC
- orderByAsc
- 排序:ORDER BY 字段, ... ASC
- 例: orderByAsc("id", "name") ---> order by id ASC,name ASC
- orderByDesc
- 排序:ORDER BY 字段, ... DESC
- 例: orderByDesc("id", "name") ---> order by id DESC,name DESC
5.5、邏輯查詢
- or
- 拼接 OR
- 主動調? or 表示緊接著下?個?法不是? and 連接!(不調? or 則默認為使? and 連接)
- and
- AND 嵌套
- 例: and(i -> i.eq("name", "李?").ne("status", "活著")) ---> and (name = '李?' and status <> '活著')
5.6、select
在 MP 查詢中,默認查詢所有的字段,如果有需要也可以通過 select ?法進?指定字段。 package com.lagou.mp; import com.lagou.mp.mapper.UserMapper; import com.lagou.mp.pojo.User; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.List; @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testWrapper() {QueryWrapper<User> wrapper = new QueryWrapper<>();//SELECT id,name,age FROM tb_user WHERE name = ? OR age = ?wrapper.eq("name", "jack").or().eq("age", 24).select("id", "name", "age");List<User> users = this.userMapper.selectList(wrapper);for (User user : users) {System.out.println(user);}} }6. ActiveRecord
ActiveRecord (簡稱 AR )?直?受動態語?( PHP 、 Ruby 等)的喜愛,? Java 作為準靜態語?,對于 ActiveRecord 往往只能感嘆其優雅,所以我們也在 AR 道路上進?了?定的探索,希望?家能夠喜歡。? 什么是 ActiveRecord ? ActiveRecord 也屬于 ORM (對象關系映射)層,由 Rails 最早提出,遵循標準的 ORM 模型:表映射到記錄,記錄映射到對象,字段映射到對象屬性。配合遵循的命名和配置慣例,能夠很?程度的快速實現模型的操作,?且簡潔易懂。 ActiveRecord 的主要思想是:- 每?個數據庫表對應創建?個類,類的每?個對象實例對應于數據庫中表的??記錄;通常 表的每個字段在類中都有相應的Field;
- ActiveRecord同時負責把??持久化,在ActiveRecord中封裝了對數據庫的訪問,即 CURD;;
- ActiveRecord是?種領域模型(Domain Model),封裝了部分業務邏輯;
6.1、開啟AR之旅
在 MP 中,開啟 AR ?常簡單,只需要將實體對象繼承 Model 即可。 package com.lagou.mp.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.activerecord.Model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class User extends Model<User> {private Long id;private String userName;private String password;private String name;private Integer age;private String email; }6.2、根據主鍵查詢
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testAR() {User user = new User();user.setId(2L);User user2 = user.selectById();System.out.println(user2);} }6.3、新增數據
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testARInsert() {User user = new User();user.setName("應顛");user.setAge(30);user.setEmail("yingdian@lagou.cn");boolean insert = user.insert();System.out.println(insert);} } [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] == > Preparing: INSERT INTO tb_user ( name, age, email ) VALUES ( ?, ?, ?, ?, ? ) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] == > Parameters: 應癲 (String), 30 (Integer), liubei@lagou.cn(String) [main] [com.lagou.mp.mapper.UserMapper.insert]-[DEBUG] < == Updates: 16.4、更新操作
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testAR() {User user = new User();user.setId(8L);user.setAge(35);boolean update = user.updateById();System.out.println(update);} } 結果: [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] == > Preparing: UPDATE tb_user SET age = ? WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] == > Parameters: 35 (Integer), 8 (Long) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] < == Updates: 16.5、刪除操作
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testAR() {User user = new User();user.setId(7L);boolean delete = user.deleteById();System.out.println(delete);} }結果:
[main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] == > Preparing: DELETE FROM tb_user WHERE id = ? [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] == > Parameters: 7 (Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] < == Updates: 16.6、根據條件查詢
@RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testARFindById() {User user = new User();QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();userQueryWrapper.le("age","20");List<User> users = user.selectList(userQueryWrapper);for (User user1 : users) {System.out.println(user1);}} } 結果: User (id = 1 , name = Jone, age = 18 , email = test1@baomidou.com) User (id = 2 , name = Jack, age = 20 , email = test2@baomidou.com) User (id = 7 , name = ?慕 , age = 18 , email = test@lagou.cn)7. 插件
7.1、mybatis的插件機制
MyBatis 允許你在已映射語句執?過程中的某?點進?攔截調?。默認情況下, MyBatis 允許使?插件 來攔截的?法調?包括: 1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 2. ParameterHandler (getParameterObject, setParameters) 3. ResultSetHandler (handleResultSets, handleOutputParameters) 4. StatementHandler (prepare, parameterize, batch, update, query) 我們看到了可以攔截 Executor 接?的部分?法,?如 update , query , commit , rollback 等?法,還有其他接?的?些?法等。 總體概括為: 1. 攔截執?器的?法 2. 攔截參數的處理 3. 攔截結果集的處理 4. 攔截 Sql 語法構建的處理 攔截器示例: package com.lagou.mp.plugins; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import java.util.Properties; @Intercepts({@Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})}) public class MyInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {//攔截?法,具體業務邏輯編寫的位置return invocation.proceed();}@Overridepublic Object plugin(Object target) {//創建target對象的代理對象,?的是將當前攔截器加?到該對象中return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {//屬性設置} }注?到Spring容器:
/*** ?定義攔截器*/@Beanpublic MyInterceptor myInterceptor(){return new MyInterceptor();}或者通過xml配置,mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><plugins><plugin interceptor="com.lagou.mp.plugins.MyInterceptor"></plugin></plugins> </configuration>7.2、執?分析插件
在 MP 中提供了對 SQL 執?的分析的插件,可?作阻斷全表更新、刪除的操作,注意:該插件僅適?于開發環境,不適?于?產環境。 SpringBoot 配置: @Bean public SqlExplainInterceptor sqlExplainInterceptor(){SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();List<ISqlParser> sqlParserList = new ArrayList<>();// 攻擊 SQL 阻斷解析器、加?解析鏈sqlParserList.add(new BlockAttackSqlParser());sqlExplainInterceptor.setSqlParserList(sqlParserList);return sqlExplainInterceptor; } 測試: @Test public void testUpdate(){User user = new User();user.setAge(20);int result = this.userMapper.update(user, null);System.out.println("result = " + result); } 結果: Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4 9) at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38) at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72) at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processUpdate( BlockAttackSqlParser.java:45) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(Abstract JsqlParser.java:92) at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlPar ser.java:67) at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser (AbstractSqlParserHandler.java:76) at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(Sql ExplainInterceptor.java:63) at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61) at com.sun.proxy. $Proxy70 .update(Unknown Source) at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession. java:197) ... 41 more 可以看到,當執?全表更新時,會拋出異常,這樣有效防?了?些誤操作。7.3、性能分析插件
性能分析攔截器,?于輸出每條 SQL 語句及其執?時間,可以設置最?執?時間,超過時間會拋出異常。 該插件只?于開發環境,不建議?產環境使?。 配置: javaconfig ?式 @Bean public PerformanceInterceptor performanceInterceptor(){PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();performanceInterceptor.setMaxTime(100);performanceInterceptor.setFormat(true);return performanceInterceptor; } xml ?式 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><plugins><!-- SQL 執?性能分析,開發環境使?,線上不推薦。 maxTime 指的是 sql 最?執?時 ? --><plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor "><property name="maxTime" value="100" /><!--SQL是否格式化 默認false--><property name="format" value="true" /></plugin></plugins> </configuration> 執?結果: Time : 11 ms - ID : com.lagou.mp.mapper.UserMapper.selectById Execute SQL : SELECT id, user_name, password, name, age, email FROM tb_user WHERE id = 7 可以看到,執?時間為 11ms 。如果將 maxTime 設置為 1 ,那么,該操作會拋出異常。7.4、樂觀鎖插件
7.4.1、主要適?場景
意圖: 當要更新?條記錄的時候,希望這條記錄沒有被別?更新 樂觀鎖實現?式:- 取出記錄時,獲取當前version
- 更新時,帶上這個version
- 執?更新時, set version = newVersion where version = oldVersion
- 如果version不對,就更新失敗
7.4.2、插件配置
spring xml: <bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor" /> spring boot: @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() {return new OptimisticLockerInterceptor(); }7.4.3、注解實體字段
需要為實體字段添加 @Version 注解。 第?步,為表添加 version 字段,并且設置初始值為 1 : ALTER TABLE `tb_user` ADD COLUMN `version` int(10) NULL AFTER `email`; UPDATE `tb_user` SET `version`='1';第?步,為User實體對象添加version字段,并且添加@Version注解:
@Version private Integer version;7.4.4、測試
測試?例: @Test public void testUpdate(){User user = new User();user.setAge(30);user.setId(2L);user.setVersion(1); //獲取到version為1int result = this.userMapper.updateById(user);System.out.println("result = " + result); } 執??志: main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] Original SQL: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser]- [DEBUG] parser sql: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [org.springframework.jdbc.datasource.DataSourceUtils]-[DEBUG] Fetching JDBC Connection from DataSource [main] [org.mybatis.spring.transaction.SpringManagedTransaction]-[DEBUG] JDBC Connection [HikariProxyConnection@540206885 wrapping com.mysql.jdbc.JDBC4Connection@27e0f2f5] will not be managed by Spring [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] == > Preparing: UPDATE tb_user SET age = ?, version = ? WHERE id = ? AND version = ? [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] == > Parameters: 30 (Integer), 2 (Integer), 2 (Long), 1 (Integer) [main] [com.lagou.mp.mapper.UserMapper.updateById]-[DEBUG] < == Updates: 1 [main] [org.mybatis.spring.SqlSessionUtils]-[DEBUG] Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@30135202] result = 1 可以看到,更新的條件中有 version 條件,并且更新的 version 為 2 。 如果再次執?,更新則不成功。這樣就避免了多?同時更新時導致數據的不?致。7.4.5、特別說明
- ?持的數據類型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整數類型下 newVersion = oldVersion + 1
- newVersion 會回寫到 entity 中
- 僅?持 updateById(id) 與 update(entity, wrapper) ?法
- 在 update(entity, wrapper) ?法下, wrapper 不能復?!!!
8. Sql 注?器
我們已經知道,在 MP 中,通過 AbstractSqlInjector 將 BaseMapper 中的?法注?到了 Mybatis 容器,這樣這些?法才可以正常執?。 那么,如果我們需要擴充 BaseMapper 中的?法,?該如何實現呢? 下?我們以擴展 findAll ?法為例進?學習。8.1、編寫MyBaseMapper
package com.lagou.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import java.util.List; public interface MyBaseMapper<T> extends BaseMapper<T> {List<T> findAll(); } 其他的 Mapper 都可以繼承該 Mapper ,這樣實現了統?的擴展。 如: package com.lagou.mp.mapper; import com.lagou.mp.pojo.User; public interface UserMapper extends MyBaseMapper<User> {User findById(Long id); }8.2、編寫MySqlInjector
如果直接繼承 AbstractSqlInjector 的話,原有的 BaseMapper 中的?法將失效,所以我們選擇繼承 DefaultSqlInjector 進?擴展。 package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector; import java.util.List; public class MySqlInjector extends DefaultSqlInjector {@Overridepublic List<AbstractMethod> getMethodList() {List<AbstractMethod> methodList = super.getMethodList();methodList.add(new FindAll());// 再擴充?定義的?法list.add(new FindAll());return methodList;} }8.3、編寫FindAll
package com.lagou.mp.sqlInjector; import com.baomidou.mybatisplus.core.enums.SqlMethod; import com.baomidou.mybatisplus.core.injector.AbstractMethod; import com.baomidou.mybatisplus.core.metadata.TableInfo; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlSource; public class FindAll extends AbstractMethod {@Overridepublic MappedStatement injectMappedStatement(Class<?> mapperClass, Class<? > modelClass, TableInfo tableInfo) {String sqlMethod = "findAll";String sql = "select * from " + tableInfo.getTableName();SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource, modelClass, tableInfo);} }8.4、注冊到Spring容器
/** * ?定義SQL注?器 */ @Bean public MySqlInjector mySqlInjector(){return new MySqlInjector(); }8.5、測試
@Test public void testFindAll(){List<User> users = this.userMapper.findAll();for (User user : users) {System.out.println(user);} } 輸出的 SQL : [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] == > Preparing: select * from tb_user [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] == > Parameters: [main] [com.lagou.mp.mapper.UserMapper.findAll]-[DEBUG] < == Total: 10 ?此,我們實現了全局擴展 SQL 注?器。9. ?動填充功能
有些時候我們可能會有這樣的需求,插?或者更新數據時,希望有些字段可以?動填充數據,?如密碼、version 等。在 MP 中提供了這樣的功能,可以實現?動填充。9.1、添加@TableField注解
@TableField(fill = FieldFill.INSERT) //插?數據時進?填充 private String version; 為 email 添加?動填充功能,在新增數據時有效。 FieldFill 提供了多種模式選擇: public enum FieldFill { 9.2、編寫MyMetaObjectHandler 9.3、測試/*** 默認不處理*/DEFAULT,/*** 插?時填充字段*/INSERT,/*** 更新時填充字段*/UPDATE,/*** 插?和更新時填充字段*/INSERT_UPDATE }9.2、編寫MyMetaObjectHandler
package com.lagou.mp.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; @Component public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {Object password = getFieldValByName("version", metaObject);if(null == password){//字段為空,可以進?填充setFieldValByName("version", "123456", metaObject);}}@Overridepublic void updateFill(MetaObject metaObject) {} }9.3、測試
@Test public void testInsert(){User user = new User();user.setName("冰冰");user.setAge(30);user.setVersion(1);int result = this.userMapper.insert(user);System.out.println("result = " + result); } 結果:10. 邏輯刪除
開發系統時,有時候在實現功能時,刪除操作需要實現邏輯刪除,所謂邏輯刪除就是將數據標記為刪除,?并?真正的物理刪除(?DELETE 操作),查詢時需要攜帶狀態條件,確保被標記的數據不被查詢到。這樣做的?的就是避免數據被真正的刪除。 MP 就提供了這樣的功能,?便我們使?,接下來我們?起學習下。10.1、修改表結構
ALTER TABLE `tb_user` ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表刪除,0代表未刪除' AFTER `version`; 為 tb_user 表增加 deleted 字段,?于表示數據是否被刪除, 1 代表刪除, 0 代表未刪除。 同時,也修改 User 實體,增加 deleted 屬性并且添加 @TableLogic 注解: @TableLogic private Integer deleted;10.2、配置
application.properties : # 邏輯已刪除值(默認為 1) mybatis-plus.global-config.db-config.logic-delete-value=1 # 邏輯未刪除值(默認為 0) mybatis-plus.global-config.db-config.logic-not-delete-value=010.3、測試
@Test public void testDeleteById(){this.userMapper.deleteById(2L); } 執?的 SQL : [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] == > Preparing: UPDATE tb_user SET deleted = 1 WHERE id = ? AND deleted = 0 [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] == > Parameters: 2 (Long) [main] [com.lagou.mp.mapper.UserMapper.deleteById]-[DEBUG] < == Updates: 1 測試查詢: @Test public void testSelectById(){User user = this.userMapper.selectById(2L);System.out.println(user); } 執?的 SQL : [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] == > Preparing: SELECT id,user_name,password,name,age,email,version,deleted FROM tb_user WHERE id = ? AND deleted = 0 [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] == > Parameters: 2 (Long) [main] [com.lagou.mp.mapper.UserMapper.selectById]-[DEBUG] < == Total: 0 可?,已經實現了邏輯刪除。11. 代碼?成器
AutoGenerator 是 MyBatis-Plus 的代碼?成器,通過 AutoGenerator 可以快速?成 Entity 、 Mapper 、 Mapper XML 、 Service 、 Controller 等各個模塊的代碼,極?的提升了開發效率。11.1、創建?程
pom.xml : <?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.3.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.lagou</groupId><artifactId>lagou-mp-generator</artifactId><version>0.0.1-SNAPSHOT</version><name>lagou-mp-generator</name><description>Demo project for Spring Boot</description><properties><java.version>11</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--mybatis-plus的springboot?持--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.1</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.1.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><!--mysql驅動--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--簡化代碼的?具包--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build> </project>11.2、代碼
package com.lagou.mp.generator; import java.util.ArrayList; import java.util.List; import java.util.Scanner; 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.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.FileOutConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.TemplateConfig; import com.baomidou.mybatisplus.generator.config.po.TableInfo; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; /** * <p> * mysql 代碼?成器演示例? * </p> */ public class MysqlGenerator {/*** <p>* 讀取控制臺內容* </p>*/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 + "!");}/*** RUN THIS*/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("lagou");gc.setOpen(false);mpg.setGlobalConfig(gc);// 數據源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&useSSL=false&characterEncoding=utf8");// dsc.setSchemaName("public");dsc.setDriverName("com.mysql.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("root");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();pc.setModuleName(scanner("模塊名"));pc.setParent("com.lagou.mp.generator");mpg.setPackageInfo(pc);// ?定義配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {// to do nothing}};List<FileOutConfig> focList = new ArrayList<>();focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {@Overridepublic String outputFile(TableInfo tableInfo) {// ?定義輸??件名稱return projectPath + "/lagou-mpgenerator/src/main/resources/mapper/" + pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;}});cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);mpg.setTemplate(new TemplateConfig().setXml(null));// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel); //strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.common.BaseEntity");strategy.setEntityLombokModel(true); //strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.common.BaseController");strategy.setInclude(scanner("表名"));strategy.setSuperEntityColumns("id");strategy.setControllerMappingHyphenStyle(true);strategy.setTablePrefix(pc.getModuleName() + "_");mpg.setStrategy(strategy);// 選擇 freemarker 引擎需要指定如下加,注意 pom 依賴必須有!mpg.setTemplateEngine(new FreemarkerTemplateEngine());mpg.execute();} }11.3、測試
代碼已?成: 實體對象:12. MybatisX 快速開發插件
MybatisX 是?款基于 IDEA 的快速開發插件,為效率??。 安裝?法:打開 IDEA ,進? File -> Settings -> Plugins -> Browse Repositories ,輸? mybatisx 搜索并安裝。 功能:- Java 與 XML 調回跳轉
- Mapper ?法?動?成 XML
?
?
總結
以上是生活随笔為你收集整理的Mybatis-Plus 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用sortablejs实现表格拖拽排序
- 下一篇: oversample upsample