Spring 事务传播原理及数据库事务操作原理

相关内容:
架构师系列内容:架构师学习笔记(持续更新)

先看看 Spring 事务的基础配置

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                  http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
                  http://www.springframework.org/schema/aop
                  http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!-- 配置事务传播特性 -->
    <tx:advice id="transactionAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED"
                       rollback-for="Exception,RuntimeException,SQLException"/>
            <tx:method name="remove*" propagation="REQUIRED"
                       rollback-for="Exception,RuntimeException,SQLException"/>
            <tx:method name="modify*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
            <tx:method name="login" propagation="NOT_SUPPORTED"/>
            <tx:method name="query*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut expression="execution(* com.jarvisy.mvc1.demo.aop.service..*(..))"
                      id="transactionPointcut"/>
        <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice"/>
    </aop:config>


</beans>

Spring 事务管理基于 AOP 来实现,主要是统一封装非功能性需求。

1、事务基本概念

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
特点:事务是恢复和并发控制的基本单位。事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
原子性(Automicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(Consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(Durability):持久性也称永久性(Permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

2、事务的基本原理

Spring 事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能的。对于纯 JDBC 操作数据库,想要用到事务,可以按照以下步骤进行:
1、获取连接 Connection con = DriverManager.getConnection()
2、开启事务 con.setAutoCommit(true/false);
3、执行 CRUD
4、提交事务/回滚事务 con.commit() / con.rollback();
5、关闭连接 conn.close();

使用 Spring 的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由 Spirng 自动完成。 那么 Spring 是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解 Spring 的事务管理实现原理了。下面简单地介绍下,注解方式为例子:
配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional 标识。Spring 在启动的时候会去解析生成相关的 bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction 的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。真正的数据库层的事务提交和回滚是通过 binlog 或者 redo log 实现的。

3、Spring 事务的传播属性

所谓 spring 事务的传播属性,就是定义在存在多个事务同时存在的时候,spring 应该如何处理这些事务的行为。这些属性在 TransactionDefinition 中定义,具体常量的解释见下表:

常量名称常量解释
PROPAGATION_REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring默认的事务的传播。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按事务,这个事务拥有多个可以回滚的保存响。它只对DataSourceTransactionManager 事务管理器起效。
4、数据库隔离级别
隔离级别隔离级别的值导致的问题
Read-Uncommitted0导致脏读
Read-Committed1避免脏读,允许不可重复读和幻读
Repeatable-Read2避免脏读,不可重复读,允许幻读
Serializable3串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重

脏读: 一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读: 一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
幻读: 第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。

总结: 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle少数数据库默认隔离级别为:Repeatable Read 比如: MySQL Inno

5、Spring 中的隔离级别
常量解释
ISOLATION_DEFAULT这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。
ISOLATION_READ_UNCOMMITTED这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
ISOLATION_SERIALIZABLE这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
6、事务的嵌套

通过上面的理论知识的铺垫,我们大致知道了数据库事务和 Spring 事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解 Spring 事务传播的机制。假设外层事务 Service A 的 Method A() 调用 内层 Service B 的 Method B()

PROPAGATION_REQUIRED(Spring 默认)
如果 ServiceB.MethodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行ServiceA.MethodA() 的时候 Spring 已经起了事务,这时调用 ServiceB.MethodB(),ServiceB.MethodB() 看到自己已经运行在 ServiceA.MethodA() 的事务内部,就不再起新的事务。
假如 ServiceB.MethodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。 这样,在 ServiceA.MethodA() 或者在 ServiceB.MethodB() 内的任何地方出现异常,事务都会被回滚。

PROPAGATION_REQUIRES_NEW
比如我们设计 ServiceA.MethodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.MethodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。那么当执行到 ServiceB.MethodB() 的时候,ServiceA.MethodA() 所在的事务就会挂起,ServiceB.MethodB() 会起一个新的事务,等待 ServiceB.MethodB() 的事务完成以后,它才继续执行。
他与PROPAGATION_REQUIRED的事务区别在于事务的回滚程度了。因为ServiceB.MethodB()是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.MethodB()已经提交 ,那么 ServiceA.MethodA()失败回滚 ,ServiceB.MethodB() 是不会回滚的。如果 ServiceB.MethodB() 失败回滚,如果他抛出的异常被 ServiceA.MethodA() 捕获,ServiceA.MethodA() 事务仍然可能提交(主要看 B 抛出的异常是不是 A 会回滚的异常)

PROPAGATION_SUPPORTS
假设 ServiceB.MethodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.MethodB()时,如果发现 ServiceA.MethodA()已经开启了一个事务,则加入当前的事务,如果发现 ServiceA.MethodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。

PROPAGATION_NESTED
现在的情况就变得比较复杂了,ServiceB.MethodB()的事务属性被配置为PROPAGATION_ESTED, 此时两者之间又将如何协作呢? ServiceB.MethodB() 如果 rolback, 那么内部事务(即 ServiceB.MethodB() 将回滚到它执行前的 SavePoint而外部事务(即 ServiceA.MethodA() 可以有以下两种处理方式:
捕获异常,执行异常分支逻辑:

void MethodA() {
    try {
        ServiceB.MethodB();
    } catch (SomeException) {
        // 执行其他业务, 如 ServiceC.MethodC();
    }
}

这种方式也是嵌套事务最有价值的地方,它起到了分支执行的效果,如果ServiceB.MethodB()失败, 那么执行 ServiceC.MethodC(), 而 ServiceB.MethodB()已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过),这种特性可以用在某些特殊的业务中 , 而 PROPAGATION_REQUIRED 和PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
外部事务回滚/提交 代码不做任何修改, 那么如果内部事务(ServiceB.MethodB()rolback, 那么首先 ServiceB.MethodB() 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA.MethodA() 将根据具体的配置决定自己是comit 还是 rolback。
另外三种事务传播属性基本用不到,在此不做分析。

7、Spring 事务 API 架构图

在这里插入图片描述
使用 Spring 进行基本的 JDBC 访问数据库有多种选择。Spring 至少提供了三种不同的工作模式:JdbcTemplate, 一个在 Spring2.5 中新提供的 SimpleJdbc 类能够更好的处理数据库元数据; 还有一种称之为 RDBMS Object 的风格的面向对象封装方式, 有点类似于 JDO 的查询设计。 我们在这里简要列举你采取某一种工作方式的主要理由. 不过请注意, 即使你选择了其中的一种工作模式, 你依然可以在你的代码中混用其他任何一种模式以获取其带来的好处和优势。 所有的工作模式都必须要求 JDBC 2.0 以上的数据库驱动的支持, 其中一些高级的功能可能需要 JDBC 3.0 以上的数据库驱动支持。JdbcTemplate - 这是经典的也是最常用的 Spring 对于 JDBC 访问的方案。这也是最低级别的封装, 其他的工作模式事实上在底层使用了 JdbcTemplate 作为其底层的实现基础。JdbcTemplate 在 JDK 1.4 以上的环境上工作得很好。NamedParameterJdbcTemplate - 对 JdbcTemplate 做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的 JDBC 所使用的“?”作为参数的占位符。这种方式在你需要为某个 SQL 指定许多个参数时,显得更加直观而易用。该特性必须工作在 JDK1.4 以上。SimpleJdbcTemplate-这个类结合了JdbcTemplate和NamedParameterJdbcTemplate 的最常用的功能,同时它也利用了一些 Java 5 的特性所带来的优势,例如泛型、varargs 和 autoboxing 等,从而提供了更加简便的 API 访问方式。需要工作在 Java 5 以上的环境中。SimpleJdbcInsert 和 SimpleJdbcCall - 这两个类可以充分利用数据库元数据的特性来简化配置。通过使用这两个类进行编程,你可以仅仅提供数据库表名或者存储过程的名称以及一个 Map 作为参数。其中 Map 的 key 需要与数据库表中的字段保持一致。这两个类通常和 SimpleJdbcTemplate 配合使用。这两个类需要工作在 JDK 5 以上,同时数据库需要提供足够的元数据信息。
RDBMS 对象包括 MappingSqlQuery, SqlUpdate and StoredProcedure - 这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象。该对象在你定义了你的查询语句,声明查询参数并编译相应的 Query 之后被模型化。一旦模型化完成,任何执行函数就可以传入不同的参数对之进行多次调用。这种方式需要工作在 JDK 1.4 以上。

异常处理
异常结构如下:
在这里插入图片描述
SQLExceptionTranslator 是 一 个 接 口 , 如 果 你 需 要 在SQLException 和org.springframework.dao.DataAccessException 之间作转换,那么必须实现该接口。转换器类的实现可以采用一般通用的做法(比如使用 JDBC 的 SQLState code),如果为了使转换更准确,也可以进行定制(比如使用 Oracle 的 error code)。

SQLErrorCodeSQLExceptionTranslator 是 SQLExceptionTranslator 的默认实现。 该实现使用指定数据库厂商的 error code,比采用 SQLState 更精确。转换过程基于一个JavaBean ( 类 型 为 SQLErrorCodes ) 中 的 error code 。这 个 JavaBean 由SQLErrorCodesFactory 工厂类创建,其中的内容来自于 “sql-error-codes.xml”配置文 件 。 该 文 件 中 的 数 据 库 厂 商 代 码 基 于Database MetaData信息中的DatabaseProductName,从而配合当前数据库的使用。

SQLErrorCodeSQLExceptionTranslator 使用以下的匹配规则:首先检查是否存在完成定制转换的子类实现。 通 常SQLErrorCodeSQLExceptionTranslator 这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。

接着将 SQLException 的 error code 与错误代码集中的 error code 进行匹配。 默认情况下错误代码集将从 SQLErrorCodesFactory 取得。 错误代码集来自 classpath 下的sql-error-codes.xml 文件,它们将与数据库 metadata 信息中的 database name 进行映射。

使用 fallback 翻译器。SQLStateSQLExceptionTranslator 类是缺省的 fallback 翻译器。config 模块

NamespaceHandler 接口,DefaultBeanDefinitionDocumentReader 使用该接口来处理在 spring xml 配置文件中自定义的命名空间。

在这里插入图片描述
在 jdbc 模块,我们使用 JdbcNamespaceHandler 来处理 jdbc 配置的命名空间,其代码如下:

public class JdbcNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("embedded-database", new EmbeddedDatabaseBeanDefinitionParser());
        registerBeanDefinitionParser("initialize-database", new InitializeDatabaseBeanDefinitionParser());
    }
}

其中,EmbeddedDatabaseBeanDefinitionParser继承了AbstractBeanDefinitionParser,解析元素,并使用EmbeddedDatabaseFactoryBean 创建一个 BeanDefinition。顺便介绍一下用到的软件包 org.w3c.dom。
软件包 org.w3c.dom:为文档对象模型 (DOM) 提供接口,该模型是 Java API for XMLProcessing 的组件 API。该 Document Object Model Level 2 Core API 允许程序动态访问和更新文档的内容和结构。
Attr: Attr 接口表示 Element 对象中的属性。
CDATASection: CDATA 节用于转义文本块,该文本块包含的字符如果不转义则会被视为标记。
CharacterData: CharacterData 接口使用属性集合和用于访问 DOM 中字符数据的方法扩展节点。
Comment: 此接口继承自 CharacterData 表示注释的内容,即起始 ‘ ’ 之间的所有字符。
Document: Document 接口表示整个 HTML 或 XML 文档。
DocumentFragment: DocumentFragment 是“轻量级”或“最小”Document 对象。
DocumentType: 每个 Document 都有 doctype 属性,该属性的值可以为 null,也可以为 DocumentType 对象。
DOMConfiguration: 该 DOMConfiguration 接口表示文档的配置,并维护一个可识别的参数表。
DOMError: DOMError 是一个描述错误的接口。
DOMErrorHandler: DOMErrorHandler 是在报告处理 XML 数据时发生的错误或在进行某些其他处理(如验证文档)时 DOM 实现可以调用的回调接口。
DOMImplementation: DOMImplementation 接口为执行独立于文档对象模型的任何特定实例的操作提供了许多方法。
DOMImplementationList: DOMImplementationList 接口提供对 DOM 实现的有序集合的抽象,没有定义或约束如何实现此集合。
DOMImplementationSource: 此接口允许 DOM 实现程序根据请求的功能和版本提供一个或多个实现,如下所述。
DOMLocator: DOMLocator 是一个描述位置(如发生错误的位置)的接口。
DOMStringList: DOMStringList 接口提供对 DOMString 值的有序集合的抽象,没有定义或约束此集合是如何实现的。
Element: Element 接口表示 HTML 或 XML 文档中的一个元素。
Entity: 此接口表示在 XML 文档中解析和未解析的已知实体。
EntityReference: EntityReference 节点可以用来在树中表示实体引用。
NamedNodeMap: 实现 NamedNodeMap 接口的对象用于表示可以通过名称访问的节点的集合。
NameList NameList :接口提供对并行的名称和名称空间值对(可以为 null 值)的有序集合的抽象,无需定义或约束如何实现此集合。
Node: 该 Node 接口是整个文档对象模型的主要数据类型。
NodeList: NodeList 接口提供对节点的有序集合的抽象,没有定义或约束如何实现此集合。
Notation: 此接口表示在 DTD 中声明的表示法。
ProcessingInstruction: ProcessingInstruction 接口表示“处理指令”,该指令作为一种在文档的文本中保持特定于处理器的信息的方法在 XML 中使用。
Text: 该 Text 接口继承自 CharacterData,并且表示 Element 或 Attr 的文本内容(在 XML 中称为 字符数据)。
TypeInfo: TypeInfo 接口表示从 Element 或 Attr 节点引用的类型,用与文档相关的模式指定。
UserDataHandler: 当使用 Node.setUserData() 将一个对象与节点上的键相关联时,当克隆、导入或重命名该对象关联的节点时应用程序可以提供调用的处理程序。

core 模块
1.JdbcTeamplate 对象,其结构如下:
在这里插入图片描述
2.RowMapper
在这里插入图片描述
3.元数据 metaData 模块
Spring 应用到工厂模式,结合代码可以更具体了解。
在这里插入图片描述
在这里插入图片描述

CallMetaDataProviderFactory 创建 CallMetaDataProvider 的工厂类,其代码如下:

public class CallMetaDataProviderFactory {


   /** List of supported database products for procedure calls */
   public static final List<String> supportedDatabaseProductsForProcedures = Arrays.asList(
         "Apache Derby",
         "DB2",
         "MySQL",
         "Microsoft SQL Server",
         "Oracle",
         "PostgreSQL",
         "Sybase"
      );


   /** List of supported database products for function calls */
   public static final List<String> supportedDatabaseProductsForFunctions = Arrays.asList(
         "MySQL",
         "Microsoft SQL Server",
         "Oracle",
         "PostgreSQL"
      );


   private static final Log logger = LogFactory.getLog(CallMetaDataProviderFactory.class);




   /**
    * Create a CallMetaDataProvider based on the database metadata
    * @param dataSource used to retrieve metadata
    * @param context the class that holds configuration and metadata
    * @return instance of the CallMetaDataProvider implementation to be used
    */
   static public CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {
      try {
         CallMetaDataProvider result = (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
            String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
            boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData();
            if (context.isFunction()) {
               if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) {
                  if (logger.isWarnEnabled()) {
                     logger.warn(databaseProductName + " is not one of the databases fully supported for function calls " +
                           "-- supported are: " + supportedDatabaseProductsForFunctions);
                  }
                  if (accessProcedureColumnMetaData) {
                     logger.warn("Metadata processing disabled - you must specify all parameters explicitly");
                     accessProcedureColumnMetaData = false;
                  }
               }
            }
            else {
               if (!supportedDatabaseProductsForProcedures.contains(databaseProductName)) {
                  if (logger.isWarnEnabled()) {
                     logger.warn(databaseProductName + " is not one of the databases fully supported for procedure calls " +
                           "-- supported are: " + supportedDatabaseProductsForProcedures);
                  }
                  if (accessProcedureColumnMetaData) {
                     logger.warn("Metadata processing disabled - you must specify all parameters explicitly");
                     accessProcedureColumnMetaData = false;
                  }
               }
            }
            CallMetaDataProvider provider;
            if ("Oracle".equals(databaseProductName)) {
               provider = new OracleCallMetaDataProvider(databaseMetaData);
            }
            else if ("DB2".equals(databaseProductName)) {
               provider = new Db2CallMetaDataProvider((databaseMetaData));
            }
            else if ("Apache Derby".equals(databaseProductName)) {
               provider = new DerbyCallMetaDataProvider((databaseMetaData));
            }
            else if ("PostgreSQL".equals(databaseProductName)) {
               provider = new PostgresCallMetaDataProvider((databaseMetaData));
            }
            else if ("Sybase".equals(databaseProductName)) {
               provider = new SybaseCallMetaDataProvider((databaseMetaData));
            }
            else if ("Microsoft SQL Server".equals(databaseProductName)) {
               provider = new SqlServerCallMetaDataProvider((databaseMetaData));
            }
            else if ("HDB".equals(databaseProductName)) {
               provider = new HanaCallMetaDataProvider((databaseMetaData));
            }
            else {
               provider = new GenericCallMetaDataProvider(databaseMetaData);
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Using " + provider.getClass().getName());
            }
            provider.initializeWithMetaData(databaseMetaData);
            if (accessProcedureColumnMetaData) {
               provider.initializeWithProcedureColumnMetaData(databaseMetaData,
                     context.getCatalogName(), context.getSchemaName(), context.getProcedureName());
            }
            return provider;
         });
         return result;
      }
      catch (MetaDataAccessException ex) {
         throw new DataAccessResourceFailureException("Error retrieving database metadata", ex);
      }
   }


}

TableMetaDataProviderFactory 创建 TableMetaDataProvider 工厂类,其创建过程如下:

public class TableMetaDataProviderFactory {


   private static final Log logger = LogFactory.getLog(TableMetaDataProviderFactory.class);
   /**
    * Create a TableMetaDataProvider based on the database metadata.
    * @param dataSource used to retrieve metadata
    * @param context the class that holds configuration and metadata
    * @return instance of the TableMetaDataProvider implementation to be used
    */
   public static TableMetaDataProvider createMetaDataProvider(DataSource dataSource, TableMetaDataContext context) {
      try {
         TableMetaDataProvider result = (TableMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
            String databaseProductName =
                  JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
            boolean accessTableColumnMetaData = context.isAccessTableColumnMetaData();
            TableMetaDataProvider provider;
            if ("Oracle".equals(databaseProductName)) {
               provider = new OracleTableMetaDataProvider(databaseMetaData,
                     context.isOverrideIncludeSynonymsDefault());
            }
            else if ("HSQL Database Engine".equals(databaseProductName)) {
               provider = new HsqlTableMetaDataProvider(databaseMetaData);
            }
            else if ("PostgreSQL".equals(databaseProductName)) {
               provider = new PostgresTableMetaDataProvider(databaseMetaData);
            }
            else if ("Apache Derby".equals(databaseProductName)) {
               provider = new DerbyTableMetaDataProvider(databaseMetaData);
            }
            else {
               provider = new GenericTableMetaDataProvider(databaseMetaData);
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Using " + provider.getClass().getSimpleName());
            }
            provider.initializeWithMetaData(databaseMetaData);
            if (accessTableColumnMetaData) {
               provider.initializeWithTableColumnMetaData(databaseMetaData, context.getCatalogName(),
                     context.getSchemaName(), context.getTableName());
            }
            return provider;
         });
         return result;
      }
      catch (MetaDataAccessException ex) {
         throw new DataAccessResourceFailureException("Error retrieving database metadata", ex);
      }
   }


}

