MybatisPlus?BaseMapper?實(shí)現(xiàn)對(duì)數(shù)據(jù)庫增刪改查源碼
MybatisPlus 是一款在 Mybatis 基礎(chǔ)上進(jìn)行的增強(qiáng) orm 框架,可以實(shí)現(xiàn)不寫 sql 就完成數(shù)據(jù)庫相關(guān)的操作。普通的 mapper 接口通過繼承 BaseMapper 接口,即可獲得增強(qiáng),如下所示:
public interface UserMapper extends BaseMapper<User> {
}接下來就對(duì)其源碼一探究竟,看看他到底是如何實(shí)現(xiàn)的
環(huán)境搭建
1、使用 h2 數(shù)據(jù)庫,方便測(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、編寫 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è)類做了什么
@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è)類是個(gè) BeanDefinitionRegistryPostProcessor,核心邏輯就是在這個(gè)類中,即掃描指定 mapper 接口所在的包,并生成接口的代理對(duì)象,注入到 ioc 容器中,查看該類的 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)入父類 scan 方法,發(fā)現(xiàn)核心方法是子類的 doScan(), 來到 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;
}
// ......
}
}通過這一系列源碼,可以知道,@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è)類是個(gè) FactoryBean,那么它的 getObject() 方法就是調(diào)用 sqlSessionTemplate 的 getMapper() 方法獲取代理對(duì)象,關(guān)于這個(gè) getMapper() 方法的解析,可以參考我之前寫的《Mybatis 通過接口實(shí)現(xiàn) sql 執(zhí)行原理解析》
到這里,MapperFactory 生成的 bean 被放到了 ioc 容器中,結(jié)束了嗎?我們忽略了 MapperFactory 的父類 SqlSessionDaoSupport,下面一節(jié)來看看這個(gè)父類 SqlSessionDaoSupport 做了什么
SqlSessionDaoSupport
這個(gè)類看名字是給 Dao 做支持的,Dao 指的就是那個(gè) mapper 接口,做什么支持?其實(shí)給就是給 BaseMapper 里定義的方法生成對(duì)應(yīng)的 Statemnet,注冊(cè)到 MybatisMapperRegistry 中,這樣調(diào)用 BaseMapper 方法時(shí),代理類就會(huì)從 MybatisMapperRegistry 中找到 Statemnet,這樣可以取出 sql 執(zhí)行了,來看源碼,其他都是抽象方法,只有一個(gè)初始化方法
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 讓子類處理
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}調(diào)用了抽象方法,子類實(shí)現(xiàn)了 checkDaoConfig(),來看下 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) 方法,相信看過 mybatis 源碼的小伙伴們已經(jīng)知道要干什么了吧。就是解析這個(gè) mapper 類方法,找到對(duì)應(yīng)的 sql,并封裝成 statemnet,下面看看這個(gè) configuration.addMapper(this.mapperInterface) 的實(shí)現(xiàn)邏輯吧
MybatisConfiguration.addMapper()
因?yàn)槭?MybatisPlus,所以源碼內(nèi)部的 Configuration 類是 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類對(duì)應(yīng)的代理工廠類,用于生成代理對(duì)象
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
// 解析mapper類,生成 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 加入 注解過濾緩存
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ì)用注解重寫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)注最后注釋,注入 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í)體類,根據(jù)注解解析出表的信息
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 拿到所有的AbstractMethod實(shí)現(xiàn)類
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)類有很多,如下

可以說,BaseMapper 中每個(gè)方法都有一個(gè)對(duì)應(yīng)的 AbstractMethod 實(shí)現(xiàn)類,以 selectList() 為例,可以找到 SelectList 類
在下面循環(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);
}子類實(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 后面的條件語句,并加上動(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 語句是
<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 語句執(zhí)行,就能拿到數(shù)據(jù)了
小結(jié)
到此,MybatisPlus BaseMapper 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫增刪改查源碼解析完畢,相信通過源碼的閱讀能對(duì) mybatisPlus 有更深的了解
到此這篇關(guān)于MybatisPlus BaseMapper 實(shí)現(xiàn)對(duì)數(shù)據(jù)庫增刪改查源碼解析的文章就介紹到這了,更多相關(guān)MybatisPlus BaseMapper 數(shù)據(jù)庫增刪改查內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中invokedynamic字節(jié)碼指令問題
這篇文章主要介紹了Java中invokedynamic字節(jié)碼指令問題,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
Java編程基于快速排序的三個(gè)算法題實(shí)例代碼
這篇文章主要介紹了Java編程基于快速排序的三個(gè)算法題實(shí)例代碼,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
SpringBoot項(xiàng)目實(shí)現(xiàn)MyBatis流式查詢的教程詳解
這篇文章主要介紹了SpringBoot項(xiàng)目如何實(shí)現(xiàn)MyBatis的流式查詢,mybatis的流式查詢,有點(diǎ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

