南宁Java培训
达内南宁JAVA中心

13471155827
qq:269520999

热门课程

Spring 中 Mybatis 的花样配置,南宁java培训

  • 时间:2017-11-20 15:50
  • 发布:不详
  • 来源:网络

一、前言

Mybatis作为一个优秀的存储过程和高级映射的持久层框架,目前在项目实践中运用的比较广泛,最近做项目时候发现了一种之前没见过的配置方式,这里总结下常用的配置方式以便备忘查找。

二、Spring中Mybatis的配置方案一

2.1 多数据源配置案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
(1)数据源配置
 <bean id="dataSourceForA"class="com.alibaba.druid.pool.DruidDataSource"init-method="init"destroy-method="close">
        <property name="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url"value="${db1_url}"/>
        <property name="username"value="$db1_user}"/>
        <property name="password"value="${db1_passwd}"/>
        <property name="maxWait"value="${db1_maxWait}"/>
        <property name="maxActive"value="28"/>
        <property name="initialSize"value="2"/>
        <property name="minIdle"value="0"/>
        <property name="timeBetweenEvictionRunsMillis"value="300000"/>
        <property name="testOnBorrow"value="false"/>
        <property name="testWhileIdle"value="true"/>
        <property name="validationQuery"value="select 1 from dual"/>
        <property name="filters"value="stat"/>
    </bean>
 
(2)创建sqlSessionFactory
<bean id="sqlSessionFactoryForA"class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="mapperLocations"value="classpath*:com/**/mapper1/*Mapper*.xml"/>
        <property name="dataSource"ref="dataSourceForA"/>
        <property name="typeAliasesPackage"value="com.zlx.***.dal"/>
</bean>
     
(3)配置扫描器,扫描指定路径的mapper生成数据库操作代理类
<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="annotationClass"value="javax.annotation.Resource"></property>
        <property name="basePackage"value="com.zlx1.***.dal.***.mapper"/>
        <property name="sqlSessionFactory"ref="sqlSessionFactoryForA"/>
</bean>
 
(4)数据源配置
 <bean id="dataSourceForB"class="com.alibaba.druid.pool.DruidDataSource"init-method="init"destroy-method="close">
        <property name="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url"value="${db_url}"/>
        <property name="username"value="$db_user}"/>
        <property name="password"value="${db_passwd}"/>
        <property name="maxWait"value="${db_maxWait}"/>
        <property name="maxActive"value="28"/>
        <property name="initialSize"value="2"/>
        <property name="minIdle"value="0"/>
        <property name="timeBetweenEvictionRunsMillis"value="300000"/>
        <property name="testOnBorrow"value="false"/>
        <property name="testWhileIdle"value="true"/>
        <property name="validationQuery"value="select 1 from dual"/>
        <property name="filters"value="stat"/>
    </bean>
 
(5)创建sqlSessionFactory
<bean id="sqlSessionFactoryForB"class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="mapperLocations"value="classpath*:com/**/mapper/*Mapper*.xml"/>
        <property name="dataSource"ref="dataSourceForB"/>
        <property name="typeAliasesPackage"value="com.zlx.***.dal"/>
</bean>
     
(6)配置扫描器,扫描指定路径的mapper生成数据库操作代理类
<beanclass="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="annotationClass"value="javax.annotation.Resource"></property>
        <property name="basePackage"value="com.zlx.***.dal.***.mapper"/>
        <property name="sqlSessionFactory"ref="sqlSessionFactoryForB"/>
</bean>

  • (1)(2)(3)是一组配置, (4)(5)(6)是一组配置,配置指定的数据源到对应的包下扫描配置文件生成数据库操作代理类。
  • (1)(4)分别创建了两个数据源,(2)(5)根据对应的数据源创建SqlSession工厂,(3)(6)配置mybaits扫描器,根据对应的SqlSession工厂和包路径生成代理后的数据库操作类。

2.1 原理简单介绍

2.1.1 SqlSessionFactory原理

