首先根据MyBatis Plus入门实践详解 搭建好工程。然后创建数据库表与相关的类。
表结构如下:
EmployeeMapper接口继承自BaseMapper
public interface EmployeeMapper extends BaseMapper {
}
这个BaseMapper是com.baomidou.mybatisplus.mapper.BaseMapper。这里测试的MyBatis Plus版本是:
com.baomidou
mybatis-plus
2.3
BaseMapper定义了常用的增删改查接口,继承该接口后无需编写mapper.xml文件,即可获得通用的CRUD功能。
当然你可以在这个employeeMapper里面自定义方法,方法通过注解或者mapper.xml里面insert|update|select|delete实现。
测试代码如下:
@Test
public void testCommonInsert() {
//初始化Employee对象
Employee employee = new Employee();
employee.setLastName("MP");
employee.setEmail("[email protected]");
employee.setSalary(20000.0);
//插入到数据库
Integer result = employeeMapper.insert(employee);
System.out.println("result: " + result );
//获取当前数据在数据库中的主键值
Integer key = employee.getId();
System.out.println("key:" + key );
}
测试结果如下:
结论:
insert方法在插入时, 会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现到SQL语句中
测试代码如下:
@Test
public void testCommonInsert() {
//初始化Employee对象
Employee employee = new Employee();
employee.setLastName("MP");
employee.setEmail("[email protected]");
employee.setSalary(20000.0);
//插入到数据库
Integer result = employeeMapper.insertAllColumn(employee);
System.out.println("result: " + result );
//获取当前数据在数据库中的主键值
Integer key = employee.getId();
System.out.println("key:" + key );
}
insertAllColumn方法在插入时, 不管属性是否非空, 属性所对应的字段都会出现到SQL语句中。
测试代码如下:
@Test
public void testCommonUpdate() {
//初始化修改对象
Employee employee = new Employee();
employee.setId(7);
employee.setLastName("小泽老师");
employee.setEmail("[email protected]");
employee.setGender(0);
Integer result = employeeMapper.updateById(employee);
System.out.println("result: " + result );
}
结论
updateById方法在更新时, 会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现到SQL语句中
测试代码如下:
@Test
public void testCommonUpdate() {
//初始化修改对象
Employee employee = new Employee();
employee.setId(7);
employee.setLastName("小泽老师");
employee.setEmail("[email protected]");
employee.setGender(0);
Integer result = employeeMapper.updateAllColumnById(employee);
System.out.println("result: " + result );
}
updateAllColumnById方法在更新时, 不管属性是否非空, 属性所对应的字段都会出现到SQL语句中。
测试代码如下:
Employee employee = employeeMapper.selectById(7);
System.out.println(employee);
测试代码如下:
Employee employee = new Employee();
employee.setId(7);
employee.setLastName("小泽老师");
employee.setGender(0);
Employee result = employeeMapper.selectOne(employee);
System.out.println("result: " +result );
测试结果如下:
这里需要注意的是使用selectOne时需要保证数据库中顶多查询出一条数据,否则就会报错nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
测试代码如下:
List idList = new ArrayList<>();
idList.add(7);
idList.add(8);
idList.add(9);
List emps = employeeMapper.selectBatchIds(idList);
System.out.println(emps);
测试代码如下:
Map columnMap = new HashMap<>();
//这里注意是列名, 非对象属性名
columnMap.put("last_name", "MP");
columnMap.put("gender", 1);
List emps = employeeMapper.selectByMap(columnMap);
System.out.println(emps);
测试代码如下:
List emps = employeeMapper.selectPage(new Page<>(2, 2), null);
System.out.println(emps);
测试结果如下:
可以看到SQL语句上面并没有limit关键字进行分页,但是返回的数据结果确实是经过分页处理的。这说明其并非是真正的物理分页,而是借助于RowBounds实现的内存分页。如果想实现物理分页,可以考虑使用pageHelper插件。
故而上述分页实例并不推荐,推荐使用MyBatis Plus的分页插件。
测试代码如下:
Integer result = employeeMapper.deleteById(13);
System.out.println("result: " + result );
根据组合条件删除,返回删除条数,没有删除返回0.
测试代码如下:
Map columnMap = new HashMap<>();
columnMap.put("last_name", "MP");
columnMap.put("email", "[email protected]");
Integer result = employeeMapper.deleteByMap(columnMap);
System.out.println("result: " + result );
根据ID批量删除,返回删除的记录数,没有删除返回0。
测试代码如下:
List idList = new ArrayList<>();
idList.add(13);
idList.add(14);
idList.add(15);
Integer result = employeeMapper.deleteBatchIds(idList);
System.out.println("result: " + result );
测试结果如下:
如下,分页查询tbl_employee表中,年龄在18~50之间且性别为男且姓名为Tom的所有用户。
测试代码如下:
List emps =employeeMapper.selectPage(new Page(1, 2),
new EntityWrapper()
.between("age", 18, 50)
.eq("gender", 1)
.eq("last_name", "Tom")
);
System.out.println(emps);
这里使用new EntityWrapper
传一个Wrapper实例对象进去,使用between、eq这些关键字来封装查询条件(在JPA里面也可以看到这种思想)
。
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee
WHERE (age BETWEEN ? AND ? AND gender = ? AND last_name = ?)
还可以使用Wrapper的另外一个子类Condition来实现上述效果,condition和EntityWrapper都wrapper的子类:
List emps = employeeMapper.selectPage(
new Page(1,2),
Condition.create()
.between("age", 18, 50)
.eq("gender", "1")
.eq("last_name", "Tom")
);
System.out.println(emps);
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee
WHERE (age BETWEEN ? AND ? AND gender = ? AND last_name = ?)
查询tbl_employee表中, 性别为女并且名字中带有"老师" 或者 邮箱中带有"a"。
测试代码如下:
List emps = employeeMapper.selectList(
new EntityWrapper()
.eq("gender", 0)
.like("last_name", "老师")
.orNew() // SQL: (gender = ? AND last_name LIKE ?) OR (email LIKE ?)
.like("email", "a")
);
System.out.println(emps);
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee
WHERE (gender = ? AND last_name LIKE ?) OR (email LIKE ?)
Parameters: 0(Integer), %老师%(String), %a%(String)
上面使用的是orNew,下面对比使用or的时候查询SQL:
List emps = employeeMapper.selectList(
new EntityWrapper()
.eq("gender", 0)
.like("last_name", "老师")
.or() // SQL: (gender = ? AND last_name LIKE ? OR email LIKE ?)
.like("email", "a")
);
System.out.println(emps);
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee
WHERE (gender = ? AND last_name LIKE ? OR email LIKE ?)
Parameters: 0(Integer), %老师%(String), %a%(String)
可以发现OR只是作为一个查询条件,而orNew则封装了两个子查询。针对本例效果是一致的,但是在某些情况下结果会不一致。
查询性别为女的, 根据age进行排序(asc/desc), 简单分页。
测试代码如下:
List emps = employeeMapper.selectList(
new EntityWrapper()
.eq("gender", 0)
.orderBy("age")//默认升序 可以使用orderAsc
//.orderDesc(Arrays.asList(new String [] {"age"}))
.last("desc limit 1,3")
);
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee
WHERE (gender = ?) ORDER BY age desc limit 1,3
测试代码如下:
@Test
public void testEntityWrapperUpdate() {
Employee employee = new Employee();
employee.setLastName("苍老师");
employee.setEmail("[email protected]");
employee.setGender(0);
employeeMapper.update(employee,
new EntityWrapper()
.eq("last_name", "Tom")
.eq("age", 44)
);
}
更新SQL如下:
Preparing: UPDATE tbl_employee SET last_name=?, email=?, gender=? WHERE (last_name = ? AND age = ?)
Parameters: 苍老师(String), [email protected](String), 0(Integer), Tom(String), 44(Integer)
Updates: 0
测试代码如下:
@Test
public void testEntityWrapperDelete() {
employeeMapper.delete(
new EntityWrapper()
.eq("last_name", "Tom")
.eq("age", 22)
);
}
测试结果如下:
为什么继承了BaseMapper后,不需要写SQL,不需要mapper.xml配置文件,即可使用大多CRUD方法?
以如下代码为例跟踪一下究竟发生了什么:
@Test
public void testEntityWrapperUpdate() {
Employee employee = new Employee();
employee.setLastName("苍老师");
employee.setEmail("[email protected]");
employee.setGender(0);
Integer update = employeeMapper.update(employee,
new EntityWrapper()
.eq("last_name", "Tom")
.eq("age", 44)
);
System.out.println(update);
}
此时的employeeMapper是个代理对象org.apache.ibatis.binding.MapperProxy@7728643a,如下图所示其是一个代理对象。
这里sqlsessionTemplate是sqlsession实现类:
主要属性如下:
继续跟踪其sqlSessionFactory属性,即employeeMapper.sqlSession.sqlSessionFactory:
sqlSessionFactory只有一个属性configuration,其是MybatisConfiguration实例对象,该实例对象有MybatisMapperRegistry注册中心实例对象、 mappedStatements(Configuration$StrictMap
)。那么什么是mappedStatements?我们看一下:
似乎找到了employeeMapper那么多默认功能的来源!项目启动的时候初始化employeeMapper对象时会自动注入这些方法!
那么具体如何注入的呢?我们跟下代码看看。
如下图所示,在启动的时候根据type注入了employeeMapper,在AbstractAutowireCapableBeanFactory类中调用了afterPropertiesSet方法,然后将一系列mapper的方法注入。
那么有两个问题:
1.1 AbstractAutowireCapableBeanFactory.initializeBean
如下所示我们跟踪源码从这里开始,主要跟踪其invokeInitMethods方法。
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction
1.2 AbstractAutowireCapableBeanFactory.invokeInitMethods
方法源码如下所示:
#这里的bean 是wrappedBean
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction
在bean属性被设置后告诉bean的bean factory是什么并检测其是否实现了InitializingBean接口或者自定义了一个init method。如果是,则尝试调用其afterPropertiesSet以及invokeCustomInitMethod(调用自定义的init method方法)。
为什么要从这里开始?
这里是bean实例化过程的一步,执行栈信息如下(可以看到是spring初始化bean的过程):
这里方法参数具体值如下:
具体看下此时的包装bean:
MapperFactoryBean类继承图如下:
可以看到其实现了InitializingBean接口,afterPropertiesSet方法会在bean属性设置完后进行一系列操作。关于该接口可以参考博文:Spring中bean的初始化和销毁几种实现方式详解
我们跟踪进afterPropertiesSet方法,这里是DaoSupport.afterPropertiesSet方法,源码如下:
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
//从这里跟进去 参考1.3
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
1.3 MapperFactoryBean.checkDaoConfig
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
//获取到configuration --MyBatisConfiguration实例
Configuration configuration = getSqlSession().getConfiguration();
//如果该configuration没有该mapperInterface,则进行addMapper操作
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
//这个是核心,参考1.4
configuration.addMapper(this.mapperInterface);
}
//...
}
}
}
configuration.addMapper(this.mapperInterface);直接调用了mybatisMapperRegistry的addMapper方法:
public void addMapper(Class type) {
this.mybatisMapperRegistry.addMapper(type);
}
1.4 MybatisMapperRegistry.addMapper
public void addMapper(Class type) {
//判断是否接口
if (type.isInterface()) {
//判断是否已经存在
if (this.hasMapper(type)) {
return;
}
boolean loadCompleted = false;
try {
//type:new MapperProxyFactory(type) 放到knownMappers这个hashmap中
this.knownMappers.put(type, new MapperProxyFactory(type));
//获取一个解析器,进行对象方法注入
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(this.config, type);
//重点来了 参考1.5
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
这里我们重点看下MybatisMapperAnnotationBuilder 是什么,其构造函数如下:
public MybatisMapperAnnotationBuilder(Configuration configuration, Class> type) {
super(configuration, type);
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
this.sqlAnnotationTypes.add(Select.class);
this.sqlAnnotationTypes.add(Insert.class);
this.sqlAnnotationTypes.add(Update.class);
this.sqlAnnotationTypes.add(Delete.class);
this.sqlProviderAnnotationTypes.add(SelectProvider.class);
this.sqlProviderAnnotationTypes.add(InsertProvider.class);
this.sqlProviderAnnotationTypes.add(UpdateProvider.class);
this.sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
其首先调用了父类MapperAnnotationBuilder的构造函数,如下所示:
public MapperAnnotationBuilder(Configuration configuration, Class> type) {
//资源符号路径转换
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
this.sqlAnnotationTypes.add(Select.class);
this.sqlAnnotationTypes.add(Insert.class);
this.sqlAnnotationTypes.add(Update.class);
this.sqlAnnotationTypes.add(Delete.class);
this.sqlProviderAnnotationTypes.add(SelectProvider.class);
this.sqlProviderAnnotationTypes.add(InsertProvider.class);
this.sqlProviderAnnotationTypes.add(UpdateProvider.class);
this.sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
调用完父类的构造函数后,当前MybatisMapperAnnotationBuilder如下:
然后继续走MybatisMapperAnnotationBuilder的构造函数,最终如下所示:
1.5 MybatisMapperAnnotationBuilder.parse
public void parse() {
//拿到接口完整路径
String resource = this.type.toString();
if (!this.configuration.isResourceLoaded(resource)) {
//获取mapper.xml文件 这个很有意思,参考1.6
this.loadXmlResource();
this.configuration.addLoadedResource(resource);
//设置当前的Namespace--命名空间
this.assistant.setCurrentNamespace(this.type.getName());
this.parseCache();
this.parseCacheRef();
//获取接口的方法
Method[] methods = this.type.getMethods();
//如果是BaseMapper的子类,则会调用inspectInject方法
if (BaseMapper.class.isAssignableFrom(this.type)) {
//获取注入器进行注入 参考1.7
GlobalConfigUtils.getSqlInjector(this.configuration).inspectInject(this.assistant, this.type);
}
Method[] arr$ = methods;
int len$ = methods.length;
for(int i$ = 0; i$ < len$; ++i$) {
Method method = arr$[i$];
try {
if (!method.isBridge()) {
this.parseStatement(method);
}
} catch (IncompleteElementException var8) {
this.configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
this.parsePendingMethods();
}
这里可以看下获取的接口的方法:
对比下我们在employeeMapper里面看到的方法(可以猜测,将会把这些方法一个个注入进去,具体注入什么进去到哪里呢?):
1.6 MybatisMapperAnnotationBuilder.loadXmlResource
private void loadXmlResource() {
if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) {
// 这里xmlResource 是com/jane/mp/mapper/EmployeeMapper.xml
String xmlResource = this.type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
//尝试读取文件流 具体可参考
inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource);
} catch (IOException var4) {
;
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName());
xmlParser.parse();
}
}
}
1.7 AutoSqlInjector.inspectInject
public void inspectInject(MapperBuilderAssistant builderAssistant, Class> mapperClass) {
//interface com.jane.mp.mapper.EmployeeMapper
String className = mapperClass.toString();
Set mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
//如果缓存中没有className 则调用inject,然后放到mapperRegistryCache
if (!mapperRegistryCache.contains(className)) {
//参考1.8
this.inject(builderAssistant, mapperClass);
//将会放到GlobalConfiguration.mapperRegistryCache中(Set),下次即可从其获取
mapperRegistryCache.add(className);
}
}
1.8 AutoSqlInjector.inject(MapperBuilderAssistant builderAssistant, Class> mapperClass)
public void inject(MapperBuilderAssistant builderAssistant, Class> mapperClass) {
this.configuration = builderAssistant.getConfiguration();
this.builderAssistant = builderAssistant;
this.languageDriver = this.configuration.getDefaultScriptingLanguageInstance();
Class> modelClass = this.extractModelClass(mapperClass);
if (null != modelClass) {
if (this.getGlobalConfig().isSqlParserCache()) {
PluginUtils.initSqlParserInfoCache(mapperClass);
}
//获取表信息
TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
this.injectSql(builderAssistant, mapperClass, modelClass, table);
}
}
1.9 AutoSqlInjector.inject(MapperBuilderAssistant builderAssistant, Class> mapperClass, Class> modelClass, TableInfo table)
protected void injectSql(MapperBuilderAssistant builderAssistant, Class> mapperClass, Class> modelClass, TableInfo table) {
/**
* 表信息包含主键,注入主键相关方法
*/
if (StringUtils.isNotEmpty(table.getKeyProperty())) {
/** 删除 */
this.injectDeleteByIdSql(false, mapperClass, modelClass, table);
this.injectDeleteByIdSql(true, mapperClass, modelClass, table);
/** 修改 */
this.injectUpdateByIdSql(true, mapperClass, modelClass, table);
this.injectUpdateByIdSql(false, mapperClass, modelClass, table);
/** 查询 */
this.injectSelectByIdSql(false, mapperClass, modelClass, table);
this.injectSelectByIdSql(true, mapperClass, modelClass, table);
} else {
// 表不包含主键时 给予警告
logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",
modelClass.toString()));
}
/**
* 正常注入无需主键方法
*/
/** 插入 */
this.injectInsertOneSql(true, mapperClass, modelClass, table);
this.injectInsertOneSql(false, mapperClass, modelClass, table);
/** 删除 */
this.injectDeleteSql(mapperClass, modelClass, table);
this.injectDeleteByMapSql(mapperClass, table);
/** 修改 */
this.injectUpdateSql(mapperClass, modelClass, table);
/** 修改 (自定义 set 属性) */
this.injectUpdateForSetSql(mapperClass, modelClass, table);
/** 查询 */
this.injectSelectByMapSql(mapperClass, modelClass, table);
this.injectSelectOneSql(mapperClass, modelClass, table);
this.injectSelectCountSql(mapperClass, modelClass, table);
this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table);
this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table);
this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table);
this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table);
this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table);
/** 自定义方法 */
this.inject(configuration, builderAssistant, mapperClass, modelClass, table);
}
① 调用AutoSqlInjector.injectDeleteByIdSql方法:
protected void injectDeleteByIdSql(boolean batch, Class> mapperClass, Class> modelClass, TableInfo table) {
SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID;
String idStr = table.getKeyProperty();
//判断是否批量删除,若是则封装foreach 语句
if (batch) {
sqlMethod = SqlMethod.DELETE_BATCH_BY_IDS;
StringBuilder ids = new StringBuilder();
ids.append("\n");
ids.append("#{item}");
ids.append("\n ");
idStr = ids.toString();
}
//SQL格式化
String sql = String.format(sqlMethod.getSql(), table.getTableName(), table.getKeyColumn(), idStr);
//根据SQL创建sqlSource
SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
//封装一个个MappedStatement放到
this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource);
}
sqlMethod.getSql如下:
② this.addDeleteMappedStatement
AutoSqlInjector.addDeleteMappedStatement方法源码如下:
public MappedStatement addDeleteMappedStatement(Class> mapperClass, String id, SqlSource sqlSource) {
return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, (Class)null, (String)null, Integer.class, new NoKeyGenerator(), (String)null, (String)null);
}
其调用了addMappedStatement方法:
public MappedStatement addMappedStatement(Class> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class> parameterClass, String resultMap, Class> resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) {
String statementName = mapperClass.getName() + "." + id;
//判断是否已经存在
if (this.hasMappedStatement(statementName)) {
System.err.println("{" + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL.");
return null;
} else {
boolean isSelect = false;
//判断是否查询语句
if (sqlCommandType == SqlCommandType.SELECT) {
isSelect = true;
}
//从这里继续跟 参考③
return this.builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, (Integer)null, (Integer)null, (String)null, parameterClass, resultMap, resultType, (ResultSetType)null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, this.configuration.getDatabaseId(), this.languageDriver, (String)null);
}
}
③ MapperBuilderAssistant.addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class> parameterType, String resultMap, Class> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets)
是的,这个方法的参数多到了难以想象(xml中标签属性都有)。主要参数如下所示:
方法源码如下:
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class> parameterType, String resultMap, Class> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
if (this.unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
} else {
//给方法名拼接上name space: com.jane.mp.mapper.EmployeeMapper.deleteById
id = this.applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//获取statementBuilder
org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
//获取参数对象
ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
//从这里跟进去,参考④
this.configuration.addMappedStatement(statement);
return statement;
}
}
④ MybatisConfiguration.addMappedStatement(MappedStatement ms)
public void addMappedStatement(MappedStatement ms) {
logger.debug("addMappedStatement: " + ms.getId());
//判断是否刷新,如果是就移除掉
if (GlobalConfigUtils.isRefresh(ms.getConfiguration())) {
this.mappedStatements.remove(ms.getId());
} else if (this.mappedStatements.containsKey(ms.getId())) {
logger.error("mapper[" + ms.getId() + "] is ignored, because it's exists, maybe from xml file");
return;
}
//从这里继续跟进去
super.addMappedStatement(ms);
}
留意日志发现其打印了addMappedStatement: com.jane.mp.mapper.EmployeeMapper.deleteById
父类Configuration的addMappedStatement方法很简单:
public void addMappedStatement(MappedStatement ms) {
//以 ms.getId():ms 放入MybatisConfiguration的mappedStatements属性中
this.mappedStatements.put(ms.getId(), ms);
}
如下可以看到刚添加的deleteById :
同理注入其他:
在上面的图示中我们还看到了许多SqlRunner.XXX 方法,那么这些是什么时候注入的呢?是在实例化MybatisSqlSessionFactoryBean过程中注入的。这又是一个漫长的过程,参考【7】
如下图所示,我们这里仍旧从AbstractAutowireCapableBeanFactory.invokeInitMethods方法中的afterPropertiesSet方法开始开始:
方法源码如下:
public void afterPropertiesSet() throws Exception {
//几个断言
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//创建一个sqlsessionfactory,从这里跟进去②
this.sqlSessionFactory = buildSqlSessionFactory();
}
这个是核心方法,用来创建Configuration 实例并根据configuration实例获取sqlsessionFactory实例。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
Configuration configuration;
// TODO 加载自定义 MybatisXmlConfigBuilder
MybatisXMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
//这里,如果configLocation 不为null,则获取MybatisXMLConfigBuilder并得到Configuration
} else if (this.configLocation != null) {
xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
// TODO 使用自定义配置,注意这里直接new了一个MybatisConfiguration对象
configuration = new MybatisConfiguration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
if (hasLength(this.typeAliasesPackage)) {
// TODO 支持自定义通配符
String[] typeAliasPackageArray;
if (typeAliasesPackage.contains("*") && !typeAliasesPackage.contains(",")
&& !typeAliasesPackage.contains(";")) {
typeAliasPackageArray = PackageHelper.convertTypeAliasesPackage(typeAliasesPackage);
} else {
typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
}
if (typeAliasPackageArray == null) {
throw new MybatisPlusException("not find typeAliasesPackage:" + typeAliasesPackage);
}
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
// TODO 自定义枚举类扫描处理
if (hasLength(this.typeEnumsPackage)) {
Set classes = null;
if (typeEnumsPackage.contains("*") && !typeEnumsPackage.contains(",")
&& !typeEnumsPackage.contains(";")) {
classes = PackageHelper.scanTypePackage(typeEnumsPackage);
} else {
String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
if (typeEnumsPackageArray == null) {
throw new MybatisPlusException("not find typeEnumsPackage:" + typeEnumsPackage);
}
classes = new HashSet();
for (String typePackage : typeEnumsPackageArray) {
classes.addAll(PackageHelper.scanTypePackage(typePackage));
}
}
// 取得类型转换注册器
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
for (Class cls : classes) {
if (cls.isEnum()) {
if (IEnum.class.isAssignableFrom(cls)) {
typeHandlerRegistry.register(cls.getName(), com.baomidou.mybatisplus.handlers.EnumTypeHandler.class.getCanonicalName());
} else {
// 使用原生 EnumOrdinalTypeHandler
typeHandlerRegistry.register(cls.getName(), org.apache.ibatis.type.EnumOrdinalTypeHandler.class.getCanonicalName());
}
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 设置元数据相关
GlobalConfigUtils.setMetaData(dataSource, globalConfig);
SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration);
// TODO SqlRunner
SqlRunner.FACTORY = sqlSessionFactory;
// TODO 缓存 sqlSessionFactory
globalConfig.setSqlSessionFactory(sqlSessionFactory);
// TODO 设置全局参数属性
globalConfig.signGlobalConfig(sqlSessionFactory);
if (!isEmpty(this.mapperLocations)) {
if (globalConfig.isRefresh()) {
//TODO 设置自动刷新配置 减少配置
new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
2, true);
}
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 解析mapper.xml中的配置,并将CRUD等创建为一个个mappedstatement对象放到configuration中
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
return sqlSessionFactory;
}
其他咱们先不管,由于本文测试的时候使用了mybatis-config.xml,所以这里会走到xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
,我们继续跟踪其构造函数:
public MybatisXMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
#从这里继续跟踪进去
private MybatisXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//TODO 自定义 Configuration
super(new MybatisConfiguration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
#可以看到首先调用了 super(new MybatisConfiguration());
#还是走到了new MybatisConfiguration()
那么我们看下MybatisConfiguration这个类,其实MyBatis 或者 MP全局配置对象,其属性如下:
public class MybatisConfiguration extends Configuration {
private static final Log logger = LogFactory.getLog(MybatisConfiguration.class);
/**
* Mapper 注册
*/
public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
/**
* 初始化调用
*/
public MybatisConfiguration() {
this.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
logger.debug("Mybatis-plus init success.");
}
//...
}
方法列表如下:
可以看到该类主要做三件事情:获取一个MybatisMapperRegistry注册中心、往mappedStatements中添加MappedStatement对象以及往mybatisMapperRegistry注册中心添加mapper: mybatisMapperRegistry.addMappers(packageName, superType);
需要擦亮眼睛的是,public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
这是一个用final修饰的变量,也就是常量。那么常量是在何时被赋值的呢?
静态变量和常量(如果有初始值)在类加载过程中就已经初始化了,在编译的时候存储在class的常量池中,在加载后又被放进方法区的运行时常量池中。
这里常量mybatisMapperRegistry被赋予了初始值,那么在类MybatisConfiguration加载过程中的编译阶段就会将初始值存入constantValue属性(class文件的常量池)中,在准备阶段就将constantValue的值赋给mybatisMapperRegistry。
这里对类加载、对象创建过程不懂的同学可以参考博文:Java类的加载过程详解(加载验证准备解析初始化使用卸载)、https://blog.csdn.net/J080624/article/details/82116500
ok,咱们继续回去看下MybatisMapperRegistry。
类源码如下:
public class MybatisMapperRegistry extends MapperRegistry {
//如果一个mapper已经被加载过,将会被放到knownMappers 中
private final Map, MapperProxyFactory>> knownMappers = new HashMap<>();
//MybatisConfiguration实例对象的引用
private final Configuration config;
public MybatisMapperRegistry(Configuration config) {
super(config);
this.config = config;
// TODO注入SqlRunner
GlobalConfigUtils.getSqlInjector(config).injectSqlRunner(config);
}
@Override
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
@Override
public boolean hasMapper(Class type) {
return knownMappers.containsKey(type);
}
@Override
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
// throw new BindingException("Type " + type +
// " is already known to the MybatisPlusMapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// TODO 自定义无 XML 注入---解析mapper中方法上面使用了注解SQL
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/**
* @since 3.2.2
*/
@Override
public Collection> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
}
核心在这里:
GlobalConfigUtils.getSqlInjector(config).injectSqlRunner(config);
这里将会调用AutoSqlInjector.injectSqlRunner(Configuration configuration)注入sqlrunner相关的mappedStatement对象。
@Override
public void injectSqlRunner(Configuration configuration) {
this.configuration = configuration;
this.languageDriver = configuration.getDefaultScriptingLanguageInstance();
initSelectList();
initSelectObjs();
initInsert();
initUpdate();
initDelete();
initCount();
}
同样是在MybatisSqlSessionFactoryBean实例化过程中。代码如下实例:
if (!isEmpty(this.mapperLocations)) {
if (globalConfig.isRefresh()) {
//TODO 设置自动刷新配置 减少配置
new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2,
2, true);
}
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 获取一个XMLMapperBuilder 然后解析那些mapper.xml配置文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
}
//...
}
}
//...
方法源码如下所示:
public void parse() {
//如果当前mapper.xml配置文件没有加载过,就会解析并加载
if (!configuration.isResourceLoaded(resource)) {
//从这里跟进去,参考2
configurationElement(parser.evalNode("/mapper"));
//解析完xml文件后,将该String resource放到loadedResources这个hashset中
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
方法源码如下:
private void configurationElement(XNode context) {
try {
//获取namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//解析那些CRUD标签,并创建一个个MappedStatement,然后放到MappedStatements这个map中 参考3
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
List list
)如下图所示,这里每一个NODE就对应了了一条SQL。
其有继续调用了如下方法:
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
//获取一个XMLStatementBuilder ,然后解析结点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//这里参考4
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
这个方法获取了标签(insert|update|delete|select)的相关属性,并在最后调用了builderAssistant这个助手将其添加MybatisConfiguration的 protected final Map
:
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
Class> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
//看到这个是不是很熟悉?ok下面就不再重复过程了
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
如下所示:
public interface EmployeeMapper extends BaseMapper {
@Select("select * from tbl_employee")
List selectEmployee();
}
注解在哪里被解析然后注入?
① MapperFactoryBean.checkDaoConfig
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
这个核心入口configuration.addMapper(this.mapperInterface);
,会调用如下方法:
@Override
public void addMapper(Class type) {
mybatisMapperRegistry.addMapper(type);
}
② MybatisMapperRegistry.addMapper(Class
)
@Override
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
// throw new BindingException("Type " + type +
// " is already known to the MybatisPlusMapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
//这里会对方法上面注解进行解析
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
③ MybatisMapperAnnotationBuilder.parse
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
// TODO 注入 CURD 动态 SQL (应该在注解之前注入)
if (BaseMapper.class.isAssignableFrom(type)) {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
在sqlSessionFactoryBean实例化过程中,会调用XMLMapperBuilder的parse方法。
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//解析那些mapper.xml配置文件
configurationElement(parser.evalNode("/mapper"));
//标记资源已经被解析
configuration.addLoadedResource(resource);
//这里这里这里!
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
这里我们重点看下bindMapperForNamespace方法:
private void bindMapperForNamespace() {
//获取命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
//如果没有被解析过就执行如下方法
configuration.addLoadedResource("namespace:" + namespace);
//这里是核心入口
configuration.addMapper(boundType);
}
}
}
}
configuration.addMapper(boundType);
会调用MybatisMapperRegistry.addMapper(Class
:
@Override
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// TODO 自定义无 XML 注入---解析方法上面的注解!!!
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
当然,因为首先会做判断是否已经解析。所以当你配置了mapperLocations且其不为空的时候,在实例化sqlSessionFactoryBean的过程中就会完成mappedStatement对象的注入。当你实例化employeeMapper时调用其包装类的afterPropertiesSet方法以及其后一系列过程最终走到上面方法的时候,就不会再次注入!
为什么要看这个方法呢?上面我们分析了没有mapper.xml、有mapper.xml(配置了mapperLocations属性)的情况,但是如果有mapper.xml但是没有配置mapperLocations属性呢?
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//在这里,会尝试加载接口下面的同名xml文件进行解析!!!
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
Method[] methods = type.getMethods();
// TODO 注入 CURD 动态 SQL (应该在注解之前注入)
if (BaseMapper.class.isAssignableFrom(type)) {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
MybatisMapperAnnotationBuilder.loadXmlResource
private void loadXmlResource() {
//判断xml是否已经加载过
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//接口同名xml文件
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
//获取XMLMapperBuilder进行解析
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
也就是说即使你调用了MybatisMapperAnnotationBuilder而非MapperXmlBuilder的parse方法时,同样给xml了一个解析机会!当然,当你调用MapperXmlBuilder的parse方法时,在bindMapperForNamespace()
也给了注解解析的机会!
其是Configuration的一个属性,类型为StrictMap:
protected final Map mappedStatements =
new StrictMap("Mapped Statements collection");
一 个 MappedStatement对象对应 Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条 SQL语句。
其主要属性如下:
看到这些属性是否很熟悉?我们对照下mapper.xml中一个select标签的属性:
这是一个SQL模板的枚举类,根据这些模板解析出一个个具体的SQL
public enum SqlMethod {
/**
* 插入
*/
INSERT_ONE("insert", "插入一条数据(选择字段插入)", ""),
INSERT_ONE_ALL_COLUMN("insertAllColumn", "插入一条数据(全部字段插入)", ""),
/**
* 删除
*/
DELETE_BY_ID("deleteById", "根据ID 删除一条数据", ""),
DELETE_BY_MAP("deleteByMap", "根据columnMap 条件删除记录", ""),
DELETE("delete", "根据 entity 条件删除记录", ""),
DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量删除数据", ""),
/**
* 逻辑删除
*/
LOGIC_DELETE_BY_ID("deleteById", "根据ID 逻辑删除一条数据", ""),
LOGIC_DELETE_BY_MAP("deleteByMap", "根据columnMap 条件逻辑删除记录", ""),
LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", ""),
LOGIC_DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量逻辑删除数据", ""),
/**
* 修改
*/
UPDATE_BY_ID("updateById", "根据ID 选择修改数据", ""),
UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 修改全部数据", ""),
UPDATE("update", "根据 whereEntity 条件,更新记录", ""),
UPDATE_FOR_SET("updateForSet", "根据 whereEntity 条件,自定义Set值更新记录", ""),
/**
* 逻辑删除 -> 修改
*/
LOGIC_UPDATE_BY_ID("updateById", "根据ID 修改数据", ""),
LOGIC_UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 选择修改数据", ""),
/**
* 查询
*/
SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s}"),
SELECT_BY_MAP("selectByMap", "根据columnMap 查询一条数据", ""),
SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", ""),
SELECT_ONE("selectOne", "查询满足条件一条数据", ""),
SELECT_COUNT("selectCount", "查询满足条件总记录数", ""),
SELECT_LIST("selectList", "查询满足条件所有数据", ""),
SELECT_PAGE("selectPage", "查询满足条件所有数据(并翻页)", ""),
SELECT_MAPS("selectMaps", "查询满足条件所有数据", ""),
SELECT_MAPS_PAGE("selectMapsPage", "查询满足条件所有数据(并翻页)", ""),
SELECT_OBJS("selectObjs", "查询满足条件所有数据", ""),
/**
* 逻辑删除 -> 查询
*/
LOGIC_SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s} %s"),
LOGIC_SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", "");
private final String method;
private final String desc;
private final String sql;
SqlMethod(final String method, final String desc, final String sql) {
this.method = method;
this.desc = desc;
this.sql = sql;
}
public String getMethod() {
return this.method;
}
public String getDesc() {
return this.desc;
}
public String getSql() {
return this.sql;
}
}