MybatisPlus?BaseMapper?實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼
MybatisPlus 是一款在 Mybatis 基礎(chǔ)上進(jìn)行的增強(qiáng) orm 框架,可以實(shí)現(xiàn)不寫(xiě) sql 就完成數(shù)據(jù)庫(kù)相關(guān)的操作。普通的 mapper 接口通過(guò)繼承 BaseMapper 接口,即可獲得增強(qiáng),如下所示:
public interface UserMapper extends BaseMapper<User> { }
接下來(lái)就對(duì)其源碼一探究竟,看看他到底是如何實(shí)現(xiàn)的
環(huán)境搭建
1、使用 h2 數(shù)據(jù)庫(kù),方便測(cè)試,導(dǎo)入相關(guān)依賴(lài)
dependencies { implementation 'org.springframework.boot:spring-boot-starter-web:2.7.1' implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3.1' implementation 'org.projectlombok:lombok:1.18.24' implementation 'com.h2database:h2:1.4.200' }
2、springboot 配置文件
spring: datasource: driver-class-name: org.h2.Driver username: root password: test sql: init: schema-locations: classpath:db/schema-h2.sql data-locations: classpath:db/data-h2.sql
3、resources 目錄下新建 db 目錄,創(chuàng)建 sql 文件
schema-h2.sql
DROP TABLE IF EXISTS demo_user; CREATE TABLE demo_user ( id int primary key, name varchar, age int, email varchar );
data-h2.sql
DELETE FROM demo_user; INSERT INTO demo_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');
4、編寫(xiě) mapper 文件
public interface UserMapper extends BaseMapper<User> { }
5、啟動(dòng)測(cè)試
@MapperScan("org.example.mapper") @SpringBootApplication public class Main { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(Main.class, args); UserMapper userMapper = context.getBean(UserMapper.class); System.out.println(userMapper.selectList(null)); } }
結(jié)果如下
[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)]
從 @MapperScan 入手
@MapperScan 注解的作用是掃描指定 mapper 接口所在的包,并生成接口的代理對(duì)象,注入到 ioc 容器中,接口定義如下
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(MapperScannerRegistrar.class) @Repeatable(MapperScans.class) public @interface MapperScan { }
可以看到 Import 了個(gè) MapperScannerRegistrar,點(diǎn)進(jìn)去看看這個(gè)類(lèi)做了什么
@Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes mapperScanAttrs = AnnotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); if (mapperScanAttrs != null) { // 注冊(cè)一個(gè) beanDefinition registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0)); } } void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) { // 注冊(cè)MapperScannerConfigurer的BeanDefinition BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class); // ...... registry.registerBeanDefinition(beanName, builder.getBeanDefinition()); }
這個(gè) importRegister 注冊(cè)了一個(gè) MapperScannerConfigurer,這個(gè)類(lèi)是個(gè) BeanDefinitionRegistryPostProcessor,核心邏輯就是在這個(gè)類(lèi)中,即掃描指定 mapper 接口所在的包,并生成接口的代理對(duì)象,注入到 ioc 容器中,查看該類(lèi)的 postProcessBeanDefinitionRegistry() 方法
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // 設(shè)置一些scanner參數(shù) scanner.setSqlSessionTemplate(this.sqlSessionTemplate); // ...... // 掃描mapper接口 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
進(jìn)入父類(lèi) scan 方法,發(fā)現(xiàn)核心方法是子類(lèi)的 doScan(), 來(lái)到 MapperScannerConfigurer.doScan()
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { // 拿到掃描到的 beanDefinition Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { // 處理 mapper beanDefinition processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
核心在 processBeanDefinitions(beanDefinitions) 中
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); // 設(shè)置該BeanDefinition的beanClass是 MapperFactoryBean definition.setBeanClass(this.mapperFactoryBeanClass); // ...... // 設(shè)置該MapperFactoryBean 中的 sqlSessionTemplateBeanName if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) { definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName)); explicitFactoryUsed = true; } // ...... } }
通過(guò)這一系列源碼,可以知道,@MapperScan 指定的包在 MapperScannerConfigurer 被掃描成 BeanDefinition, 并且修改了 BeanDefinition 的 beanClass 屬性為 MapperFactory,這樣 spring 實(shí)例化 UserMapper 單例 bean 時(shí),會(huì)生成對(duì)應(yīng)的 MapperFactory
看看這個(gè) MapperFactory 是什么鬼
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } public SqlSession getSqlSession() { return this.sqlSessionTemplate; } }
這個(gè)類(lèi)是個(gè) FactoryBean,那么它的 getObject() 方法就是調(diào)用 sqlSessionTemplate 的 getMapper() 方法獲取代理對(duì)象,關(guān)于這個(gè) getMapper() 方法的解析,可以參考我之前寫(xiě)的《Mybatis 通過(guò)接口實(shí)現(xiàn) sql 執(zhí)行原理解析》
到這里,MapperFactory 生成的 bean 被放到了 ioc 容器中,結(jié)束了嗎?我們忽略了 MapperFactory 的父類(lèi) SqlSessionDaoSupport,下面一節(jié)來(lái)看看這個(gè)父類(lèi) SqlSessionDaoSupport 做了什么
SqlSessionDaoSupport
這個(gè)類(lèi)看名字是給 Dao 做支持的,Dao 指的就是那個(gè) mapper 接口,做什么支持?其實(shí)給就是給 BaseMapper 里定義的方法生成對(duì)應(yīng)的 Statemnet,注冊(cè)到 MybatisMapperRegistry 中,這樣調(diào)用 BaseMapper 方法時(shí),代理類(lèi)就會(huì)從 MybatisMapperRegistry 中找到 Statemnet,這樣可以取出 sql 執(zhí)行了,來(lái)看源碼,其他都是抽象方法,只有一個(gè)初始化方法
@Override public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { // 讓子類(lèi)處理 checkDaoConfig(); // Let concrete implementations initialize themselves. try { initDao(); } catch (Exception ex) { throw new BeanInitializationException("Initialization of DAO failed", ex); } }
調(diào)用了抽象方法,子類(lèi)實(shí)現(xiàn)了 checkDaoConfig(),來(lái)看下 MapperFactoryBean.checkDaoConfig()
protected void checkDaoConfig() { super.checkDaoConfig(); Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = this.getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 解析這個(gè) mapper 方法 configuration.addMapper(this.mapperInterface); } catch (Exception var6) { this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6); throw new IllegalArgumentException(var6); } finally { ErrorContext.instance().reset(); } } }
看到 configuration.addMapper(this.mapperInterface) 方法,相信看過(guò) mybatis 源碼的小伙伴們已經(jīng)知道要干什么了吧。就是解析這個(gè) mapper 類(lèi)方法,找到對(duì)應(yīng)的 sql,并封裝成 statemnet,下面看看這個(gè) configuration.addMapper(this.mapperInterface) 的實(shí)現(xiàn)邏輯吧
MybatisConfiguration.addMapper()
因?yàn)槭?MybatisPlus,所以源碼內(nèi)部的 Configuration 類(lèi)是 MybatisConfiguration,查看他的 addMapper() 方法源碼
@Override public <T> void addMapper(Class<T> type) { mybatisMapperRegistry.addMapper(type); }
再進(jìn)入 mybatisMapperRegistry.addMapper(type) 源碼
@Override public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; } boolean loadCompleted = false; try { // TODO 注冊(cè)mapper類(lèi)對(duì)應(yīng)的代理工廠類(lèi),用于生成代理對(duì)象 knownMappers.put(type, new MybatisMapperProxyFactory<>(type)); MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); // 解析mapper類(lèi),生成 statement parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
進(jìn)入 parse() 方法查看
@Override public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { // 解析mapper.xml loadXmlResource(); configuration.addLoadedResource(resource); String mapperName = type.getName(); assistant.setCurrentNamespace(mapperName); // 解析緩存 parseCache(); parseCacheRef(); IgnoreStrategy ignoreStrategy = InterceptorIgnoreHelper.initSqlParserInfoCache(type); for (Method method : type.getMethods()) { if (!canHaveStatement(method)) { continue; } if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent() && method.getAnnotation(ResultMap.class) == null) { parseResultMap(method); } try { // TODO 加入 注解過(guò)濾緩存 InterceptorIgnoreHelper.initSqlParserInfoCache(ignoreStrategy, mapperName, method); parseStatement(method); } catch (IncompleteElementException e) { // TODO 使用 MybatisMethodResolver 而不是 MethodResolver configuration.addIncompleteMethod(new MybatisMethodResolver(this, method)); } } // TODO 注入 CURD 動(dòng)態(tài) SQL , 放在在最后, because 可能會(huì)有人會(huì)用注解重寫(xiě)sql try { // https://github.com/baomidou/mybatis-plus/issues/3038 if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { parserInjector(); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new InjectorResolver(this)); } } parsePendingMethods(); }
關(guān)注最后注釋?zhuān)⑷?CRUD 動(dòng)態(tài) SQL,其實(shí)就是給 BaseMapper 里的方法創(chuàng)建對(duì)應(yīng)的 Statement,查看內(nèi)部邏輯:
void parserInjector() { // DefaultSqlInjector.inspectInject(); GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); }
這里先獲取到默認(rèn)的 Sql 注入器 DefaultSqlInjector,再調(diào)用其 inspectInject() 方法注入 sql
@Override 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)) { // 根據(jù)實(shí)體類(lèi),根據(jù)注解解析出表的信息 TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass); // 拿到所有的AbstractMethod實(shí)現(xiàn)類(lèi) List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo); if (CollectionUtils.isNotEmpty(methodList)) { // 循環(huán)注入自定義方法 methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); } else { logger.debug(mapperClass.toString() + ", No effective injection method was found."); } mapperRegistryCache.add(className); } } }
這里面的 AbstractMethod 的實(shí)現(xiàn)類(lèi)有很多,如下
可以說(shuō),BaseMapper 中每個(gè)方法都有一個(gè)對(duì)應(yīng)的 AbstractMethod 實(shí)現(xiàn)類(lèi),以 selectList() 為例,可以找到 SelectList 類(lèi)
在下面循環(huán)注入的地方:methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo))
, 進(jìn)入 AbstractMethod.inject() 方法
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); /* 注入自定義方法 */ injectMappedStatement(mapperClass, modelClass, tableInfo); }
子類(lèi)實(shí)現(xiàn)了 injectMappedStatement 方法,還是以 SelectList 為例
@Override public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { // selectList sql 模版 SqlMethod sqlMethod = SqlMethod.SELECT_LIST; // 格式化sql String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo), sqlOrderBy(tableInfo), sqlComment()); // 封裝成 sqlSource SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); // 注冊(cè) mapperStatement return this.addSelectMappedStatementForTable(mapperClass, methodName, sqlSource, tableInfo); }
其中 sqlSelectColumns(tableInfo, true) 方法是構(gòu)造出 select 的所有列名,并加上動(dòng)態(tài)sql標(biāo)簽
<choose> <when test="ew != null and ew.sqlSelect != null"> ${ew.sqlSelect} </when> <otherwise>id,name,age,email</otherwise> </choose>
其中 sqlWhereEntityWrapper(true, tableInfo) 方法是構(gòu)造出 where 后面的條件語(yǔ)句,并加上動(dòng)態(tài)sql標(biāo)簽
<if test="ew != null"> <where> <if test="ew.entity != null"> <if test="ew.entity.id != null">id=#{ew.entity.id}</if> <if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if> <if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if> <if test="ew.entity['email'] != null"> AND email=#{ew.entity.email}</if> </if> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere"> <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment} </if> </where> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere"> ${ew.sqlSegment} </if> </if>
最后 format 后的 sql 語(yǔ)句是
<script> <if test="ew != null and ew.sqlFirst != null"> ${ew.sqlFirst} </if> SELECT <choose> <when test="ew != null and ew.sqlSelect != null"> ${ew.sqlSelect} </when> <otherwise>id,name,age,email</otherwise> </choose> FROM demo_user <if test="ew != null"> <where> <if test="ew.entity != null"> <if test="ew.entity.id != null">id=#{ew.entity.id}</if> <if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if> <if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if> <if test="ew.entity['email'] != null"> AND email=#{ew.entity.email}</if> </if> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere"> <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment} </if> </where> <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere"> ${ew.sqlSegment} </if> </if> <if test="ew != null and ew.sqlComment != null"> ${ew.sqlComment} </if> </script>
最后是把 sql 封裝成了 SqlSource,并構(gòu)造 MapperStatement 存入 configuration.mappedStatements 中,后面 mapper 調(diào)用 selectList 方法時(shí),會(huì)從 mappedStatements 中找到對(duì)應(yīng)的 statement,并取出 sql 語(yǔ)句執(zhí)行,就能拿到數(shù)據(jù)了
小結(jié)
到此,MybatisPlus BaseMapper 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼解析完畢,相信通過(guò)源碼的閱讀能對(duì) mybatisPlus 有更深的了解
到此這篇關(guān)于MybatisPlus BaseMapper 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)增刪改查源碼解析的文章就介紹到這了,更多相關(guān)MybatisPlus BaseMapper 數(shù)據(jù)庫(kù)增刪改查內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Mybatis-Plus接口BaseMapper與Services使用詳解
- Mapper層繼承BaseMapper<T>需要引入的pom依賴(lài)方式
- mybatis抽取基類(lèi)BaseMapper增刪改查的實(shí)現(xiàn)
- 淺談Mybatis Plus的BaseMapper的方法是如何注入的
- mybatis-plus中BaseMapper入門(mén)使用
- MybatisPlus BaseMapper 中的方法全部 Invalid bound statement (not found Error處理)
- Mybatis-Plus BaseMapper的用法詳解
- BaseMapper接口的使用方法
相關(guān)文章
Spring Boot 在啟動(dòng)時(shí)進(jìn)行配置文件加解密的方法詳解
這篇文章主要介紹了Spring Boot 在啟動(dòng)時(shí)進(jìn)行配置文件加解密的方法,本文通過(guò)實(shí)例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Java Jackson之ObjectMapper常用用法總結(jié)
這篇文章主要給大家介紹了關(guān)于Java Jackson之ObjectMapper常用用法的相關(guān)資料,ObjectMapper是一個(gè)Java庫(kù),用于將JSON字符串轉(zhuǎn)換為Java對(duì)象或?qū)ava對(duì)象轉(zhuǎn)換為JSON字符串,需要的朋友可以參考下2024-01-01JavaWEB項(xiàng)目之如何配置動(dòng)態(tài)數(shù)據(jù)源
這篇文章主要介紹了JavaWEB項(xiàng)目之如何配置動(dòng)態(tài)數(shù)據(jù)源問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06使用WebSocket實(shí)現(xiàn)即時(shí)通訊(一個(gè)群聊的聊天室)
這篇文章主要為大家詳細(xì)介紹了使用WebSocket實(shí)現(xiàn)即使通訊,實(shí)現(xiàn)一個(gè)群聊的聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Mybatis-plus通過(guò)添加攔截器實(shí)現(xiàn)簡(jiǎn)單數(shù)據(jù)權(quán)限
系統(tǒng)需要根據(jù)用戶(hù)所屬的公司,來(lái)做一下數(shù)據(jù)權(quán)限控制,具體一點(diǎn),就是通過(guò)表中的company_id進(jìn)行權(quán)限控制,項(xiàng)目使用的是mybatis-plus,所以通過(guò)添加攔截器的方式,修改查詢(xún)sql,實(shí)現(xiàn)數(shù)據(jù)權(quán)限,本文就通過(guò)代碼給大家詳細(xì)的講解一下,需要的朋友可以參考下2023-08-08SpringBoot通知機(jī)制的實(shí)現(xiàn)方式
這篇文章主要介紹了SpringBoot通知機(jī)制的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07一篇文章教你如何用Java自定義一個(gè)參數(shù)校驗(yàn)器
這篇文章主要介紹了使用java自定義一個(gè)參數(shù)校驗(yàn)器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)2021-09-09IDEA啟動(dòng)Tomcat報(bào)Unrecognized option: --add-opens=java
這篇文章主要為大家介紹了解決IDEA啟動(dòng)Tomcat報(bào)Unrecognized option: --add-opens=java.base/java.lang=ALL-UNNAMED的方法,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08