(2)(5)作用是根据配置创建一个SqlSessionFactory,看下SqlSessionFactoryBean的代码知道它实现了FactoryBean和InitializingBean类,由于实现了InitializingBean,所以自然它的afterPropertiesSet方法,由于实现了FactoryBean类,所以自然会有getObject方法。下面看下时序图:

从时序图可知,SqlSessionFactoryBean类主要是通过属性配置创建SqlSessionFactory实例,具体是解析配置中所有的mapper文件放到configuration,然后作为构造函数参数实例化一个DefaultSqlSessionFactory作为SqlSessionFactory。

2.1.2 MapperScannerConfigurer原理

扫描指定路径的mapper生成数据库操作代理类
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware接口,所以会重写一下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//在bean注册到ioc后创建实例前修改bean定义和新增bean注册,这个是在context的refresh方法调用
voidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)throwsBeansException;
 
//在bean注册到ioc后创建实例前修改bean定义或者属性值
voidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throwsBeansException;
 
//set属性设置后调用
voidafterPropertiesSet()throwsException;
 
//获取IOC容器上下文,在context的prepareBeanFactory中调用
voidsetApplicationContext(ApplicationContext applicationContext)throwsBeansException;
 
//获取bean在ioc容器中名字,在context的prepareBeanFactory中调用
voidsetBeanName(String name);

先上个扫描mapper生成代理类并注册到ioc时序图:

首先MapperScannerConfigurer实现的afterPropertiesSet方法用来确保属性basePackage不为空

1
2
3
publicvoidafterPropertiesSet()throwsException {
    notNull(this.basePackage,"Property 'basePackage' is required");
  }

