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)依賴
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.sql3、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)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中invokedynamic字節(jié)碼指令問(wèn)題
這篇文章主要介紹了Java中invokedynamic字節(jié)碼指令問(wèn)題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
Java編程基于快速排序的三個(gè)算法題實(shí)例代碼
這篇文章主要介紹了Java編程基于快速排序的三個(gè)算法題實(shí)例代碼,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
SpringBoot項(xiàng)目實(shí)現(xiàn)MyBatis流式查詢的教程詳解
這篇文章主要介紹了SpringBoot項(xiàng)目如何實(shí)現(xiàn)MyBatis的流式查詢,mybatis的流式查詢,有點(diǎn)冷門(mén),實(shí)際用的場(chǎng)景比較少,但是在某些特殊場(chǎng)景下,卻是十分有效的一個(gè)方法,感興趣的同學(xué)可以參考一下2023-06-06
springmvc使用JSR-303進(jìn)行數(shù)據(jù)校驗(yàn)實(shí)例
本篇文章主要介紹了詳解springmvc使用JSR-303進(jìn)行數(shù)據(jù)校驗(yàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02

