一、前言

一般我们会在datasource.xml中进行如下配置,但是其中每个配置项原理和用途是什么,并不是那么清楚,如果不清楚的话,在使用时候就很有可能会遇到坑,所以下面对这些配置项进行一一解说

(1)配置数据源
?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xmlns:jee="http://www.springframework.org/schema/jee"xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd   http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd   http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd   http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"><!-- (1) 数据源 --><bean id="dataSource"class="com.alibaba.druid.pool.DruidDataSource" init-method="init"destroy-method="close"><property name="driverClassName"value="com.mysql.jdbc.Driver" /><property name="url" value="jdbc:mysql://127.0.0.1:3306/test" /><property name="username" value="root" /><property name="password" value="123456" /><property name="maxWait" value="3000" /><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) session工厂 --><bean id="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><property name="mapperLocations"value="classpath*:mapper/*Mapper*.xml" /><property name="dataSource" ref="dataSource" /></bean><!-- (3) 配置扫描器,扫描指定路径的mapper生成数据库操作代理类 --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="annotationClass"value="javax.annotation.Resource"></property><property name="basePackage" value="com.zlx.user.dal.sqlmap" /><property name="sqlSessionFactory" ref="sqlSessionFactory" /></bean></beans>
  • 其中(1)是配置数据源,这里使用了druid连接池,用户可以根据自己的需要配置不同的数据源,也可以选择不适用数据库连接池,而直接使用具体的物理连接。

  • 其中(2)创建sqlSessionFactory,用来在(3)时候使用。

  • 其中(3)配置扫描器,扫描指定路径的mapper生成数据库操作代理类

二、SqlSessionFactory内幕

第二节配置中配置SqlSessionFactory的方式如下:

<!-- (2) session工厂 --><bean id="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"><property name="mapperLocations"value="classpath*:mapper/*Mapper*.xml" /><property name="dataSource" ref="dataSource" /></bean>