使用 SqlParameterSource 提供参数值
使用 Map 来指定参数值有时候工作得非常好,但是这并不是最简单的使用方式。Spring提供了一些其他的 SqlParameterSource 实现类来指定参数值。 我们首先可以看看BeanPropertySqlParameterSource 类,这是一个非常简便的指定参数的实现类,只要你有一个符合 JavaBean 规范的类就行了。它将使用其中的 getter 方法来获取参数值。
SqlParameter封装了定义sql参数的对象 。 CallableStateMentCallback ,PrePareStateMentCallback,StateMentCallback,ConnectionCallback 回调类分别对应 JdbcTemplate 中的不同处理方法。
在这里插入图片描述
simple 实现
在这里插入图片描述
DataSource
Spring 通过 DataSource 获取数据库的连接。Datasource 是 jdbc 规范的一部分,它通过 ConnectionFactory 获取。一个容器和框架可以在应用代码层中隐藏连接池和事务管理。
当使用 spring 的 jdbc 层,你可以通过 JNDI 来获取 DataSource,也可以通过你自己配置的第三方连接池实现来获取。流行的第三方实现由 apache Jakarta Commonsdbcp 和 c3p0。
在这里插入图片描述
TransactionAwareDataSourceProxy 作为目标 DataSource 的一个代理, 在对目标DataSource 包装的同时,还增加了 Spring 的事务管理能力, 在这一点上,这个类的功能非常像 J2EE 服务器所提供的事务化的 JNDI DataSource。
Note该类几乎很少被用到,除非现有代码在被调用的时候需要一个标准的 JDBC DataSource接口实现作为参数。 这种情况下,这个类可以使现有代码参与 Spring 的事务管理。通常最好的做法是使用更高层的抽象 来对数据源进行管理,比如 JdbcTemplate 和DataSourceUtils 等等。
注意:DriverManagerDataSource 仅限于测试使用,因为它没有提供池的功能,这会导致在多个请求获取连接时性能很差。

