Spring事务

1.事务基本概念

1.1 事务

事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit).

特点:

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

1.2 事务的基本原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.

对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

  • 获取连接Connectioncon=DriverManager.getConnection()
  • 开启事务con.setAutoCommit(true/false)
  • 执行CRUD
  • 提交事务/回滚事务con.commit()/con.rollback()
  • 关闭连接conn.close()

使用Spring的事务管理功能后,我们可以不再写步骤2和4的代码,而是由Spirng自动完成. 那么Spring是如何在我们书写的CRUD之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了.下面简单地介绍下,注解方式为例子

配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识. spring在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务).

真正的数据库层的事务提交和回滚是通过binlog或者redolog实现的.

1.3 Spring事务的传播属性

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

常量名称 常量解释
PROPAGATION_REQUIRED 支持当前事务,如果当前没有事务,就新建一个事务.这是最常见的选择,也是Spring默认的事务的传播
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起.新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY 支持当前事务,如果当前没有事务,就抛出异常
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动事务,则按REQUIRED属性执行.它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点.内部事务的回滚不会对外部事务造成影响.它只对DataSourceTransactionManager事务管理器起效.

1.4 数据库隔离级别

隔离级别 隔离级别的值 导致的问题
Read-Uncommitted 0 导致脏读
Read-Committed 1 避免脏读,允许不可重复读和幻读
Repeatable-Read 2 避免脏读,不可重复读,允许幻读
Serializable 3 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读.执行效率慢,使用时慎重
  • 脏读: 一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据.如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据
  • 不可重复读: 一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的
  • 幻读: 第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改

总结:

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

1.5 Spring中的隔离级别

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

1.6 事务的嵌套

通过上面的理论知识的铺垫,我们大致知道了数据库事务和spring事务的一些属性和特点,接下来我们通过分析一些嵌套事务的场景,来深入理解spring事务传播的机制.

eg: 假设外层事务ServiceAMethodA()调用内层ServiceBMethodB()

  • 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_NESTED,此时两者之间又将如何协作呢?
    • ServiceB.MethodB如果rollback,那么内部事务(即ServiceB.MethodB)将回滚到它执行前的SavePoint而外部事务(即ServiceA.MethodA)可以有以下两种处理方式:
      • 捕获异常,执行异常分支逻辑.这种方式也是嵌套事务最有价值的地方,它起到了分支执行的效果,如果ServiceB.MethodB失败,那么执行ServiceC.MethodC(),而ServiceB.MethodB已经回滚到它执行之前的SavePoint,所以不会产生脏数据(相当于此方法从未执行过),这种特性可以用在某些特殊的业务中,而PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW都没有办法做到这一点.
      • 外部事务回滚/提交代码不做任何修改,那么如果内部事务(ServiceB.MethodB)rollback,那么首先ServiceB.MethodB回滚到它执行之前的SavePoint(在任何情况下都会如此),外部事务(即ServiceA.MethodA)将根据具体的配置决定自己是commit还是rollback.另外三种事务传播属性基本用不到,在此不做分析.

1.7 Spring事务API架构图

事务API架构图

2.基于SpringJDBC开发ORM框架

2.1 介绍

使用Spring进行基本的JDBC访问数据库有多种选择.Spring至少提供了三种不同的工作模式:

  • JdbcTemplate: 一个在Spring2.5中新提供的
  • SimpleJdbc: 能够更好的处理数据库元数据
  • RDBMSObject: 面向对象封装方式,有点类似于JDO的查询设计.

    我们在这里简要列举你采取某一种工作方式的主要理由.不过请注意,即使你选择了其中的一种工作模式,你依然可以在你的代码中混用其他任何一种模式以获取其带来的好处和优势.所有的工作模式都必须要求JDBC2.0以上的数据库驱动的支持,其中一些高级的功能可能需要JDBC3.0以上的数据库驱动支持.

