当前位置 : 主页 > 编程语言 > java >

Mybatis源码分析——Mapper接口和XML文件里的SQL是如何建立关系的?

来源:互联网 收集:自由互联 发布时间:2022-10-14
前言 这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到过,因此自己印象很深刻。 另外,估计不少同学应该也注意到了,DAO 接口的全路径名和 XML 文件中的 SQL 的 n

前言

这是 mybatis 比较常问到的面试题,我自己在以前的面试过程中被问到过,因此自己印象很深刻。

另外,估计不少同学应该也注意到了,DAO 接口的全路径名和 XML 文件中的 SQL 的 namespace + id 是一样的。其实,这也是建立关联的根本原因。

正文

当一个项目中使用了 Spring 和 Mybatis 时,通常会有以下配置。当然现在很多项目应该都是 SpringBoot 了,可能没有以下配置,但是究其底层原理都是类似的,无非是将扫描 bean 等一些工作通过注解来实现。

<!-- DAO接口所在包名,Spring会自动查找其下的类 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--basePackage指定要扫描的包,在此包之下的映射器都会被搜索到。可指定多个包,包与包之间用逗号或分号分隔--> <property name="basePackage" value="com.joonwhee.open.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 自动扫描mapping.xml文件 --> <property name="mapperLocations" value="classpath:config/mapper/*.xml"/> <property name="configLocation" value="classpath:config/mybatis/mybatis-config.xml"/> <!--Entity package --> <property name="typeAliasesPackage" value="com.joonwhee.open.po"/> </bean> <!-- dataSource --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean>

通常我们还会有 DAO 类和 对用的 mapper 文件,如下。

package com.yibo.open.mapper; import com.yibo.open.po.UserPO; public interface UserPOMapper { UserPO queryByPrimaryKey(Integer id); } <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.yibo.open.mapper.UserPOMapper" > <resultMap id="BaseResultMap" type="com.yibo.open.po.UserPO"> <result column="id" property="id" jdbcType="INTEGER" /> <result column="name" property="name" jdbcType="VARCHAR" /> </resultMap> <select id="queryByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer"> select id, name from user where id = #{id,jdbcType=INTEGER} </select> </mapper>

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<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); //遍历basePackages进行扫描 for (String basePackage : basePackages) { //找出匹配的类 Set<BeanDefinition> 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<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { 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<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { 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.eval("/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.eval("cache-ref")); cacheElement(context.eval("cache")); parameterMapElement(context.eval("/mapper/parameterMap")); resultMapElements(context.eval("/mapper/resultMap")); sqlElement(context.eval("/mapper/sql")); // 4.解析增删改查节点,封装成Statement buildStatementFromContext(context.eval("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<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } // 解析增删改查节点,封装成Statement buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> 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。

<select id="queryByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer"> select id, name, password, age from user where id = #{id,jdbcType=INTEGER} </select>

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 对象,并替换 <include /> 标签相关的内容 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. // 11 解析 <selectKey /> 标签 processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> 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 对象。如果存在,意味着是 <selectKey /> 标签配置的 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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("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 <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } } public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public <T> void addMapper(Class<T> 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<T>(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<T> extends SqlSessionDaoSupport implements FactoryBean<T> { 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> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } } public class Configuration { protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 1.从knownMappers缓存中获取 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public T newInstance(SqlSession sqlSession) { // 1.构造一个MapperProxy final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); // 2.使用MapperProxy来构建实例对象 return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> 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<T> 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<T> mapperInterface; private final Map<Method, MapperMethod> 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 <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; // 1.参数转换成sql命令参数 Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { // 2.执行查询操作 result = sqlSession.<E>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 <E> List<E> selectList(String statement) { return this.selectList(statement, null); } @Override public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public <E> List<E> 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

上一篇:Demo:cl_salv_table ALV 弹出框
下一篇:没有了
网友评论