MybatisPlus?BaseMapper?實現對數據庫增刪改查源碼
MybatisPlus 是一款在 Mybatis 基礎上進行的增強 orm 框架,可以實現不寫 sql 就完成數據庫相關的操作。普通的 mapper 接口通過繼承 BaseMapper 接口,即可獲得增強,如下所示:
public interface UserMapper extends BaseMapper<User> {
}接下來就對其源碼一探究竟,看看他到底是如何實現的
環(huán)境搭建
1、使用 h2 數據庫,方便測試,導入相關依賴
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、啟動測試
@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));
}
}結果如下
[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 接口所在的包,并生成接口的代理對象,注入到 ioc 容器中,接口定義如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
}可以看到 Import 了個 MapperScannerRegistrar,點進去看看這個類做了什么
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 注冊一個 beanDefinition
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
// 注冊MapperScannerConfigurer的BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
// ......
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}這個 importRegister 注冊了一個 MapperScannerConfigurer,這個類是個 BeanDefinitionRegistryPostProcessor,核心邏輯就是在這個類中,即掃描指定 mapper 接口所在的包,并生成接口的代理對象,注入到 ioc 容器中,查看該類的 postProcessBeanDefinitionRegistry() 方法
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 設置一些scanner參數
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
// ......
// 掃描mapper接口
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}進入父類 scan 方法,發(fā)現核心方法是子類的 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();
// 設置該BeanDefinition的beanClass是 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
// ......
// 設置該MapperFactoryBean 中的 sqlSessionTemplateBeanName
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
definition.getPropertyValues().add("sqlSessionTemplate",
new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
}
// ......
}
}通過這一系列源碼,可以知道,@MapperScan 指定的包在 MapperScannerConfigurer 被掃描成 BeanDefinition, 并且修改了 BeanDefinition 的 beanClass 屬性為 MapperFactory,這樣 spring 實例化 UserMapper 單例 bean 時,會生成對應的 MapperFactory
看看這個 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;
}
}這個類是個 FactoryBean,那么它的 getObject() 方法就是調用 sqlSessionTemplate 的 getMapper() 方法獲取代理對象,關于這個 getMapper() 方法的解析,可以參考我之前寫的《Mybatis 通過接口實現 sql 執(zhí)行原理解析》
到這里,MapperFactory 生成的 bean 被放到了 ioc 容器中,結束了嗎?我們忽略了 MapperFactory 的父類 SqlSessionDaoSupport,下面一節(jié)來看看這個父類 SqlSessionDaoSupport 做了什么
SqlSessionDaoSupport
這個類看名字是給 Dao 做支持的,Dao 指的就是那個 mapper 接口,做什么支持?其實給就是給 BaseMapper 里定義的方法生成對應的 Statemnet,注冊到 MybatisMapperRegistry 中,這樣調用 BaseMapper 方法時,代理類就會從 MybatisMapperRegistry 中找到 Statemnet,這樣可以取出 sql 執(zhí)行了,來看源碼,其他都是抽象方法,只有一個初始化方法
@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);
}
}調用了抽象方法,子類實現了 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 {
// 解析這個 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 源碼的小伙伴們已經知道要干什么了吧。就是解析這個 mapper 類方法,找到對應的 sql,并封裝成 statemnet,下面看看這個 configuration.addMapper(this.mapperInterface) 的實現邏輯吧
MybatisConfiguration.addMapper()
因為是 MybatisPlus,所以源碼內部的 Configuration 類是 MybatisConfiguration,查看他的 addMapper() 方法源碼
@Override
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}再進入 mybatisMapperRegistry.addMapper(type) 源碼
@Override
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
}
boolean loadCompleted = false;
try {
// TODO 注冊mapper類對應的代理工廠類,用于生成代理對象
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
// 解析mapper類,生成 statement
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}進入 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 動態(tài) SQL , 放在在最后, because 可能會有人會用注解重寫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();
}關注最后注釋,注入 CRUD 動態(tài) SQL,其實就是給 BaseMapper 里的方法創(chuàng)建對應的 Statement,查看內部邏輯:
void parserInjector() {
// DefaultSqlInjector.inspectInject();
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}這里先獲取到默認的 Sql 注入器 DefaultSqlInjector,再調用其 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)) {
// 根據實體類,根據注解解析出表的信息
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 拿到所有的AbstractMethod實現類
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 的實現類有很多,如下

可以說,BaseMapper 中每個方法都有一個對應的 AbstractMethod 實現類,以 selectList() 為例,可以找到 SelectList 類
在下面循環(huán)注入的地方:methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)), 進入 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);
}子類實現了 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);
// 注冊 mapperStatement
return this.addSelectMappedStatementForTable(mapperClass, methodName, sqlSource, tableInfo);
}其中 sqlSelectColumns(tableInfo, true) 方法是構造出 select 的所有列名,并加上動態(tài)sql標簽
<choose>
<when test="ew != null and ew.sqlSelect != null">
${ew.sqlSelect}
</when>
<otherwise>id,name,age,email</otherwise>
</choose>其中 sqlWhereEntityWrapper(true, tableInfo) 方法是構造出 where 后面的條件語句,并加上動態(tài)sql標簽
<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,并構造 MapperStatement 存入 configuration.mappedStatements 中,后面 mapper 調用 selectList 方法時,會從 mappedStatements 中找到對應的 statement,并取出 sql 語句執(zhí)行,就能拿到數據了
小結
到此,MybatisPlus BaseMapper 實現對數據庫增刪改查源碼解析完畢,相信通過源碼的閱讀能對 mybatisPlus 有更深的了解
到此這篇關于MybatisPlus BaseMapper 實現對數據庫增刪改查源碼解析的文章就介紹到這了,更多相關MybatisPlus BaseMapper 數據庫增刪改查內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring Boot 在啟動時進行配置文件加解密的方法詳解
這篇文章主要介紹了Spring Boot 在啟動時進行配置文件加解密的方法,本文通過實例給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
Java Jackson之ObjectMapper常用用法總結
這篇文章主要給大家介紹了關于Java Jackson之ObjectMapper常用用法的相關資料,ObjectMapper是一個Java庫,用于將JSON字符串轉換為Java對象或將Java對象轉換為JSON字符串,需要的朋友可以參考下2024-01-01
IDEA啟動Tomcat報Unrecognized option: --add-opens=java
這篇文章主要為大家介紹了解決IDEA啟動Tomcat報Unrecognized option: --add-opens=java.base/java.lang=ALL-UNNAMED的方法,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-08-08

