一、簡介
MybatisPlus可以節省大量時間,所有的CRUD代碼都可以自動化完成。MyBatis-Plus是一個MyBatis的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。
特性:
- 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
- 損耗小:啟動即會自動注入基本 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 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
二、快速入門
注:本文實驗環境為Spring Boot + MySQL。
1. 加入依賴
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.6</version><relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies><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>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</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><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>RELEASE</version><scope>test</scope></dependency></dependencies>
2, 創建數據庫表
DROP TABLE IF EXISTS 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)
);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');
3. 編寫application.yml
spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/mybatis_plus?userUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4. 引入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
5. 編寫pojo
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {private Long id;private String name;private Integer age;private String email;
}
6. 編寫mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
//所有的CRUD都已經完成,不需要像以前一樣配置一大堆文件:pojo->dao(連接mybatis,配置mapper.xml文件)->service->controller
}
7. 編寫啟動類
@SpringBootApplication
@MapperScan("com.shiftycat.mybatis_plus.mapper")
public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}}
8. 測試
@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);}}
}
三、Mapper層的通用CRUD
1. 插入操作
// int insert(T entity);
@Test
public void testInsert() {User user = new User();user.setAge(18);user.setEmail("123456@qq.com");user.setName("張三");//返回的result是受影響的?數,并不是?增后的idint result = userMapper.insert(user);System.out.println(result);System.out.println(user.getId());
}
1)@TableId
通過數據庫可以看到,數據已經寫入到了數據庫,但是,id的值不正確,我們期望的是數據庫自增長,實際是MP生成了id的值寫入到了數據庫。
解決方案:自定義ID生成器
在復雜分布式系統中,往往需要大量的數據和消息進行唯一標識。比如支付寶每一個賬號在數據庫分表后都需要有一個唯一ID做標識。此時一個能夠生成全局唯一ID的系統是非常必要的。所以,生成的ID需要具備一下特點:
- 全局唯一性:不能出現重復的ID號,既然是唯一標識,這是最基本的要求。
- 趨勢遞增:在MySQL InnoDB引擎中使用的是聚集索引,由于多數RDBMS使用B-tree的數據結構來存儲索引數據,在主鍵的選擇上面我們應該盡量使用有序的主鍵保證寫入性能。
- 單調遞增:保證下一個ID一定大于上一個ID,例如事務版本號、IM增量消息、排序等特殊需求。
- 信息安全:如果ID是連續的,惡意用戶的扒取工作就非常容易做了,直接按照順序下載指定URL即可;如果是訂單號就更危險了,競對可以直接知道我們一天的單量。所以在一些應用場景下,會需要ID無規則、不規則。
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {@TableId(type = IdType.AUTO) //指定id類型為自增長private Long id;private String name;private Integer age;private String email;
}
通過@TableId(type = IdType.AUTO)
來設置主鍵ID自增長,ctrl+鼠標左鍵可查看剩下的type枚舉類型。主要主鍵生成方式有:
-
數據庫ID自增(AUTO)
-
雪花算法(ASSIGN_ID)
這種方案是一種以劃分命名空間來生成ID的一種算法,這種方案把64-bit分別劃分成多段,分開來標示機器、時間等。使用41bit作為毫秒數,10bit作為機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作為毫秒內的流水號(意味著每個節點在每毫秒可以產生4096個ID),最后還有一個符號位,永遠是0。
優點:
- 毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增的。
- 不依賴數據庫等第三方系統,以服務的方式部署,穩定性更高,生成ID的性能也是非常高的。
- 可以根據自身業務特性分配bit位,非常靈活。
缺點:
- 強依賴機器時鐘,如果機器上時鐘回撥,會導致發號重復或者服務會處于不可用狀態。
-
UUID(ASSIGN_UUID)
UUID(Universally Unique Identifier)的標準型式包含32個16進制數字,以連字號分為五段,形式為8-4-4-4-12的36個字符。
優點:
- 性能非常高:本地生成,沒有網絡消耗。
缺點:
- 沒有排序,無法保證趨勢遞增。
- UUID往往使用字符串存儲,查詢的效率比較低。
- 不易于存儲:UUID太長,16字節128位,通常以36長度的字符串表示,很多場景不適用。
- 信息不安全:基于MAC地址生成UUID的算法可能會造成MAC地址泄露,這個漏洞曾被用于尋找梅麗莎病毒的制作者位置。
- ID作為主鍵時在特定的環境會存在一些問題,比如做DB主鍵的場景下,UUID就非常不適用:
- MySQL官方有明確的建議主鍵要盡量越短越好[4],36個字符長度的UUID不符合要求。
- 對MySQL索引不利:如果作為數據庫主鍵,在InnoDB引擎下,UUID的無序性可能會引起數據位置頻繁變動,嚴重影響性能。
注:自 3.3.0 開始,默認使用雪花算法+UUID(不含中劃線)。
public enum IdType {// 數據庫ID?增AUTO(0),// 該類型為未設置主鍵類型NONE(1),// ?戶輸?ID,該類型可以通過??注冊?動填充插件進?填充INPUT(2),// 分配ID (主鍵類型為number或string)(雪花算法)ASSIGN_ID(3),// 分配UUID (主鍵類型為 string)ASSIGN_UUID(4);private final int key;private IdType(int key) {this.key = key;}public int getKey() {return this.key;}
}
2)@TableField
@TableField注解可以指定字段的?些屬性:
- value:指定屬性對應的數據庫表字段名。如果屬性名與表字段名一致,可以不用設置該屬性。
- exist:設置屬性是否為數據庫表字段,默認為true。如果設置為false,則表示該屬性不對應任何數據庫字段。
- select:設置查詢時是否查詢該屬性,默認為true。如果設置為false,則表示查詢時不查詢該屬性。
- insert:設置插入時是否插入該屬性,默認為true。如果設置為false,則表示插入時不插入該屬性。
- update:設置更新時是否更新該屬性,默認為true。如果設置為false,則表示更新時不更新該屬性。
- keepGlobalFormat:設置是否保持全局的命名規則,默認為false。如果設置為true,則屬性名將按照全局的命名規則進行轉換。
- condition:設置查詢條件,只有滿足條件的數據才會查詢到該屬性。
幾個常見的應用場景:
-
映射數據庫表字段名與實體類屬性名不一致
通過設置value屬性,可以靈活地指定屬性對應的數據庫表字段名,避免在查詢和更新操作時發生字段名不匹配的錯誤。
-
設置插入和更新時需要忽略的字段
通過設置insert和update屬性,可以靈活地控制是否插入或者更新某個屬性,避免不必要的數據庫操作。
-
根據條件查詢指定的字段
通過設置condition屬性,可以指定查詢時的條件,只有滿足條件的數據才會查詢到該字段,提高查詢性能。
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {@TableId(type = IdType.AUTO) //指定id類型為自增長private Long id;private String name;@TableField(select = false)private Integer age;@TableField(value = "email")private String mail;@TableField(exist = false)private String address;
}
3)@TableName
@TableName用來設置實體類對應的表明。如果我們不設置這個注解,我們操作的數據庫的表就由BaseMapper<User>
泛型決定(User)
- value作用:value指定數據庫中的表名
@TableName(value="user")
public class User {......
}
另外的一種方法是在ymal文件中設置實體類所對應的表的統一前綴。
mybatis-plus:global-config:db-config:table-prefix: db_
2. 更新操作
1)根據主鍵更新
// 據 ID 修改
// int updateById(@Param("et") T entity);
@Test
public void testUpdateById() {User user = new User();user.setId(5L); //主鍵user.setAge(21); //更新的字段//根據id更新,更新不為null的字段this.userMapper.updateById(user);
}
2)根據條件更新
/*** 根據 whereEntity 條件,更新記錄* 正常的更新sql語句為:update 表名 set 字段 = 值 where 條件語句;* @param entity 實體對象 (set 條件值,可以為 null)* @param updateWrapper 實體對象封裝操作類(可以為 null,??的 entity ?于?成 where 語句)*/
// int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);
@Test
public void testUpdate1() {/*根據條件進行更新:將姓名為“張三”的年齡修改為30歲。*/User user = new User();//更新的字段user.setAge(30);//更新的條件QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("name", "張三");//執?更新操作int result = this.userMapper.update(user, wrapper);System.out.println("result = " + result);
}@Test
public void testUpdate2() {// 方法2:通過UpdateWrapper進?更新//更新的條件以及字段UpdateWrapper<User> wrapper=new UpdateWrapper<>();wrapper.eq("name","張三").set("age",28);//執?更新操作int result=this.userMapper.update(null,wrapper);System.out.println("result = "+result);
}
3. 刪除操作
1)根據主鍵刪除
// int deleteById(T entity);
@Test
public void testDeleteById() {//執?刪除操作int result = this.userMapper.deleteById(5L);System.out.println("result = " + result);
}
2)根據條件刪除
// int deleteByMap(@Param("cm") Map<String, Object> columnMap);
@Test
public void testDeleteByMap1() {Map<String, Object> columnMap = new HashMap<>();columnMap.put("age", 28);columnMap.put("name", "張三");//將columnMap中的元素設置為刪除的條件,多個之間為and關系int result = this.userMapper.deleteByMap(columnMap);System.out.println("result = " + result);
}// int delete(@Param("ew") Wrapper<T> queryWrapper);
@Test
public void testDeleteByMap2() {User user = new User();user.setAge(18);user.setName("張三");//將實體對象進?包裝,包裝為操作條件QueryWrapper<User> wrapper = new QueryWrapper<>(user);int result = this.userMapper.delete(wrapper);System.out.println("result = " + result);
}
3)根據主鍵批量刪除
// int deleteBatchIds(@Param("coll") Collection<?> idList);
@Test
public void testDeleteByMap() {//根據id集合批量刪除int result = this.userMapper.deleteBatchIds(Arrays.asList(2L, 3L));System.out.println("result = " + result);
}
4. 查詢操作
1)根據主鍵查詢
// T selectById(Serializable id);
@Test
public void testSelectById() {//根據id查詢數據User user = this.userMapper.selectById(1L);System.out.println("result = " + user);
}
2)根據主鍵批量查詢
// List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
@Test
public void testSelectBatchIds() {//根據id集合批量查詢List<User> users = this.userMapper.selectBatchIds(Arrays.asList(1L, 4L));for (User user : users) {System.out.println(user);}
}
3)根據條件查詢單個記錄
根據 entity
條件,查詢?條記錄,如果查詢結果超過一條會報錯。
default T selectOne(@Param("ew") Wrapper<T> queryWrapper) {List<T> list = this.selectList(queryWrapper);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}
}
@Test
public void testSelectOne() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.eq("name", "Jone");//根據條件查詢?條數據,如果結果超過?條會報錯User user = this.userMapper.selectOne(wrapper);System.out.println(user);
}
4)根據條件查詢總記錄數
// Long selectCount(@Param("ew") Wrapper<T> queryWrapper);
@Test
public void testSelectCount() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 20); //年齡?于20歲//根據條件查詢數據條數Long count = this.userMapper.selectCount(wrapper);System.out.println("count = " + count);
}
5)根據條件查詢全部記錄
// List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);
@Test
public void testSelectList1() {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);}
}// List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);
// 方法返回List<Map<String, Object>>類型的值
@Test
public void testSelectList2() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據List<Map<String, Object>> users = this.userMapper.selectList(wrapper);users.forEach(System.out::println);
}
// List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);
// 只返回第一個字段的值
@Test
public void testSelectList3() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 23); //年齡?于23歲//根據條件查詢數據List<Object> users = this.userMapper.selectList(wrapper);users.forEach(System.out::println);
}
6)根據條件查詢全部記錄并分頁
配置分頁插件:
@Configuration
@MapperScan("com.shiftycat.mybatis_plus.mapper") //設置mapper接?的掃描包
public class MybatisPlusConfig {/*** 新的分頁插件,一緩和二緩遵循mybatis的規則,需要設置 MybatisConfiguration#useDeprecatedExecutor = false 避免緩存出現問題(該屬性會在舊插件移除后一同移除)*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
// <P extends IPage<T>> P selectPage(P page, @Param("ew") Wrapper<T> queryWrapper);
@Test
public void testSelectPage() {QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.gt("age", 10); //年齡?于10歲//第一個參數:當前頁 第二個參數:每頁的條數Page<User> page = new Page<>(2, 1);//根據條件查詢數據IPage<User> iPage = this.userMapper.selectPage(page, wrapper);System.out.println("數據總條數:" + iPage.getTotal());System.out.println("總?數:" + iPage.getPages());System.out.println("分頁數據:" + iPage.getRecords());
}
四、SQL自動注入原理
1. MappedStatement 對象
在 Mybatis Plus 中,ISqlInjector
接口負責 SQL 的注入工作,AbstractSqlInjector
是它的實現類。
-
首先,
AbstractSqlInjector
抽象類執行inspectInject
方法。在該方法中,使用this.getMethodList()
獲取到實現類的列表,并且對于列表中的每一個元素運行inject()方法。public abstract class AbstractSqlInjector implements ISqlInjector {protected final Log logger = LogFactory.getLog(this.getClass());public AbstractSqlInjector() {}// inspectInject()方法public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0);if (modelClass != null) {String className = mapperClass.toString();Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());if (!mapperRegistryCache.contains(className)) {TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);// 獲取 CRUD 實現類列表List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);if (CollectionUtils.isNotEmpty(methodList)) {methodList.forEach((m) -> {// inject()方法在這邊m.inject()(builderAssistant, mapperClass, modelClass, tableInfo);});} else {this.logger.debug(mapperClass.toString() + ", No effective injection method was found.");}mapperRegistryCache.add(className);}}}public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo); }
-
其次,進入
inject()
方法。public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {this.configuration = builderAssistant.getConfiguration();this.builderAssistant = builderAssistant;this.languageDriver = this.configuration.getDefaultScriptingLanguageInstance();// injectMappedStatement()方法在這邊this.injectMappedStatement(mapperClass, modelClass, tableInfo);}
-
再進入
injectMappedStatement()
方法,ctrl+Alt
選擇你執行的 CRUD 操作。以SelectById
為例,生成了SqlSource
對象,再將 SQL 通過addSelectMappedStatementForTable()
方法添加到meppedStatements
中。public class SelectById extends AbstractMethod {public SelectById() {this(SqlMethod.SELECT_BY_ID.getMethod());}public SelectById(String name) {super(name);}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(), tableInfo.getLogicDeleteSql(true, true)), Object.class);// addSelectMappedStatementForTable()方法在這邊return this.addSelectMappedStatementForTable(mapperClass, this.methodName, sqlSource, tableInfo);} }
-
實現類是如何獲取到
meppedStatements
返回值:在AbstractSqlInjector
抽象類inspectInject
方法從this.getMethodList()
方法獲取。public abstract class AbstractSqlInjector implements ISqlInjector {protected final Log logger = LogFactory.getLog(this.getClass());public AbstractSqlInjector() {}public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {......// 獲取 CRUD 實現類列表List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);......}// getMethodList()方法public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo); }
-
DefaultSqlInjector
中的getMethodList()
方法獲取CRUD實現類列表。public class DefaultSqlInjector extends AbstractSqlInjector {public DefaultSqlInjector() {}public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {Stream.Builder<AbstractMethod> builder = Stream.builder().add(new Insert()).add(new Delete()).add(new DeleteByMap()).add(new Update()).add(new SelectByMap()).add(new SelectCount()).add(new SelectMaps()).add(new SelectMapsPage()).add(new SelectObjs()).add(new SelectList()).add(new SelectPage());if (tableInfo.havePK()) {builder.add(new DeleteById()).add(new DeleteBatchByIds()).add(new UpdateById()).add(new SelectById()).add(new SelectBatchByIds());} else {this.logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", tableInfo.getEntityType()));}return (List)builder.build().collect(Collectors.toList());} }
總結:項目啟動時,首先由默認注入器DefaultSqlInjector生成基礎 CRUD 實現類對象,其次遍歷實現類列表,依次注入各自的模板 SQL,最后將其添加至 mappedstatement
。
2. SQL 語句生成
SqlSource
通過解析SQL 模板、以及傳入的表信息和主鍵信息構建出了 SQL 語句。
-
在
injectMappedStatement()
方法中,首先獲取在mapper.xml method idpublic MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {// 定義 mybatis xml method id, 對應 <id="xyz">SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;// 構造 id 對應的具體 xml 片段SqlSource sqlSource = new RawSqlSource(this.configuration, String.format(sqlMethod.getSql(), this.sqlSelectColumns(tableInfo, false), tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(), tableInfo.getLogicDeleteSql(true, true)), Object.class);// 將 xml method 方法添加到 mybatis 的 MappedStatement 中return this.addSelectMappedStatementForTable(mapperClass, this.methodName, sqlSource, tableInfo); }
-
根據
AbstractSqlInjector
抽象類的inspectInject
方法中的initTableInfo
方法獲取數據庫表信息。public static synchronized TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {// 獲取數據庫表緩存信息TableInfo targetTableInfo = (TableInfo)TABLE_INFO_CACHE.get(clazz);Configuration configuration = builderAssistant.getConfiguration();// 獲取到緩存信息if (targetTableInfo != null) {Configuration oldConfiguration = targetTableInfo.getConfiguration();// 配置信息變化,則初始化if (!oldConfiguration.equals(configuration)) {targetTableInfo = initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);}return targetTableInfo;} else {// 沒有獲取到緩存信息,則初始化return initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);} }private static synchronized TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {TableInfo tableInfo = new TableInfo(configuration, clazz);tableInfo.setCurrentNamespace(currentNamespace);GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);// 初始化表名相關String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo);List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList();// 初始化字段相關initTableFields(configuration, clazz, globalConfig, tableInfo, excludePropertyList);// 自動構建 resultMaptableInfo.initResultMapIfNeed();globalConfig.getPostInitTableInfoHandler().postTableInfo(tableInfo, configuration);// 放入緩存TABLE_INFO_CACHE.put(clazz, tableInfo);TABLE_NAME_INFO_CACHE.put(tableInfo.getTableName(), tableInfo);// 緩存 lambdaLambdaUtils.installCache(tableInfo);return tableInfo; }
-
initTableName()
方法,獲取表名信息源碼中傳入了實體類信息 class,其實就是通過實體上的@TableName 注解拿到了表名。private static String[] initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();// 通過實體上的@TableName 注解拿到了表名TableName table = (TableName)clazz.getAnnotation(TableName.class);String tableName = clazz.getSimpleName();String tablePrefix = dbConfig.getTablePrefix();String schema = dbConfig.getSchema();boolean tablePrefixEffect = true;String[] excludeProperty = null;if (table != null) {if (StringUtils.isNotBlank(table.value())) {tableName = table.value();if (StringUtils.isNotBlank(tablePrefix) && !table.keepGlobalPrefix()) {tablePrefixEffect = false;}} else {tableName = initTableNameWithDbConfig(tableName, dbConfig);}if (StringUtils.isNotBlank(table.schema())) {schema = table.schema();}if (StringUtils.isNotBlank(table.resultMap())) {tableInfo.setResultMap(table.resultMap());}tableInfo.setAutoInitResultMap(table.autoResultMap());excludeProperty = table.excludeProperty();} else {tableName = initTableNameWithDbConfig(tableName, dbConfig);}String targetTableName = tableName;if (StringUtils.isNotBlank(tablePrefix) && tablePrefixEffect) {targetTableName = tablePrefix + tableName;}if (StringUtils.isNotBlank(schema)) {targetTableName = schema + "." + targetTableName;}tableInfo.setTableName(targetTableName);if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) {tableInfo.setKeySequence((KeySequence)clazz.getAnnotation(KeySequence.class));}return excludeProperty; }
-
根據
AbstractSqlInjector
抽象類的inspectInject
方法中的initTableFields
方法獲取主鍵及其他字段信息。private static void initTableFields(Configuration configuration, Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {// 數據庫全局配置GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();PostInitTableInfoHandler postInitTableInfoHandler = globalConfig.getPostInitTableInfoHandler();Reflector reflector = tableInfo.getReflector();List<Field> list = getAllFields(clazz);// 標記是否讀取到主鍵boolean isReadPK = false;// 是否存在 @TableId 注解boolean existTableId = isExistTableId(list);boolean existTableLogic = isExistTableLogic(list);List<TableFieldInfo> fieldList = new ArrayList(list.size());Iterator var13 = list.iterator();while(var13.hasNext()) {Field field = (Field)var13.next();if (!excludeProperty.contains(field.getName())) {boolean isPK = false;boolean isOrderBy = field.getAnnotation(OrderBy.class) != null;if (existTableId) {// 主鍵ID初始化TableId tableId = (TableId)field.getAnnotation(TableId.class);if (tableId != null) {if (isReadPK) {throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", new Object[]{clazz.getName()});}initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId);isReadPK = true;isPK = true;}} else if (!isReadPK) {isPK = isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field);}if (isPK) {if (isOrderBy) {tableInfo.getOrderByFields().add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, true));}} else {TableField tableField = (TableField)field.getAnnotation(TableField.class);TableFieldInfo tableFieldInfo;if (tableField != null) {tableFieldInfo = new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);fieldList.add(tableFieldInfo);postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);} else {tableFieldInfo = new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);fieldList.add(tableFieldInfo);postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);}}}}tableInfo.setFieldList(fieldList);// 未發現主鍵注解,提示警告信息if (!isReadPK) {logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));}}