JdbcTemplate 这是经典的也是最常用的Spring对于JDBC访问的方案.这也是最低级别的封装,其他的工作模式事实上在底层使用了JdbcTemplate作为其底层的实现基础.JdbcTemplate在JDK1.4以上的环境上工作得很好.

NamedParameterJdbcTemplate 对JdbcTemplate做了封装,提供了更加便捷的基于命名参数的使用方式而不是传统的JDBC所使用的"?"作为参数的占位符.这种方式在你需要为某个SQL指定许多个参数时,显得更加直观而易用.该特性必须工作在JDK1.4以上.

SimpleJdbcTemplate 这个类结合了JdbcTemplateNamedParameterJdbcTemplate的最常用的功能,同时它也利用了一些Java5的特性所带来的优势,例如泛型、varargsautoboxing等,从而提供了更加简便的API访问方式.需要工作在Java5以上的环境中.

SimpleJdbcInsert/SimpleJdbcCall 这两个类可以充分利用数据库元数据的特性来简化配置.通过使用这两个类进行编程,你可以仅仅提供数据库表名或者存储过程的名称以及一个Map作为参数.其中Map的key需要与数据库表中的字段保持一致.这两个类通常和SimpleJdbcTemplate配合使用.这两个类需要工作在JDK5以上,同时数据库需要提供足够的元数据信息.

RDBMS对象 包括MappingSqlQuery,SqlUpdateandStoredProcedure. 这种方式允许你在初始化你的数据访问层时创建可重用并且线程安全的对象.该对象在你定义了你的查询语句,声明查询参数并编译相应的Query之后被模型化.一旦模型化完成,任何执行函数就可以传入不同的参数对之进行多次调用.这种方式需要工作在JDK1.4以上.

2.2 异常处理异常结构如下

异常处理

  • SQLExceptionTranslator是一个接口,如果你需要在SQLExceptionorg.springframework.dao.DataAccessException之间作转换,那么必须实现该接口.转换器类的实现可以采用一般通用的做法(比如使用JDBCSQLStatecode),如果为了使转换更准确,也可以进行定制(比如使用Oracleerrorcode).
  • SQLErrorCodeSQLExceptionTranslatorSQLExceptionTranslator的默认实现.该实现使用指定数据库厂商的errorcode,比采用SQLState更精确.转换过程基于一个JavaBean(类型为SQLErrorCodes)中的errorcode.这个JavaBeanSQLErrorCodesFactory工厂类创建,其中的内容来自于sql-error-codes.xml配置文件.该文件中的数据库厂商代码基于DatabaseMetaData信息中的DatabaseProductName,从而配合当前数据库的使用.
  • SQLErrorCodeSQLExceptionTranslator使用以下的匹配规则:
    • 首先检查是否存在完成定制转换的子类实现.通常SQLErrorCodeSQLExceptionTranslator这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用.
    • 接着将SQLExceptionerrorcode与错误代码集中的errorcode进行匹配.默认情况下错误代码集将从SQLErrorCodesFactory取得.错误代码集来自classpath下的sql-error-codes.xml文件,它们将与数据库metadata信息中的databasename进行映射.
  • 使用fallback翻译器.SQLStateSQLExceptionTranslator类是缺省的fallback翻译器.

2.3 config模块

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

Config

jdbc模块,我们使用JdbcNamespaceHandler来处理jdbc配置的命名空间,其代码如下:


/**
 * {@link NamespaceHandler} for JDBC configuration namespace.
 */
public class JdbcNamespaceHandler extends NamespaceHandlerSupport {

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

其中,EmbeddedDatabaseBeanDefinitionParser继承了AbstractBeanDefinitionParser,解析<embedded-database>元素,并使用EmbeddedDatabaseFactoryBean创建一个BeanDefinition.顺便介绍一下用到的软件包org.w3c.dom.

软件包org.w3c.dom: 为文档对象模型(DOM)提供接口,该模型是JavaAPIforXMLProcessing的组件API.该DocumentObjectModelLevel2CoreAPI允许程序动态访问和更新文档的内容和结构.

