mybatis-spring 整合记录

未完待接着往里面更新

参考:
+ mybatis官方文档
+ mybatis-spring
+ 源代码
+ 参考官方源码

  • 入口

         @Test
          public void testMyBatisAPI() {
            session = sqlSessionFactory.openSession();//得到 new DefaultSqlSession(configuration, executor, autoCommit);
            session.getMapper(TestMapper.class).findTest();
            session.close();
    
            assertNoCommit();
            assertSingleConnection();
            assertExecuteCount(1);
          }
    
  • 这里我们看看org.mybatis.spring.SqlSessionFactoryBean是个啥东东:

    • 帮我们完成了框架的初始化工作,使我们能得到一个sqlSession对象
    • 用来创建org.apache.ibatis.session.SqlSessionFactory接口实现对象的一个工厂

    官网代码节选:

    <!--org/mybatis/spring/TestMapper.xml-->
        <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.mybatis.spring.TestMapper">
    
    <select id="findTest" resultType="int">
        SELECT<include refid="includedSql"/>
    </select>
    
    <sql id="includedSql">1</sql>
    
    <!-- invalid SQL used to test exception translation -->
    <select id="findFail" resultType="int">
        SELECT 'fail'
    </select>
    
    <insert id="insertTest">
        INSERT #{test} INTO test
    </insert>
    
    <insert id="insertFail">
        INSERT fail
    </insert>
    </mapper>
    
        package org.mybatis.spring;
        public abstract class AbstractMyBatisSpringTest {
    
          protected static PooledMockDataSource dataSource = new PooledMockDataSource();
    
          protected static SqlSessionFactory sqlSessionFactory;
    
          protected static ExecutorInterceptor executorInterceptor = new ExecutorInterceptor();
    
          protected static DataSourceTransactionManager txManager;
    
          protected static PersistenceExceptionTranslator exceptionTranslator;
    
          protected MockConnection connection;
    
          protected MockConnection connectionTwo;
    
          @BeforeClass
          public static void setupBase() throws Exception {
            // create an SqlSessionFactory that will use SpringManagedTransactions
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();//创建实例,默认的构造器
            factoryBean.setMapperLocations(new Resource[] { new ClassPathResource("org/mybatis/spring/TestMapper.xml") });//存储mapper文件集合
            // note running without SqlSessionFactoryBean.configLocation set => default configuration
            factoryBean.setDataSource(dataSource);//存储数据源
            factoryBean.setPlugins(new Interceptor[] { executorInterceptor });//
    
            exceptionTranslator = new MyBatisExceptionTranslator(dataSource, true);
    
            sqlSessionFactory = factoryBean.getObject();
    
            txManager = new DataSourceTransactionManager(dataSource);
      }
    
        package org.mybatis.spring;
    
        public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    
              private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
    
              private Resource configLocation;//全局配置文件的位置
    
              private Configuration configuration;//全局配置对象org.apache.ibatis.session.Configuration
    
              private Resource[] mapperLocations;////存储mapper文件集合
    
              private DataSource dataSource;//存储数据源
    
              private TransactionFactory transactionFactory;
    
              private Properties configurationProperties;
    
              private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    
              private SqlSessionFactory sqlSessionFactory;//org.apache.ibatis.session.SqlSessionFactory
    
              //EnvironmentAware requires spring 3.1
              private String environment = SqlSessionFactoryBean.class.getSimpleName();
    
              private boolean failFast;
    
              private Interceptor[] plugins;//存储插件集合
    
              private TypeHandler<?>[] typeHandlers;
    
              private String typeHandlersPackage;
    
              private Class<?>[] typeAliases;
    
              private String typeAliasesPackage;
    
              private Class<?> typeAliasesSuperType;
    
              //issue #19. No default provider.
              private DatabaseIdProvider databaseIdProvider;
    
              private Class<? extends VFS> vfs;
    
              private ObjectFactory objectFactory;
    
              private ObjectWrapperFactory objectWrapperFactory;
    
              /**
                org.springframework.beans.factory.FactoryBean的实现方法SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>,用于产出SqlSessionFactory
               */
              @Override
              public SqlSessionFactory getObject() throws Exception {
                if (this.sqlSessionFactory == null) {//第一次调用时为空
                //初始化
                  afterPropertiesSet();
                }
            //返回this.sqlSessionFactory实例(单例)
                return this.sqlSessionFactory;
              }
    
        //该类实现的InitializingBean接口,那么先看看这个void afterPropertiesSet() throws Exception;
              @Override
              public void afterPropertiesSet() throws Exception {
                notNull(dataSource, "Property 'dataSource' is required");
                notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");//在创建对象的时候默认会被初始化  private org.apache.ibatis.session.SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    
                state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
                          "Property 'configuration' and 'configLocation' can not specified with together");
    
                this.sqlSessionFactory = buildSqlSessionFactory();
              }
    
              /**
           * Build a {@code SqlSessionFactory} instance.
           *
           * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
           * {@code SqlSessionFactory} instance based on an Reader.
           * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
           *
           * @return SqlSessionFactory
           * @throws IOException if loading the config file failed
           */
          protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    
            Configuration configuration;
    
            XMLConfigBuilder xmlConfigBuilder = null;
            // private org.apache.ibatis.session.Configuration configuration;
            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);
              }
            } else if (this.configLocation != null) {
              xmlConfigBuilder = new XMLConfigBuilder(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");
              }
              configuration = new Configuration();
    //如果没有配置,类似上面的调用,那么就自行创建一个
              configuration.setVariables(this.configurationProperties);//设置配置资源
            }
    
            if (this.objectFactory != null) {
              configuration.setObjectFactory(this.objectFactory);
            }
    
            if (this.objectWrapperFactory != null) {
              configuration.setObjectWrapperFactory(this.objectWrapperFactory);
            }
    
            if (hasLength(this.typeAliasesPackage)) {
              String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                  ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
              for (String packageToScan : typeAliasPackageArray) {
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                        typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                if (LOGGER.isDebugEnabled()) {
                  LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
              }
            }
    
            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.vfs != null) {
              configuration.setVfsImpl(this.vfs);
            }
    
            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));//配置环境environment为当前类classsimplename
    
            if (!isEmpty(this.mapperLocations)) {
              for (Resource mapperLocation : this.mapperLocations) {
                if (mapperLocation == null) {
                  continue;
                }
    
                try {
                  XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                      configuration, mapperLocation.toString(), configuration.getSqlFragments());//创建对应mapper的xmlMapperBuilder
                  xmlMapperBuilder.parse();//解析mapper配置信息
                } 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 this.sqlSessionFactoryBuilder.build(configuration);//构造一个sqlSessionFactory,默认实现DefaultSqlSessionFactory
      }
    
    
  • getMapper

    这里的泛型即我们传递的type类型,一个mapper的接口类型

        package org.mybatis.spring;
    
        /**
         * 
         *
         * @version $Id$
         */
        public interface TestMapper {
    
          int findTest();
    
          void insertTest(String test);
    
        }
    
        public class DefaultSqlSession implements SqlSession {
    
        @Override
          public <T> T getMapper(Class<T> type) {
            return configuration.<T>getMapper(type, this);
          }
    
        public class Configuration {
              public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
              //  protected MapperRegistry mapperRegistry = new MapperRegistry(this);
              ![](media/14578436519097/14578506978972.jpg)
    //在初始化配置对象的时候将会把,mapper xml对应的clazz注册进去:XMLMapperBuilder解析mapper的configuration.addMapper(boundType);方法中
                return mapperRegistry.getMapper(type, sqlSession);
              }
    
               public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
                final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);//得到以mapper接口对应的一个代理工厂
                if (mapperProxyFactory == null) {
                  throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
                }
                try {
                  return mapperProxyFactory.newInstance(sqlSession);
                } catch (Exception e) {
                  throw new BindingException("Error getting mapper instance. Cause: " + e, e);
                }
              }
    
            public class MapperProxyFactory<T> {
              public T newInstance(SqlSession sqlSession) {
                final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);//创建一个public class MapperProxy<T> implements InvocationHandler, Serializable,代理对象的InvocationHandler对象
                return newInstance(mapperProxy);//生成代理对象
              }
    
                protected T newInstance(MapperProxy<T> mapperProxy) {
                return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
              }
    

