Spring和Mybatis的整合使用及源码解析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

简介
之前分析过Spring的源码,里面涉及到很多Spring的拓展接口,具体的介绍文字在http://blog.csdn.net/lgq2626/article/details/78729368
文章中,文章中介绍了InitializingBean这个接口的是在依赖注入完成之后调用了此接口中的afterPropertiesSet()方法,具体的几个重要的接口方法的执行简单总结下:
首先大致看代码:

    populateBean(beanName, mbd, instanceWrapper);//实例化
    if (exposedObject != null) {
                    exposedObject = initializeBean(beanName, exposedObject, mbd);//处理了BeanPostProcessor实现了接口类的调用 和 init-method配置和InitializingBean实现了接口类的调用
                }

在initializeBean(beanName, exposedObject, mbd)方法中,有几个重要的方法的调用,大致执行顺序为: 1.BeanPostProcessor#postProcessBeforeInitialization()方法
2.InitializingBean#afterPropertiesSet()方法
3.配置文件中init-method()方法
4.BeanPostProcessor#postProcessAfterInitialization()方法

下面开始介绍Mybitis和分析源码:

1.Mybatis在Spring中的使用

Spring和mybatis的整合的pom.xml部分内容如下:

            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>1.2.1</version>
            </dependency>
             <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.2.2</version>
             </dependency>
             <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.41</version>
            </dependency>

Spring和Mybatis整合的配置文件如下:

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />  
            <property name="url" value="jdbc:mysql://localhost:3306/test" />  
            <property name="username" value="xxx"/>  
            <property name="password" value="xxx"/>  
        </bean>  
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
            <property name="dataSource" ref="dataSource"/>  
        </bean>  

        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
            <property name="dataSource" ref="dataSource" />  
            <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
        </bean>  

        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.study.www.dao" />
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        </bean>

有了以上的配置在代码中就可以这样使用mybatis了:

    @autowired
    private TestDao  testDao;

2.Mybitis源码分析

2.1Mybatis读取配置文件并且解析配置文件(SqlSessionFactoryBean类解读)

从上面的配置文件中可以获得在Spring容器中注册了两个bean,一个是SqlSessionFactoryBean Bean,一个是MapperScannerConfigurer Bean。
我们首先分析SqlSessionFactoryBean 源码:
在buildSqlSessionFactory()方法中,
我们看到了有如下重要代码:

    configuration = new Configuration();//Mybatis的大管家,所有的信息都会放在里面,包括plugins插件, environments环境...
     configuration.addInterceptor(plugin);//Mybatis插件
      Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);//Mybatis环境
      configuration.setEnvironment(environment);
       XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                  configuration, mapperLocation.toString(), configuration.getSqlFragments());
              xmlMapperBuilder.parse();//解析xml,下面会重点分析

2.1.1 在parse()方法中,我们首先分析configurationElement方法

     configurationElement(parser.evalNode("/mapper"));
    private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");//得到namespace
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          resultMapElements(context.evalNodes("/mapper/resultMap"));//处理<resultMap>这类标签,然后再MapperBuilderAssistant类的addResultMap()方法中把每个ResultMap对象加到Configuration对象中的resultMaps属性中(中间会把<resultMap>标签中的每一个子标签封装成ResultMapping对象,然后封装成ResultMap对象,最后put到Configuration对象中,id规则为:namespace+<resultMap>的id属性 )
          sqlElement(context.evalNodes("/mapper/sql"));//解析文件中的<sql>标签
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//解析每条sql语句,会在2.1.2分析
        } catch (Exception e) {
          throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
        }
      }

2.1.2分析buildStatementFromContext方法

buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

     public void parseStatementNode() {
        String id = context.getStringAttribute("id");
          //中间省略很多获取属性的代码...
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否是select查询

        // Include Fragments before parsing
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());

        // Parse selectKey after includes,
        // in case if IncompleteElementException (issue #291)
        List<XNode> selectKeyNodes = context.evalNodes("selectKey");
        if (configuration.getDatabaseId() != null) {
          parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());//解析<selectKey>标签
        }
     ...
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);//解析sql下面会在2.1.3分析

        ...
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered, 
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver);//把MappedStatement对象装到Configuration对象的mappedStatements属性中id同样是namespace+标签id
      }