postProcessBeanFactory里面啥都没做,setBeanName获取了bean的名字,setApplicationContext里面获取了ioc上下文。下面看重要的方法postProcessBeanDefinitionRegistry,由于mybais是运行时候才通过解析mapper文件生成代理类注入到ioc,所以postProcessBeanDefinitionRegistry正好可以干这个事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
publicvoidpostProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if(this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
 
    //构造一个ClassPathMapperScanner查找mapper
    ClassPathMapperScanner scanner =newClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    //javax.annotation.Resource
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    //引用sqlSessionFactory
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    //ioc上下文
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
   //basePackage=com.alibaba.***.dal.***.mapper,com.alibaba.rock.auth.mapper,com.alibaba.rock.workflow.dal.workflow.mapper
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

下面重点看下scan方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
publicSet<BeanDefinitionHolder> doScan(String... basePackages) {
    //根据指定路径去查找对应mapper的接口类,并转化为beandefination
    Set<BeanDefinitionHolder> beanDefinitions =super.doScan(basePackages);
 
    if(beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '"+ Arrays.toString(basePackages) +"' package. Please check your configuration.");
    }else{
      //修改接口类bean的beandefination
      processBeanDefinitions(beanDefinitions);
    }
 
    returnbeanDefinitions;
  }

其中super.doScan(basePackages);根据指定路径查找mapper接口类,并生成bean的定义对象,对象中包含beanclassname,beanclass属性,最后注册该bean到ioc容器。下面看下最重要的processBeanDefinitions方法对bean定义的改造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
privatevoidprocessBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for(BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
 
      // 上面讲的扫描后beanclass设置的为mapper接口类,但是这里修改为MapperFactoryBean,MapperFactoryBean代理了mapper接口类,并且实际mapper接口类作为构造函数传入了      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      definition.getPropertyValues().add("addToConfig",this.addToConfig);
 
      //设置属性配置中的sqlSessionFactory
      booleanexplicitFactoryUsed =false;
      if(StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory",newRuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed =true;
      }elseif(this.sqlSessionFactory !=null) {
        definition.getPropertyValues().add("sqlSessionFactory",this.sqlSessionFactory);
        explicitFactoryUsed =true;
      }
 
      if(StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if(explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",newRuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed =true;
      }elseif(this.sqlSessionTemplate !=null) {
        if(explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate",this.sqlSessionTemplate);
        explicitFactoryUsed =true;
      }
 
      if(!explicitFactoryUsed) {
        if(logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '"+ holder.getBeanName() +"'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

注:这里修改了mapper接口类的beandefination中的beanclass为MapperFactoryBean,它则负责生产数据类操作代理类,实际mapper接口类作为构造函数传入了 。由于只修改了beanclass,没有修改beanname,所以我们从容器中获取时候无感知的。

在上一个代理bean如何构造的时序图:

下面看下MapperFactoryBean是如何生成代理类的:
首先,上面代码设置了MapperFactoryBean的setSqlSessionFactory方法:

1
2
3
4
5
publicvoidsetSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if(!this.externalSqlSession) {
      this.sqlSession =newSqlSessionTemplate(sqlSessionFactory);
    }
  }

上面方法创建了sqlSession,由于MapperFactoryBean为工厂bean所以实例化时候会调用getObject方法:

1
2
3
publicT getObject()throwsException {
   returngetSqlSession().getMapper(this.mapperInterface);
 }

其实是调用了SqlSessionTemplate->getMapper,其中mapperInterface就是创建MapperFactoryBean时候的构造函数参数。

1
2
3
public<T> T getMapper(Class<T> type) {
    returngetConfiguration().getMapper(type,this);
  }

这里调用getConfiguration().getMapper(type, this);实际是DefaultSqlSessionFactory里面的configration的getMapper方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public<T> T getMapper(Class<T> type, SqlSession sqlSession) {
   //knownMappers是上面时序图中步骤6设置进入的。
    finalMapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if(mapperProxyFactory ==null) {
      thrownewBindingException("Type "+ type +" is not known to the MapperRegistry.");
    }
    try{
      returnmapperProxyFactory.newInstance(sqlSession);
    }catch(Exception e) {
      thrownewBindingException("Error getting mapper instance. Cause: "+ e, e);
    }
  }
 protectedT newInstance(MapperProxy<T> mapperProxy) {
    return(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[] { mapperInterface }, mapperProxy);
  }
 
  publicT newInstance(SqlSession sqlSession) {
   //代理回调类为MapperProxy
    finalMapperProxy<T> mapperProxy =newMapperProxy<T>(sqlSession, mapperInterface, methodCache);
    returnnewInstance(mapperProxy);
  }

在上一个实际执行sql时候调用代理类的序列图:

所以当调用实际的数据库操作时候会调用MapperProxy的invoke方法:

1
2
3
4
5
6
7
8
9
10
11
publicObject invoke(Object proxy, Method method, Object[] args)throwsThrowable {
    if(Object.class.equals(method.getDeclaringClass())) {
      try{
        returnmethod.invoke(this, args);
      }catch(Throwable t) {
        throwExceptionUtil.unwrapThrowable(t);
      }
    }
    finalMapperMethod mapperMethod = cachedMapperMethod(method);
    returnmapperMethod.execute(sqlSession, args);
  }

mapperMethod.execute(sqlSession, args);里面实际是调用当前mapper对应的SqlSessionTemplate的数据库操作,而它有委托给了代理类sqlSessionProxy,sqlSessionProxy是在SqlSessionTemplate的构造函数里面创建的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
publicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
 
    notNull(sqlSessionFactory,"Property 'sqlSessionFactory' is required");
    notNull(executorType,"Property 'executorType' is required");
 
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        newClass[] { SqlSession.class},
        newSqlSessionInterceptor());
  }

所以最终数据库操作有被代理SqlSessionInterceptor执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
publicObject invoke(Object proxy, Method method, Object[] args)throwsThrowable {
      //有TransactionSynchronizationManager管理
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try{
        Object result = method.invoke(sqlSession, args);
        if(!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        returnresult;
      }catch(Throwable t) {
          .....
      }
    }
 
publicstaticSqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
 
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
 
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
 
    SqlSession session = sessionHolder(executorType, holder);
    if(session !=null) {
      returnsession;
    }
 
    if(LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }
   //这里看到了使用sessionfactory熟悉的打开了一个session
    session = sessionFactory.openSession(executorType);
 
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
 
    returnsession;
  }

关于事务配置可移步:http://www.jianshu.com/p/1d882343c036

三、Spring中Mybatis的配置方案二

2.1 多数据源配置案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
(1)数据源配置
 <bean id="dataSourceForA"class="com.alibaba.druid.pool.DruidDataSource"init-method="init"destroy-method="close">
        <property name="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url"value="${db1_url}"/>
        <property name="username"value="$db1_user}"/>
        <property name="password"value="${db1_passwd}"/>
        <property name="maxWait"value="${db1_maxWait}"/>
        <property name="maxActive"value="28"/>
        <property name="initialSize"value="2"/>
        <property name="minIdle"value="0"/>
        <property name="timeBetweenEvictionRunsMillis"value="300000"/>
        <property name="testOnBorrow"value="false"/>
        <property name="testWhileIdle"value="true"/>
        <property name="validationQuery"value="select 1 from dual"/>
        <property name="filters"value="stat"/>
    </bean>
 
(2)创建sqlSessionFactory
<bean id="sqlSessionFactoryForA"class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="mapperLocations"value="classpath*:com/**/mapper1/*Mapper*.xml"/>
        <property name="dataSource"ref="dataSourceForA"/>
        <property name="typeAliasesPackage"value="com.zlx.***.dal"/>
</bean>
     
(3)配置扫描器,扫描指定路径的mapper生成数据库操作代理类
  <mybatis:scan base-package="com.zlx1.***.dal"factory-ref="sqlSessionFactoryForA"annotation="javax.annotation.Resource"/>
 
(4)数据源配置
 <bean id="dataSourceForB"class="com.alibaba.druid.pool.DruidDataSource"init-method="init"destroy-method="close">
        <property name="driverClassName"value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url"value="${db_url}"/>
        <property name="username"value="$db_user}"/>
        <property name="password"value="${db_passwd}"/>
        <property name="maxWait"value="${db_maxWait}"/>
        <property name="maxActive"value="28"/>
        <property name="initialSize"value="2"/>
        <property name="minIdle"value="0"/>
        <property name="timeBetweenEvictionRunsMillis"value="300000"/>
        <property name="testOnBorrow"value="false"/>
        <property name="testWhileIdle"value="true"/>
        <property name="validationQuery"value="select 1 from dual"/>
        <property name="filters"value="stat"/>
    </bean>
 
(5)创建sqlSessionFactory
<bean id="sqlSessionFactoryForB"class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="mapperLocations"value="classpath*:com/**/mapper/*Mapper*.xml"/>
        <property name="dataSource"ref="dataSourceForB"/>
        <property name="typeAliasesPackage"value="com.zlx.***.dal"/>
</bean>
     
(6)配置扫描器,扫描指定路径的mapper生成数据库操作代理类
    <mybatis:scan base-package="com.zlx.***.dal"factory-ref="sqlSessionFactoryForB"annotation="javax.annotation.Resource"/>

与上节不同在在于(3)(6)

3.2 原理简单介绍

这里只看 <mybatis:scan/> 标签解析,按照惯例看jar包的spring.handler找标签解析

MapperScannerBeanDefinitionParser的代码如下:

可知MapperScannerBeanDefinitionParser所做的事情和MapperScannerConfigurer类似都是内部搞了个ClassPathMapperScanner。

更多精彩内容请关注南宁达内java培训官网!

上一篇:几种java for 的用法,南宁java培训
下一篇:我对Java程序猿的学习的建议

java练习题【含答案】

java面试题(1)

JavaWeb项目为什么我们要放弃jsp?为什么要前后端解耦?为什么要前后端分离?

Java内存管理

选择城市和中心
贵州省

广西省

海南省