- 执行findTest,调用mapper接口的方法时:
   我们的invoke方法会被执行:
   
   ```java
        
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    
         private static final long serialVersionUID = -6424540398559729838L;
          private final SqlSession sqlSession;
          private final Class<T> mapperInterface;
          private final Map<Method, MapperMethod> methodCache;
        
          public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
            this.sqlSession = sqlSession;
            this.mapperInterface = mapperInterface;
            this.methodCache = methodCache;
          }
        
        /*
        proxy-->org.apache.ibatis.binding.MapperProxy@21b2e768
       method--> public abstract int org.mybatis.spring.TestMapper.findTest()
        */
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          //method.getDeclaringClass()-->interface org.mybatis.spring.TestMapper
            if (Object.class.equals(method.getDeclaringClass())) {
              try {
                return method.invoke(this, args);
              } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
              }
            }
            final MapperMethod mapperMethod = cachedMapperMethod(method);
            return mapperMethod.execute(sqlSession, args);
      }
      
      
      public class MapperMethod {

          private final SqlCommand command;
          private final MethodSignature method;
        
          public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
            this.command = new SqlCommand(config, mapperInterface, method);
            this.method = new MethodSignature(config, method);
          }
        
          public Object execute(SqlSession sqlSession, Object[] args) {
            Object result;
            if (SqlCommandType.INSERT == command.getType()) {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.insert(command.getName(), param));
            } else if (SqlCommandType.UPDATE == command.getType()) {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.update(command.getName(), param));
            } else if (SqlCommandType.DELETE == command.getType()) {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.delete(command.getName(), param));
            } else if (SqlCommandType.SELECT == command.getType()) {
              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 {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(command.getName(), param);//还是调用sqlSession.selectOne完成查询
                ![](media/14578436519097/14578521104363.jpg)
//那么就和我们直接使用session.selectOne("org.mybatis.spring.TestMapper.findTest");一样了,后续的可以接上一篇[MyBatis 一句sql的执行流程](http://my.oschina.net/u/2316162/blog/634720)
              }
            } else if (SqlCommandType.FLUSH == command.getType()) {
                result = sqlSession.flushStatements();
            } else {
              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;
      }
  • 上面的代码实际就类别了spring在内部生成SessionFatory的过程,可以看出org.mybatis.spring.SqlSessionFactoryBean,就是帮助我们完成框架的初始化的一个spring工厂对象(> This means that the bean Spring ultimately creates is not the SqlSessionFactoryBean itself, but what the factory returns as a result of the getObject() call on the factory. In this case, Spring will build an SqlSessionFactory for you at application startup and store it with the name sqlSessionFactory . ),用来生产SqlSessionFactory,简化了我们的整合过程,可以在无需添加mybatis主配置文件的基础上,完成框架configuration对象的初始化等;
    在简要了解了它的用处之后,我们就来集成spring和mybatis,一下是官方的介绍:

    In normal MyBatis-Spring usage, you will not need to use SqlSessionFactoryBean or the corresponding SqlSessionFactory directly. Instead, the session factory will be injected into MapperFactoryBeans or other DAOs that extend SqlSessionDaoSupport .
    Note that this config file does not need to be a complete MyBatis config. Specifically, any environments, data sources and MyBatis transaction managers will be ignored . SqlSessionFactoryBean creates its own, custom MyBatis Environment with these values set as required.

    Another reason to require a config file is if the MyBatis mapper XML files are not in the same classpath location as the mapper classes. With this configuration, there are two options. This first is to manually specify the classpath of the XML files using a section in the MyBatis config file. A second option is to use the mapperLocations property of the factory bean.
    The mapperLocations property takes a list of resource locations. This property can be used to specify the location of MyBatis XML mapper files. The value can contain Ant-style patterns to load all files in a directory or to recursively search all paths from a base location.

    就我们的项目本身配置而言,首先在spring配置文件中声明 sqlSessionFactory bean

        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--...可以是任何DataSource,只要复合javax.sql.DataSource标准,这里尝试使用阿里开源的一个库-->
    </bean>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations">
            <array>
                <value>classpath:mapper/*.xml</value>
            </array>
        </property>
        <property name="typeAliasesPackage" value="com.isea533.mybatis.model"/>
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageHelper">
                    <property name="properties">
                        <value>
                            dialect=mysql
                            reasonable=true
                            supportMethodsArguments=true
                            params=count=countSql
                            autoRuntimeDialect=true
                        </value>
                    </property>
                </bean>
            </array>
        </property>
    </bean>
    

    datasource是SqlSessionFactoryBean唯一一个必备的属性,而从class和id也可以看出我们并不直接使用SqlSessionFactoryBean,我们需要的spring会帮助我们生成:sqlSessionFactory
    上面的配置会到classpath的mapper路径下查找所有.xml结尾的mapper配置文件;
    到这里我们就将sql mapper配置好了,解析来我们看看针对3之后的接口映射是怎么配置的;

  • MapperScannerConfigurer

        <bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.isea533.mybatis.mapper"/>
        <!-- 3.2.2版本新特性,markerInterface可以起到mappers配置的作用,详细情况需要看Marker接口类 -->
        <property name="markerInterface" value="com.isea533.mybatis.util.MyMapper"/>
        <!-- 通用Mapper通过属性注入进行配置,默认不配置时会注册Mapper<T>接口
        <property name="properties">
            <value>
                mappers=tk.mybatis.mapper.common.Mapper
            </value>
        </property>
        -->
    </bean>
    

你可能感兴趣的:(mybatis-spring 整合记录)