object 模块
在这里插入图片描述
JdbcTemplate
JdbcTemplate 是 core 包的核心类。它替我们完成了资源的创建以及释放工作,从而简化了我们对 JDBC 的使用。 它还可以帮助我们避免一些常见的错误,比如忘记关闭数据库连接。 JdbcTemplate 将完成 JDBC 核心处理流程,比如 SQL 语句的创建、执行,而把 SQL 语句的生成以及查询结果的提取工作留给我们的应用代码。 它可以完成 SQL 查询、更新以及调用存储过程,可以对 ResultSet 进行遍历并加以提取。它还可以捕获 JDBC异常并将其转换成 org.springframework.dao 包中定义的,通用的,信息更丰富的异常。使用 JdbcTemplate 进行编码只需要根据明确定义的一组契约来实现回调接口。PreparedStatementCreator 回调接口通过给定的 Connection 创建一个
PreparedStatement,包含 SQL 和任何相关的参数。 CallableStatementCreateor 实现同样的处理,只不过它创建的是 CallableStatement。 RowCallbackHandler 接口则从数据集的每一行中提取值。
我们可以在 DAO 实现类中通过传递一个 DataSource 引用来完成 JdbcTemplate 的实例化,也可以在 Spring 的 IOC 容器中配置一个 JdbcTemplate 的 bean 并赋予 DAO 实现类作为一个实例。 需要注意的是 DataSource 在 Spring 的 IOC 容器中总是配制成一个bean,第一种情况下,DataSource bean 将传递给 service,第二种情况下 DataSourcebean 传递给 JdbcTemplate bean。

