前言
这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到过,因此自己印象很深刻。
另外,估计不少同学应该也注意到了,DAO 接口的全路径名和 XML 文件中的 SQL 的 namespace + id 是一样的。其实,这也是建立关联的根本原因。
正文
当一个项目中使用了 Spring 和 Mybatis 时,通常会有以下配置。当然现在很多项目应该都是 SpringBoot 了,可能没有以下配置,但是究其底层原理都是类似的,无非是将扫描 bean 等一些工作通过注解来实现。
通常我们还会有 DAO 类和 对用的 mapper 文件,如下。
package com.yibo.open.mapper;
import com.yibo.open.po.UserPO;
public interface UserPOMapper {
UserPO queryByPrimaryKey(Integer id);
}
1、解析 MapperScannerConfigurer
MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor,会在 Spring 构建 IoC容器的早期被调用重写的 postProcessBeanDefinitionRegistry 方法。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
// 新建一个ClassPathMapperScanner,并填充相应属性
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
// 注册Filter,因为上面构造函数我们没有使用默认的Filter,
// 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
scanner.registerFilters();
// 扫描basePackage,basePackage可通过",; \t\n"来填写多个,
// ClassPathMapperScanner重写了doScan方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
}
注册 Filter
作用:什么类型的Mapper将会留下来。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public void registerFilters() {
boolean acceptAllInterfaces = true;
// 1.如果指定了注解,则将注解添加到includeFilters
if (this.annotationClass != null) {
this.addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// 2.如果指定了标记接口,则将标记接口添加到includeFilters,
// 但这边重写了matchClassName方法,并返回了false,
// 相当于忽略了标记接口上的匹配项,所以该参数目前相当于没有任何作用
if (this.markerInterface != null) {
this.addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
protected boolean matchClassName(String className) {
return false;
}
});
acceptAllInterfaces = false;
}
// 3.如果没有指定annotationClass和markerInterface,则
// 添加默认的includeFilters,直接返回true,接受所有类
if (acceptAllInterfaces) {
this.addIncludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
// 4.添加默认的excludeFilters,排除以package-info结尾的类
this.addExcludeFilter(new TypeFilter() {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info") ? true : metadataReader.getAnnotationMetadata().hasAnnotation("tk.mybatis.mapper.annotation.RegisterMapper");
}
});
}
}
通常我们都不会指定 annotationClass 和 markerInterface,也就是会添加默认的 Filter,相当于会接受除了 package-info 结尾的所有类。因此,basePackage 包下的类不需要使用 @Component 注解或 XML 中配置 bean 定义,也会被添加到 IoC 容器中。
扫描 basePackage
这边会走到 ClassPathBeanDefinitionScanner(ClassPathMapperScanner 的父类),然后在执行 “doScan(basePackages)” 时回到 ClassPathMapperScanner 重写的方法doScan
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
private final BeanDefinitionRegistry registry;
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//调用同类方法进行扫描,并将basePackages下的class都封装成BeanDefinitionHolder,并注册进Spring容器的BeanDefinition
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
protected Set doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set beanDefinitions = new LinkedHashSet<>();
//遍历basePackages进行扫描
for (String basePackage : basePackages) {
//找出匹配的类
Set candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
//封装成BeanDefinitionHolder 对象
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//将BeanDefinition对象注入spring的BeanDefinitionMap中,后续getBean时,就是从BeanDefinitionMap获取对应的BeanDefinition对象,取出其属性进行实例化Bean
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
}
我们重点看下doScan方法,获取basePackages下的所有Class,并将其生成BeanDefinition,注入spring的BeanDefinitionMap中,也就是Class的描述类,在调用getBean方法时,获取BeanDefinition进行实例化。此时,所有的Mapper接口已经被生成了BeanDefinition。
小结,解析 MapperScannerConfigurer 主要是做了几件事:
- 1、新建扫描器 ClassPathMapperScanner。
- 2、使用 ClassPathMapperScanner 扫描注册 basePackage 包下的所有 bean。
- 3、将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate属性。
‘
2、解析 SqlSessionFactoryBean
对于 SqlSessionFactoryBean 来说,实现了2个接口,InitializingBean 和 FactoryBean,看过Spring 文章的同学应该对这2个接口不会陌生,简单来说:
- 1、FactoryBean可以自己定义创建实例对象的方法,只需要实现它的 getObject() 方法。
- 2、InitializingBean则是会在 bean 初始化阶段被调用。
SqlSessionFactoryBean 重写这两个接口的部分方法代码如下,核心代码就一个方法—— buildSqlSessionFactory()
。
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// 如果之前没有构建,则这边也会调用afterPropertiesSet进行构建操作
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
// 构建sqlSessionFactory
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
}
buildSqlSessionFactory()
主要做了几件事:
- 1、对我们配置的参数进行相应解析。
- 2、使用配置的参数构建一个 Configuration。
- 3、使用 Configuration 新建一个 DefaultSqlSessionFactory。
这边的核心内容是对于 mapperLocations 的解析,如下代码。
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
//省略前面代码......
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// mapper处理(最重要)
if (!ObjectUtils.isEmpty(this.mapperLocations)) {
Resource[] var29 = this.mapperLocations;
var27 = var29.length;
for(var5 = 0; var5 < var27; ++var5) {
Resource mapperLocation = var29[var5];
if (mapperLocation != null) {
try {
// 新建XMLMapperBuilder
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
// 解析mapper文件
xmlMapperBuilder.parse();
} catch (Exception var20) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
} 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");
}
// 使用targetConfiguration构建DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
}
解析mapper文件
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final String resource;
public void parse() {
// 如果resource没被加载过才进行加载
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper文件
configurationElement(parser.evalNode("/mapper"));
// 将resource添加到已加载列表
configuration.addLoadedResource(resource);
// 绑定namespace的mapper
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
解析mapper文件
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
// 1.获取namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 2.设置currentNamespace属性
builderAssistant.setCurrentNamespace(namespace);
// 3.解析parameterMap、resultMap、sql等节点
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 4.解析增删改查节点,封装成Statement
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);
}
}
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
// 解析增删改查节点,封装成Statement
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
// 1.构建XMLStatementBuilder
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 2.解析节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
}
这边会一直执行到 statementParser.parseStatementNode();
这边每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL。
statementParser.parseStatementNode()
public class XMLMapperBuilder extends BaseBuilder {
public void parseStatementNode() {
// 1 获得 id 属性,编号。
String id = context.getStringAttribute("id");
// 2 获得 databaseId , 判断 databaseId 是否匹配
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 3 获得各种属性
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");
// 4 获得 lang 对应的 LanguageDriver 对象
LanguageDriver langDriver = getLanguageDriver(lang);
// 5 获得 resultType 对应的类
Class> resultTypeClass = resolveClass(resultType);
// 6 获得 resultSet 对应的枚举值
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 7 获得 statementType 对应的枚举值
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 8 获得 SQL 对应的 SqlCommandType 枚举值
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 9 获得各种属性
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
// 10 创建 XMLIncludeTransformer 对象,并替换 标签相关的内容
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 11 解析 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
// 12 创建 SqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 13 获得 KeyGenerator 对象
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
// 13.1 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 标签配置的
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
// 13.2 其次,根据标签属性的情况,判断是否使用对应的 Jdbc3KeyGenerator 或者 NoKeyGenerator 对象
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys", // 优先,基于 useGeneratedKeys 属性判断
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) // 其次,基于全局的 useGeneratedKe
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 创建 MappedStatement 对象
// 将解析出来的所有参数添加到 mappedStatements 缓存
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
public class MapperBuilderAssistant extends BaseBuilder {
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 (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 1.将id填充上namespace,例如:queryByPrimaryKey变成
// com.yibo.open.mapper.UserPOMapper.queryByPrimaryKey
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 2.使用参数构建MappedStatement.Builder
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
// 3.使用MappedStatement.Builder构建MappedStatement
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 4.将MappedStatement 添加到缓存
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
}
public class Configuration {
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
}
该方法会将节点的属性解析后封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.yibo.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。
bindMapperForNamespace()
绑定namespace的mapper
public class XMLMapperBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
// 1.解析namespace对应的绑定类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 2.boundType不为空,并且configuration还没有添加boundType,
// 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMapper(Class type) {
mapperRegistry.addMapper(type);
}
}
public class MapperRegistry {
private final Map, MapperProxyFactory>> knownMappers = new HashMap, MapperProxyFactory>>();
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将type和以该type为参数构建的MapperProxyFactory作为键值对,
// 放到knownMappers缓存中去
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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
主要是将刚刚解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。
小结,解析 SqlSessionFactoryBean 主要做了几件事:
1、解析处理所有属性参数构建 Configuration ,使用 Configuration 新建 DefaultSqlSessionFactory;
2、解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.yibo.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。
3、将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。
3、解析Mapper 文件
Mapper 文件,也就是 basePackage 指定的包下的文件,也就是上文的 interface UserPOMapper 。
上文 doScan 中说过,basePackage 包下所有 bean 定义的 beanClass 会被设置成 MapperFactoryBean.class,而 MapperFactoryBean 也是 FactoryBean,因此直接看 MapperFactoryBean 的 getObject 方法。
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
public T getObject() throws Exception {
// 1.从父类中拿到sqlSessionTemplate,这边的sqlSessionTemplate也是doScan中添加的属性
// 2.通过mapperInterface获取mapper
return this.getSqlSession().getMapper(this.mapperInterface);
}
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public T getMapper(Class type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
private final Map, MapperProxyFactory>> knownMappers = new HashMap, MapperProxyFactory>>();
public T getMapper(Class type, SqlSession sqlSession) {
// 1.从knownMappers缓存中获取
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 2.新建实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
public T newInstance(SqlSession sqlSession) {
// 1.构造一个MapperProxy
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
// 2.使用MapperProxy来构建实例对象
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
// 使用JDK动态代理来代理要创建的实例对象,InvocationHandler为mapperProxy,
// 因此当我们真正调用时,会走到mapperProxy的invoke方法
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}
这边代码用到的 sqlSessionTemplate、mapperInterface 等都是之前添加的属性。
小结,解析 DAO 文件 主要做了几件事:
1、通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象。
2、通过 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。
4、Mapper 接口被调用
当 Mapper 中的接口被调用时,会走到 MapperProxy 的 invoke 方法。
public class MapperProxy implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// Object的方法执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
//JDK8的默认方法执行
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 获取MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 真正的处理在这里
return mapperMethod.execute(sqlSession, args);
}
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
private MapperMethod cachedMapperMethod(Method method) {
//从缓存中获取mapperMethod
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//缓存中没有则构建一个并放入缓存中
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
mapperMethod.execute(sqlSession, args)
MapperMethod.execute()方法执行
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据命令类型执行来进行相应操作
// 具体的增删改查操作,都有具体的执行
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
这边就比较简单,根据不同的操作类型执行相应的操作,最终将结果返回
这边的 command 是上文 new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())
时创建的。
增删改查
public class DefaultSqlSession implements SqlSession {
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
// 从mappedStatements缓存拿到对应的MappedStatement对象,执行更新操作
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
}
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
//select,以executeForMany为例
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
// 1.参数转换成sql命令参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
// 2.执行查询操作
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
// 3.处理返回结果
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
}
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
@Override
public List selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public List selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从mappedStatements缓存中拿到对应的MappedStatement对象,执行查询操作
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
可以看出,最终都是从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行相应的操作。
这边的增删改查不是直接调用 SqlSession 中的方法,而是调用 SqlSessionTemplate 中的方法,继而通过 sqlSessionProxy 来调用 SqlSession 中的方法。SqlSessionTemplate 中的方法主要是通过 sqlSessionProxy 做了一层动态代理,基本没差别。
总结
整个流程主要是以下几个核心步骤:
1、扫描注册 basePackage 包下的所有 bean,将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean、为 MapperFactoryBean 添加 sqlSessionFactory 和 sqlSessionTemplate属性。
2、解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,key 为 id,例如:com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey,value 为 MappedStatement。并且将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,key 为 namespace 对应的 class,value 为 MapperProxyFactory。
3、创建 DAO 的 bean 时,通过 mapperInterface 从 knownMappers 缓存中获取到 MapperProxyFactory 对象,通过 JDK 动态代理创建 MapperProxyFactory 实例对象,InvocationHandler 为 MapperProxy。
4、DAO 中的接口被调用时,通过动态代理,调用 MapperProxy 的 invoke 方法,最终通过 mapperInterface 从 mappedStatements 缓存中拿到对应的 MappedStatement,执行相应的操作。
参考:
https://zhuanlan.zhihu.com/p/140087414