2.1.3 下面分析langDriver.createSqlSource();

     SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    public SqlSource parseScriptNode() {
        //解析动态标签比如if、foreach...每个动态标签都会有不同的handel去处理,如果动态标签嵌套动态标签的话,还会递归去调用parseDynamicTags(XNode node)方法,每一个标签封装成一个sqlNode
        List<SqlNode> contents = parseDynamicTags(context);
        //封装一个MixedSqlNode对象,有一个apply方法,用户解析动态sql
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        return sqlSource;
      }

到这里整个configurationElement(XNode context)方法就分析完了。

2.1.4 在parse()方法中,我们在分析bindMapperForNamespace()方法

我们接着回到parse()方法中看 bindMapperForNamespace();方法,这个方法最重要的解析就是把每个Mapper对象也就是Interface接口放到了configuration对象中
knownMappers.put(type, new MapperProxyFactory(type));
其中key为接口对象的class,value为MapperProxyFactory对象 。

——–到这里,SqlSessionFactoryBean类中重要的方法基本分析完了,若遗漏某个重点,还望大神指出来—–

下面开始MapperScannerConfigurer源码

2.2Mybatis读取配置文件并且解析配置文件(MapperScannerConfigurer类解读)

我们直接看postProcessBeanDefinitionRegistry()方法的 代码块

     scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

2.2.1 doScan()方法解析

解析doScan我们只解析到使用类似@Autowired注解处理过的接口是什么样的对象为止,其他的就是执行时候处理的东西了

    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);//直接调用Spring的东西,递归扫描所有的文件,封装成beandefinition对象,并且把beanName放到DefaultListableBeanFactory容器的beanDefinitionNames属性中,这个比较简单
     definition.setBeanClass(MapperFactoryBean.class);//设置beanDefinition的class
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);//把sqlSessionTemplate属性设置为SqlSessionTemplate对象

到目前为止,我们知道DefaultListableBeanFactory中beanDefinitionMap属性所有key为Mybatis扫描接口的class均为MapperFactoryBean对象,也就是说在依赖注入的时候会反射出一个MapperFactoryBean对象,但是MapperFactoryBean对象,但是MapperFactoryBean实现了FactoryBean接口,那么在IOC的时候就会调用到getObject()方法,在getObject()方法中SqlSession的属性是
SqlSessionTemplate对象(注意:SqlSessionTemplate类的构造方法中把sqlSessionProxy属性设置了代理类,这也是我纠结了好久的一个东西,最后发现在这里进行了代理)。
一直顺着getObject()方法跟进去发现最后getObject()返回的是一个对接口类进行代理的代理对象,实现InvocationHandler接口的对象为MapperProxy对象(这一点也可以从代码debug的时候发现)
20191102100612\_1.png
到目前为止,整个@Autowired注解的类进行IOC的时候得到的对象就完全明确了,下面2.3会分析执行过程

2.2.2 扫描注册注解类解析

这里主要是解析registerAnnotationConfigProcessors()方法,在这个方法中,会注册到容器中一些直接注解的类例如:AutowiredAnnotationBeanPostProcessor类支持@Autowired注解和@value注解…,如果使用Spring的话,一般在解析<context:component-scan>标签的时候就会解析了,这里一般不会执行到。

2.3Mybitis调用时候的执行过程源码解读

上面说到@autowired得到的一个对象就是一个接口的代理对象,并且代理类为MapperProxy,那么就从Mapperproxy类的invoke作为入口,

2.3.1 MapperProxy invoke()方法解析

     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } final MapperMethod mapperMethod = cachedMapperMethod(method);//把接口类,方法,和Configuration封装成MapperMethod对象
        return mapperMethod.execute(sqlSession, args);//下面重点分析
      }

下面会以selectList查询,重点分析(我们只看类似selectList查询)mapperMethod.execute(sqlSession, args);
一直跟进去,跟到executeForMany()方法,发现

     result = sqlSession.<E>selectList(command.getName(), param);