NamedParameterJdbcTemplate
NamedParameterJdbcTemplate 类为 JDBC 操作增加了命名参数的特性支持,而不是传 统 的 使 用 ( ‘?’ ) 作 为 参 数 的 占 位 符 。 NamedParameterJdbcTemplate 类 对JdbcTemplate 类进行了封装, 在底层,JdbcTemplate 完成了多数的工作。

浅谈分布式事务

现今互联网界,分布式系统和微服务架构盛行。一个简单操作,在服务端非常可能是由多个服务和数据库实例协同完成的。在一致性要求较高的场景下,多个独立操作之间的一致性问题显得格外棘手。
基于水平扩容能力和成本考虑,传统的强一致的解决方案(e.g.单机事务)纷纷被抛弃。其理论依据就是响当当的 CAP 原理。往往为了可用性和分区容错性,忍痛放弃强一致支持,转而追求最终一致性。

分布式系统的特性

在分布式系统中,同时满足 CAP 定律中的一致性 Consistency、可用性 Availability 和分区容错性 Partition Tolerance 三者是不可能的。在绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。
分布式事务服务(Distributed Transaction Service,DTS)是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。
CAP 理论告诉我们在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,所以我们只能在一致性和可用性之间进行权衡。
为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性。

数据一致性理解

强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。
弱一致性:系统并不保证后续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。
最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS 是一个典型的最终一致性系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值