  • 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接口的对象用于表示可以通过名称访问的节点的集合.
  • NameListNameList: 该接口提供对并行的名称和名称空间值对(可以为null值)的有序集合的抽象,无需定义或约束如何实现此集合.
  • Node: 该Node接口是整个文档对象模型的主要数据类型.
  • NodeList: NodeList接口提供对节点的有序集合的抽象,没有定义或约束如何实现此集合.
  • Notation: 此接口表示在DTD中声明的表示法.
  • ProcessingInstruction: ProcessingInstruction接口表示"处理指令",该指令作为一种在文档的文本中保持特定于处理器的信息的方法在XML中使用.
  • Text: 该Text接口继承自CharacterData,并且表示Element或Attr的文本内容(在XML中称为字符数据).
  • TypeInfo: TypeInfo接口表示从Element或Attr节点引用的类型,用与文档相关的模式指定.
  • UserDataHandler: 当使用Node.setUserData()将一个对象与节点上的键相关联时,当克隆、导入或重命名该对象关联的节点时应用程序可以提供调用的处理程序.

2.4 core模块

JdbcTeamplate对象 JdbcTeamplate

RowMapper RowMapper

元数据metaData模块 metaData

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

public final 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);


    private CallMetaDataProviderFactory() {
    }


    /**
     * Create a {@link CallMetaDataProvider} based on the database meta-data.
     * @param dataSource the JDBC DataSource to use for retrieving meta-data
     * @param context the class that holds configuration and meta-data
     * @return instance of the CallMetaDataProvider implementation to be used
     */
    public static CallMetaDataProvider createMetaDataProvider(DataSource dataSource, final CallMetaDataContext context) {
        try {
            return (CallMetaDataProvider) JdbcUtils.extractDatabaseMetaData(dataSource, databaseMetaData -> {
                String databaseProductName = JdbcUtils.commonDatabaseName(databaseMetaData.getDatabaseProductName());
                boolean accessProcedureColumnMetaData = context.isAccessCallParameterMetaData();
                if (context.isFunction()) {
                    if (!supportedDatabaseProductsForFunctions.contains(databaseProductName)) {
                        if (logger.isInfoEnabled()) {
                            logger.info(databaseProductName + " is not one of the databases fully supported for function calls " +
                                    "-- supported are: " + supportedDatabaseProductsForFunctions);
                        }
                        if (accessProcedureColumnMetaData) {
                            logger.info("Metadata processing disabled - you must specify all parameters explicitly");
                            accessProcedureColumnMetaData = false;
                        }
                    }
                }
                else {
                    if (!supportedDatabaseProductsForProcedures.contains(databaseProductName)) {
                        if (logger.isInfoEnabled()) {
                            logger.info(databaseProductName + " is not one of the databases fully supported for procedure calls " +
                                    "-- supported are: " + supportedDatabaseProductsForProcedures);
                        }
                        if (accessProcedureColumnMetaData) {
                            logger.info("Metadata processing disabled - you must specify all parameters explicitly");
                            accessProcedureColumnMetaData = false;
                        }
                    }
                }

                CallMetaDataProvider provider;
                if ("Oracle".equals(databaseProductName)) {
                    provider = new OracleCallMetaDataProvider(databaseMetaData);
                }
                else if ("PostgreSQL".equals(databaseProductName)) {
                    provider = new PostgresCallMetaDataProvider((databaseMetaData));
                }
                else if ("Apache Derby".equals(databaseProductName)) {
                    provider = new DerbyCallMetaDataProvider((databaseMetaData));
                }
                else if ("DB2".equals(databaseProductName)) {
                    provider = new Db2CallMetaDataProvider((databaseMetaData));
                }
                else if ("HDB".equals(databaseProductName)) {
                    provider = new HanaCallMetaDataProvider((databaseMetaData));
                }
                else if ("Microsoft SQL Server".equals(databaseProductName)) {
                    provider = new SqlServerCallMetaDataProvider((databaseMetaData));
                }
                else if ("Sybase".equals(databaseProductName)) {
                    provider = new SybaseCallMetaDataProvider((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;
            });
        }
        catch (MetaDataAccessException ex) {
            throw new DataAccessResourceFailureException("Error retrieving database meta-data", ex);
        }
    }

}

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

public final class TableMetaDataProviderFactory {

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


    private TableMetaDataProviderFactory() {
    }


    /**
     * Create a {@link TableMetaDataProvider} based on the database meta-data.
     * @param dataSource used to retrieve meta-data
     * @param context the class that holds configuration and meta-data
     * @return instance of the TableMetaDataProvider implementation to be used
     */
    public static TableMetaDataProvider createMetaDataProvider(DataSource dataSource, TableMetaDataContext context) {
        try {
            return (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 ("PostgreSQL".equals(databaseProductName)) {
                    provider = new PostgresTableMetaDataProvider(databaseMetaData);
                }
                else if ("Apache Derby".equals(databaseProductName)) {
                    provider = new DerbyTableMetaDataProvider(databaseMetaData);
                }
                else if ("HSQL Database Engine".equals(databaseProductName)) {
                    provider = new HsqlTableMetaDataProvider(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;
            });
        }
        catch (MetaDataAccessException ex) {
            throw new DataAccessResourceFailureException("Error retrieving database meta-data", ex);
        }
    }

}

使用SqlParameterSource提供参数值 使用Map来指定参数值有时候工作得非常好,但是这并不是最简单的使用方式. Spring提供了一些其他的·SqlParameterSource·实现类来指定参数值. 我们首先可以看看·BeanPropertySqlParameterSource·类,这是一个非常简便的指定参数的实现类,只要你有一个符合·JavaBean·规范的类就行了.它将使用其中的getter方法来获取参数值. SqlParameter封装了定义sql参数的对象.CallableStateMentCallback,PrePareStateMentCallback,StateMentCallback,ConnectionCallback回调类分别对应JdbcTemplate中的不同处理方法.

SqlParameter

simple实现

Simple

2.5 DataSource

spring通过DataSource获取数据库的连接.Datasource是jdbc规范的一部分,它通过ConnectionFactory获取.一个容器和框架可以在应用代码层中隐藏连接池和事务管理. 当使用spring的jdbc层,你可以通过JNDI来获取DataSource,也可以通过你自己配置的第三方连接池实现来获取.流行的第三方实现由apacheJakartaCommonsdbcpc3p0.

DataSource

TransactionAwareDataSourceProxy作为目标DataSource的一个代理,在对目标DataSource包装的同时,还增加了Spring的事务管理能力,在这一点上,这个类的功能非常像J2EE服务器所提供的事务化的JNDIDataSource. Note: 该类几乎很少被用到,除非现有代码在被调用的时候需要一个标准的JDBCDataSource接口实现作为参数.这种情况下,这个类可以使现有代码参与Spring的事务管理.通常最好的做法是使用更高层的抽象来对数据源进行管理,比如JdbcTemplateDataSourceUtils等等. 注意: DriverManagerDataSource仅限于测试使用,因为它没有提供池的功能,这会导致在多个请求获取连接时性能很差.

事务序列图

2.6 object模块

Object

2.7 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,第一种情况下,DataSourcebean将传递给service,第二种情况下DataSourcebean传递给JdbcTemplatebean.

2.7 NamedParameterJdbcTemplate

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

3.浅谈分布式事务

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

分布式系统的特性 在分布式系统中,同时满足CAP定律中的一致性Consistency、可用性Availability和分区容错性PartitionTolerance三者是不可能的.在绝大多数的场景,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性.

分布式事务服务(DistributedTransactionService,DTS)是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性.

CAP理论告诉我们在分布式存储系统中,最多只能实现上面的两点.而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的,所以我们只能在一致性和可用性之间进行权衡. 为了保障系统的可用性,互联网系统大多将强一致性需求转换成最终一致性的需求,并通过系统执行幂等性的保证,保证数据的最终一致性.

数据一致性理解

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

results matching ""

    No results matching ""