这个地方相当饶了,不了解动态代理的就别看下去了,首先来说,这个sqlSession对象是SqlSessionTemplate对象,点进去之后发现

    return this.sqlSessionProxy.<E> selectList(statement, parameter);//上文分析过这个sqlSessionProxy是代理对象,代理类是SqlSessionInterceptor

我们只能看SqlSessionInterceptor的invoke方法了,
SqlSessionInterceptor#invoke方法主要分为三块:
1.得到session:

    SqlSession sqlSession = getSqlSession(
              SqlSessionTemplate.this.sqlSessionFactory,
              SqlSessionTemplate.this.executorType,
              SqlSessionTemplate.this.exceptionTranslator);

在getSession方法中,有一个openSession调用,这里走的是一个DefaultSqlSessionFactory()对象,因为在解析SqlSessionFactoryBean的时候就返回的是一个DefaultSqlSessionFactory对象,然后这个openSession比较重要的东西就是configuration.newExecutor(tx, execType, autoCommit);方法,这个Executor是mybatis的执行器,
executor有四种:

        SimpleExecutor普通执行器,
        ReuseExecutor处理预编译语句执行器
        BatchExecutor批量执行器
        CachingExecutor缓存执行器

这里newExecutor方法中还有一个重要的配合用户拓展的代interceptorChain.pluginAll(executor);可以实现Interceptor接口,然后封装插件,比如分页插件。这个天getSession()返回一个DefaultSqlSession对象,也就是说这次的sqlSession是DefaultSqlSession的session。到这里,就算是得到了一个sqlSession

2.处理sql并且执行sql

    Object result = method.invoke(sqlSession, args);

这个invoke方法会调到DefaultSqlSession的方法,我们跟进selectList()方法,首先会从Configuration得到一个MappedStatement对象,
然后回调用一个wrapCollection();方法。

    private Object wrapCollection(final Object object) {
        if (object instanceof List) {
          StrictMap<Object> map = new StrictMap<Object>();
          map.put("list", object);//如果是list的话封装为一个key为list,,value为object的map
          return map;
        } else if (object != null && object.getClass().isArray()) {
          StrictMap<Object> map = new StrictMap<Object>();
          map.put("array", object);//解析数组
          return map;
        }
        return object;
      }

然后会通过执行器CachingExecutor执行。

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);//得到sqlbound
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }

这个getBoundsql 解析sql有四种解析器
20191102100612\_2.png

这里解析得到了select * from user where id = ?这种sql。
然后执行query方法,会执行到执行器SimpleExecutor.query(),也就是BaseExecutor的query方法中来,

    if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
          } else {
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);//会走到BaseExecutor的queryFromDatabase方法中去调用simpleexecutor的doQuery方法下面会贴出代码
          }

会一直走到simpleExecutor类的doQuery方法,到这个时候熟悉的jdbc就要来了,

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);//获取一个PreparedStatementHandler对象
          stmt = prepareStatement(handler, ms.getStatementLog());//获取一个jdbc连接,从连接获取一个PreparedStatement对象,并且set值,下面有贴出代码 
          return handler.<E>query(stmt, resultHandler);//最终会调用到PreparedStatementHandler类的query
        } finally {
          closeStatement(stmt);
        }
      }

     private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);//获取jdbc连接对象
        stmt = handler.prepare(connection);//获取一个PreparedStatement对象
        handler.parameterize(stmt);//对占位符?进行set值
        return stmt;
      }

PreparedStatementHandler类的query代码如下

     public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.<E> handleResultSets(ps);//通过FastResultSetHandler的handleResultSets进行返回值装配
      }

到这里整个SqlSessionInterceptor的invoke方法中的 method.invoke(sqlSession, args);就执行完了。

3.关闭session

    closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

到这里整个Spring源码执行流程就已经完了,中间还有好多东西没有分析到,其中包括:jdbcType和javaType的对应,解析${id}这种表达式 …等等。希望能帮到你们,如果其中有什么理解的不到位的地方,还望大神指出,共同学习,进步


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring和Mybatis的整合使用及源码解析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