其中mapperLocations配置mapper.xml文件所在的路径,dataSource配置数据源,下面我们具体来看SqlSessionFactoryBean的代码,SqlSessionFactoryBean实现了FactoryBean和InitializingBean扩展接口,所以具有getObject和afterPropertiesSet方法(具体可以参考:https://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e),下面我们从时序图具体看这两个方法内部做了什么:

如上时序图其中步骤(2)代码如下:

 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {Configuration configuration;XMLConfigBuilder xmlConfigBuilder = null;...//(3.1)if (this.transactionFactory == null) {this.transactionFactory = new SpringManagedTransactionFactory();}//(3.2)configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));//(3.3)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());xmlMapperBuilder.parse();} 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");}}//3.9return this.sqlSessionFactoryBuilder.build(configuration);}
  • 如上代码(3.1)创建了一个Spring事务管理工厂,这个后面会用到。

  • 代码(3.2)设置configuration对象的环境变量,其中dataSource为demo中配置文件中创建的数据源。

  • 代码(3.3)中mapperLocations是一个数组,为demo中配置文件中配置的满足classpath*:mapper/Mapper.xml条件的mapper.xml文件,本demo会发现存在
    [file[/Users/zhuizhumengxiang/workspace/mytool/distributtransaction/transactionconfig/transaction-demo/deep-learn-java/Start/target/classes/mapper/CourseDOMapper.xml],
    file[/Users/zhuizhumengxiang/workspace/mytool/distributtransaction/transactionconfig/transaction-demo/deep-learn-java/Start/target/classes/mapper/UserDOMapper.xml]] 两个文件

代码(3.3)循环遍历每个mapper.xml,然后调用XMLMapperBuilder的parse方法进行解析。

XMLMapperBuilder的parse代码中configurationElement方法做具体解析,代码如下:

 private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);...//(3.4)parameterMapElement(context.evalNodes("/mapper/parameterMap"));//(3.5)resultMapElements(context.evalNodes("/mapper/resultMap"));//(3.6)sqlElement(context.evalNodes("/mapper/sql"));//(3.7)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);}
  • 代码(3.4)解析mapper.xml中/mapper/parameterMap标签下内容,本demo中的XML文件中没有配置这个。

  • 代码(3.5)解析mapper.xml中/mapper/resultMap标签下内容,然后存放到Configuration对象的resultMaps缓存里面,这里需要提一下,所有的mapper.xml文件共享一个Configuration对象,所有mapper.xml里面的resultMap都存放到同一个Configuration对象的resultMaps里面,其中key为mapper文件的namespace和resultMap的id组成,比如UserDoMapper.xml:

<mapper namespace="com.zlx.user.dal.sqlmap.UserDOMapper" ><resultMap id="BaseResultMap" type="com.zlx.user.dal.dao.UserDO" ><id column="id" property="id" jdbcType="INTEGER" /><result column="age" property="age" jdbcType="INTEGER" /></resultMap>

其中key为com.zlx.user.dal.sqlmap.CourseDOMapper.BaseResultMap,value则为存放一个map,map里面是column与property的映射。

  • 代码(3.6)解析mapper.xml中/mapper/sql下的内容,然后保存到Configuration对象的sqlFragments缓存中,sqlFragments也是一个map,比如UserDoMapper.xml中的一个sql标签:
<sql id="Base_Column_List" >id, age
</sql>

其中key为com.zlx.user.dal.sqlmap.CourseDOMapper.Base_Column_List,value作为一个记录sql标签内容的XNode节点。

  • 代码(3.7)解析mapper.xml中select|insert|update|delete增删改查的语句,并封装为MappedStatement对象保存到Configuration的mappedStatements缓存中,mappedStatements也是一个map结构,比如:比如UserDoMapper.xml中的一个select标签:

<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >select <include refid="Base_Column_List" />from userwhere id = #{id,jdbcType=INTEGER}</select>

其中key为com.zlx.user.dal.sqlmap.CourseDOMapper.selectByPrimaryKey,value为标签内封装为MappedStatement的对象。

至此configurationElement解析XML的步骤完毕了,下面我们看时序图中步骤(12)bindMapperForNamespace代码如下:

 private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {//(3.8)configuration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}}

其中代码(3.8)注册mapper接口的Class对象到configuration中的mapperRegistry管理的缓存knownMappers中,knownMappers是个map,其中key为具体mapper接口的Class对象,value为mapper接口的代理对象MapperProxyFactory。

注:SqlSessionFactoryBean作用之一是扫描配置的mapperLocations路径下的所有mapper.xml 文件,并对其进行解析,然后把解析的所有mapper文件的信息保存到一个全局的configuration对象的具体缓存中,然后注册每个mapper.xml对应的接口类到configuration中,并为每个接口类生成了一个代理bean.

然后时序图步骤15创建了一DefaultSqlSessionFactory对象,并且传递了上面全局的configuration对象。

步骤16则返回创建的DefaultSqlSessionFactory对象。

三、MapperScannerConfigurer内幕

第二节中MapperScannerConfigurer的配置方式如下:

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="annotationClass"value="javax.annotation.Resource"></property><property name="basePackage" value="com.zlx.user.dal.sqlmap" /><property name="sqlSessionFactory" ref="sqlSessionFactory" /></bean>

其中sqlSessionFactory设置为第4节创建的DefaultSqlSessionFactory,basePackage为mapper接口类所在目录,annotationClass这是为注解@Resource,后面会知道标示只扫描basePackage路径下标注@Resource注解的mapper接口类。

MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor, InitializingBean接口,所以会重写下面方法:

(5.1)
//在bean注册到ioc后创建实例前修改bean定义和新增bean注册,这个是在context的refresh方法被调用
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;(5.2)
//set属性设置后被调用
void afterPropertiesSet() throws Exception;

更多关于Spring扩展接口的知识可以移步(https://gitbook.cn/gitchat/activity/5a84589a1f42d45a333f2a8e)

下面我们从时序图看这看postProcessBeanDefinitionRegistry和afterPropertiesSet扩展接口里面都做了些什么:

其中afterPropertiesSet代码如下:

  public void afterPropertiesSet() throws Exception {notNull(this.basePackage, "Property 'basePackage' is required");}

可知是校验basePackage是否为null,为null会抛出异常。因为MapperScannerConfigurer作用就是扫描basePackage路径下的mapper接口类然后生成代理,所以不允许basePackage为null。

postProcessBeanDefinitionRegistry的代码如下:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);...//5.3scanner.setAnnotationClass(this.annotationClass);//5.4scanner.setSqlSessionFactory(this.sqlSessionFactory);...//5.5scanner.registerFilters();//5.6scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}
  • 代码(5.3)设置注解类,这里设置的为@Resource注解,(5.4)设置sqlSessionFactory到ClassPathMapperScanner。

  • 代码(5.5)根据设置的@Resource设置过滤器,代码如下:

public void registerFilters() {boolean acceptAllInterfaces = true;if (this.annotationClass != null) {addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));acceptAllInterfaces = false;}...}public void addIncludeFilter(TypeFilter includeFilter) {this.includeFilters.add(includeFilter);}

可知具体是把@Resource注解作为了一个过滤器

  • 代码(5.6)具体执行扫描,其中basePackage为我们设置的com.zlx.user.dal.sqlmap,basePackage设置的时候允许设置多个包路径并且使用 ,; \t\n进行分割,加上上面的过滤条件,就是说对basePackage路径下标注@Resource注解的mapper接口类进行代理。

具体执行扫描的是doScan方法,其代码如下:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {//具体扫描符合条件的beanSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {...if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//注册到IOC容器registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;
}

如上代码可知是对每个包路径分别进行扫描,然后对符合条件的接口bean注册到IOC容器。

这里我们看下findCandidateComponents的逻辑:

private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {//5.8String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);...//5.9for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}if (resource.isReadable()) {try {//5.10MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setResource(resource);sbd.setSource(resource);if (isCandidateComponent(sbd)) {//5.11candidates.add(sbd);}else {}}...}...}...}}...return candidates;}

如上代码其中(5.8)是根据我们设置的basePackage得到一个扫描路径,这里根据我们demo设置的值,拼接后packageSearchPath为classpath*:com/zlx/user/dal/sqlmap/**/*.class,这里扫描出来的文件为:

file[/Users/zhuizhumengxiang/workspace/mytool/distributtransaction/transactionconfig/transaction-demo/deep-learn-java/Start/target/classes/com/zlx/user/dal/sqlmap/CourseDOMapper.class]
file[/Users/zhuizhumengxiang/workspace/mytool/distributtransaction/transactionconfig/transaction-demo/deep-learn-java/Start/target/classes/com/zlx/user/dal/sqlmap/CourseDOMapperNoAnnotition.class]
file[/Users/zhuizhumengxiang/workspace/mytool/distributtransaction/transactionconfig/transaction-demo/deep-learn-java/Start/target/classes/com/zlx/user/dal/sqlmap/UserDOMapper.class]

然后isCandidateComponent方法执行具体对上面扫描到的文件进行过滤,其代码:

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {...for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;
}

上面我们讲解过添加了一个@Resource注解的过滤器,这里执行时候器match方法如下:

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {if (matchSelf(metadataReader)) {return true;}...return false;}//判断接口类是否有@Resource注解protected boolean matchSelf(MetadataReader metadataReader) {AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();return metadata.hasAnnotation(this.annotationType.getName()) ||(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));}

经过过滤后CourseDOMapperNoAnnotition.class接口类被过滤了,因为其没有标注@Resource注解。只有CourseDOMapper和UserDOMapper两个标注@Resource的类注册到了IOC容器。

如上时序图注册后,还需要执行processBeanDefinitions对满足过滤条件的CourseDOMapper和UserDOMapper的bean定义进行修改,以便生成代理类,processBeanDefinitions代码如下:

 private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {GenericBeanDefinition definition;for (BeanDefinitionHolder holder : beanDefinitions) {definition = (GenericBeanDefinition) holder.getBeanDefinition();// (5.12)definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59definition.setBeanClass(this.mapperFactoryBean.getClass());...//5.13if (this.sqlSessionFactory != null) {definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);explicitFactoryUsed = true;}...}}

如上代码(5.12)修改bean定义的BeanClass为MapperFactoryBean,然后设置MapperFactoryBean的泛型构造函数参数为真正的被代理接口。也就是如果当前bean定义是com.zlx.user.dal.sqlmap.CourseDOMapper接口的,则设置当前bean定义的BeanClass为MapperFactoryBean,并设置com.zlx.user.dal.sqlmap.CourseDOMapper为MapperFactoryBean的构造函数参数。

代码(5.13)设置session工厂到bean定义。

注:MapperScannerConfigurer的作用是扫描指定路径下的Mapper接口类,并且可以制定过滤策略,然后对符合条件的bean定义进行修改以便在bean创建时候生成代理类,最终符合条件的mapper接口都会被转换为MapperFactoryBean,MapperFactoryBean中并且维护了第4节生成的DefaultSqlSessionFactory。

最后

更多本地事务咨询可以单击我
更多分布式事务咨询可以单击我
更多Spring事务配置解惑[单击我](https://gitbook.cn/gitchat/activity/5b4174bd18d874113f9f09a1
)

想了解更多关于粘包半包问题单击我
更多关于分布式系统中服务降级策略的知识可以单击 单击我
想系统学dubbo的单击我
想学并发的童鞋可以 单击我

SpringMybaits数据库配置解惑相关推荐

  1. VS中C#读取app.config数据库配置字符串的三种方法(转)

    关于VS2008或VS2005中数据库配置字符串的三种取法 VS2008建立Form程序时,如果添加数据源会在配置文件 app.config中自动写入连接字符串,这个字符串将会在你利用DataSet, ...

  2. mysql主从 查询负载_MySQL集群:主从数据库配置 实现查询负载

    在做web应用系统中,如果数据库出现了性能瓶颈,而你又是使用的MySQL数据库,那么就可以考虑采用数据库集群的方式来实现查询负载了.因为一般来讲任何一个系统中数据库的查询操作比更新操作要多的多,因此通 ...

  3. yii2的model数据库配置以及应用(主从数据库配置)

    2019独角兽企业重金招聘Python工程师标准>>> 1.多数据库配置 'db' => require(__DIR__ . '/db.php'),'gdb' => re ...

  4. Django models数据库配置以及多数据库调用设置

    今天来说说web框架Django怎么配置使用数据库,也就是传说中MVC(Model View Controller)中的M,Model(模型). 简单介绍一下Django中的MVC: 模型(model ...

  5. 使用IntelliJ IDEA开发SpringMVC网站(三)数据库配置

    原文:使用IntelliJ IDEA开发SpringMVC网站(三)数据库配置 摘要 讲解在IntelliJ IDEA中,如何进行Mysql数据库的配置 目录[-] 文章已针对IDEA 15做了一定的 ...

  6. jetspeed 安装及数据库配置

        今天安了一下午的jetspeed,数据库的配置怎么也弄不好,晚上就再重装了几次,最后发现是安装程序中自动生成的代码中存在问题. 安装: 1.首先到http://portals.apache.o ...

  7. mysql主备数据库配置_MySQL双主互备配置

    #主数据库配置 1.修改my.conf(windows下是my.ini)文件: 在[mysqld]部分插入如下两行: #开启二进制日志 log-bin=mysql-bin #设置server-id s ...

  8. kettle mysql 配置_Kettle数据库配置抽离

    在使用ETL工具Kettle时候,为了使作业或转换具有通用性,有时候,我们需要将数据库的连接配置从脚本或转换中抽离出来,下面介绍一种方案,该方案主要涉及的文件有: # 这两个文件,默认是在系统的用户目 ...

  9. mysql主备数据库配置文档_MySQL数据库配置主从服务器实现双机热备实例教程

    网站:bbs.osyunwei.com 程序在:Web服务器192.168.21.129上面 数据库在:MySQL服务器192.168.21.169上面 实现目的:增加一台MySQL备份服务器(192 ...

  10. oracle client 默认端口,[数据库]配置精简版Oracle客户端

    [数据库]配置精简版Oracle客户端 0 2012-12-22 16:00:24 一般只是作为客户端访问Oracle,并不需要安装庞大的Oracle.有时候经常忘记怎么配置精简版Oracle,这篇文 ...

最新文章

  1. rundll32.exe文件详解
  2. 【面试】Java基础中的那些事-One
  3. Google Map API V3调用arcgis发布的瓦片地图服务
  4. 【PAT乙级】1044 火星数字 (20 分)
  5. [计算机]“华为的冬天”——任正非(华为总裁)
  6. linux内核配置参考,[转]Linux内核配置选项 参考(3)
  7. 用函数模板实现选择排序算法_干货|STL容器和算法
  8. org.apache.hadoop.fs.ChecksumException: Checksum error
  9. python代码价格_在Python中如何用代码求出超过某价格的且受欢迎程度top5的菜品名?...
  10. fcntl函数的作用及应用场景
  11. 光纤的基本理论光纤的色散
  12. 怎么把WORD中插入的图片改为统一尺寸的,看这里,文档中图片怎么改成同样大小
  13. 南京大学计算机学类,南京大学计算机专业怎么样
  14. Zabbix监控系统系列之十一:拓扑图绘制
  15. camera raw 13.2中文版
  16. 营收1亿美金的美国软件公司_我如何在60天内损失1亿美元
  17. 《使用Java实现一元二次方程求根计算器》改进版
  18. 指南:情人节表白h5源码
  19. lua 随机数 math.random()和math.randomseed()用法
  20. 支付宝小程序开发练习,显示自定义二维码(四)

热门文章

  1. 网易邮箱大师添加附件显示服务器连接失败,网易邮箱大师如何添加Word附件 添加附件方法步骤详细介绍...
  2. 设计测测试用例的五大方法
  3. python jinja2_Python jinja2
  4. python 解压zip rar 7z文件
  5. SAS程序探索性因子分析
  6. 自制抖音去水印工具 java+微信小程序
  7. 全外显子测序助力疾病诊疗
  8. 计算机不显示验证码,验证码不显示_网页不显示验证码是怎么回事?
  9. pandas将df赋值到另一个df_pandas基础
  10. 【编程菜谱系列一】手把手教你用废旧手机改造为人脸识别监控