Spirng中的事务和数据库连接池

参考体系文章:https://blog.csdn.net/weixin_35369702/article/details/116411179

DataSource的种类

DataSource的基本角色是ConnectionFactory,所有的数据库连接将通过DataSource接口统一管理。

DataSource实现类根据功能强弱可以划分为以下三类:

简单的DataSource实现

org.springframework.jdbc.datasource.DriverManagerDataSource.

顾名思义,DriverManagerDataSource的提出,主要是为了替换最古老的基于java.sql.DriverManager获取连接的方式。

Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("jdbc:mysql://110.40.155.17:3306/test?userSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8","root","126433");

DriverManagerDataSource继承体系结构

  • 通过IOC容器使用该DriverManagerDataSource的方法如下:
@Configuration
@Data
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceConfig {
    private String url;
    private String username;
    private String password;
    private String driverClassName;

    @Bean
    public DriverManagerDataSource driverManagerDataSource(){
        DriverManagerDataSource source = new DriverManagerDataSource();
        source.setUrl(url);
        source.setDriverClassName(driverClassName);
        source.setPassword(password);
        source.setUsername(username);
        return source;
    }
}

SingleConnectionDataSource

org.springframework.jdbc.datasource.SingleConnectionDataSource

  • 通过IOC容器使用该singleConnectionDataSource的方法如下:
    @Bean
    public SingleConnectionDataSource singleConnectionDataSource(){
        SingleConnectionDataSource source = new SingleConnectionDataSource();
        source.setUrl(url);
        source.setDriverClassName(driverClassName);
        source.setPassword(password);
        source.setUsername(username);
        //可以设置是否开启自动提交---默认为true
        source.setAutoCommit(true);
        //close方法调用,也不会关闭连接
        source.setSuppressClose(false);
        return source;
    }

拥有连接缓冲池的DataSource实现

常见的数据库连接池有cp30,driud等,这需要引入额外的依赖,这里不多进行演示。

对于这类DataSource,还需要额外指定连接池大小等参数。

支持[分布式事务的DataSource

自定义DataSource

Spring提供了DelegatingDataSource的几个实现类

多数据源

主权独立的数据源

所谓主权独立是指系统中的每个数据源都对外独立承担公开数据库资源的职能:

该种数据源在spring中的简单使用如下:

public class Main {
    public static void main(String[] args) {
        DataSource mainDataSource = getMainDataSource();
        JdbcTemplate main=new JdbcTemplate(mainDataSource);
        DataSource otherDataSource = getOtherDataSource();
        JdbcTemplate other = new JdbcTemplate(otherDataSource);
    }

    private static javax.sql.DataSource getMainDataSource() {
        BasicDataSource basicDataSource = new BasicDataSource();
        YamlUtil yamlUtil = new YamlUtil("application.yml");
        basicDataSource.setDriverClassName(yamlUtil.get("spring.datasource.driver-class-name"));
        basicDataSource.setUrl(yamlUtil.get("spring.datasource.url"));
        basicDataSource.setUsername(yamlUtil.get("spring.datasource.username"));
        basicDataSource.setPassword(yamlUtil.get("spring.datasource.password"));
        return basicDataSource;
    }

    private static javax.sql.DataSource getOtherDataSource() {
        //如何获取看具体的业务需求
        ...
        return basicDataSource;
    }
}

只需要不同的JdbcTemplate拥有不同的DataSource即可。

合作连横的多数据源

AbstractRoutingDataSource原理

  • getConnection方法会调用determineTargetDataSource来动态获得一个数据库连接
	@Override
	public Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}

  • determineTargetDataSource根据determineCurrentLookupKey返回值决定从dataSource集合中选择哪一个DataSource
	protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		//determineCurrentLookupKey抽象方法,负责返回指定的key,然后去集合中动态获取到目标的dataSource
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}

  • 存储dataSource的map集合
	@Nullable
	private Map<Object, Object> targetDataSources;

这里演示一下使用AbstractRoutingDataSource实现多数据源的管控:

@Data
public class DynamicDataSource extends AbstractRoutingDataSource {
    private String datasource;
    @Override
    protected Object determineCurrentLookupKey() {
        return datasource;
    }
}
public class DataSourceTestHandler {
    /**
     * 获得helper数据库的数据源
     */
    public static DataSource getHelperDataSource(){
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("xxx");
        dataSource.setUsername("xxx");
        dataSource.setPassword("xxx");
        return dataSource;
    }

    /**
     * 获得training数据库的数据源
     */
    public static DataSource getTrainingDataSource(){
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("xxx");
        dataSource.setUsername("xxx");
        dataSource.setPassword("xxx");
        return dataSource;
    }
    
    public static DataSource getManagerDataSource(){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object,Object> sources=new HashMap<>();
        sources.put("helper",getHelperDataSource());
        sources.put("training",getTrainingDataSource());
        dynamicDataSource.setTargetDataSources(sources);
        return dynamicDataSource;
    }
}

动态切换测试:

    /**
     * 多数据源结果测试
     */
    @ResponseBody
    @GetMapping("/{dataSource}/{tableName}")
    public AjaxResponse testMultiplyDataSource(@PathVariable(name = "dataSource") String dataSource,
                                               @PathVariable(name = "tableName")String tableName) throws SQLException {
          log.info("需要进行测试的数据源为: {}",dataSource);
          return testService.testMultiplyDataSource(dataSource,tableName);
    }

小结

合纵连横和主权独立可以联合使用:

可以用于存在多个数据源的场景,并且每个数据源存在多个实例,需要进行负载均衡

  • 主权独立并入合纵连横

Spring事务

事务家族

事务的大家族中常会出现下面几个重要的家庭成员:

  • Resource Manager: 简称RM,可以将其看做是数据的管理员,例如,数据库服务器(mysql),消息服务器等
  • Transaction Processing Monitor: 简称TPM或者TP Monitor,他负责在分布式场景下协调多个RM的事务处理。
  • Transaction Manager: 简称TM,它可以认为是TP Monitor的核心模块,直接负责多RM之间事务处理的协调工作,并提供事务界定,事务上下文传播等功能接口。

我们通常会按照事务中涉及的RM的多寡将事务分为两类,即全局事务和局部事务.

全局事务

如果整个事务处理过程中存在多个RM,那么就需要通过TP Monitor来协调多RM间的事务一致性。TP Monitor通过两阶段提交协议来确保整个事务的ACID属性。

通常这种场景下的事务,被称为全局事务或者分布式事务。

所有应用程序提交的事务请求,都需要通过TP Monitor的调配之后,直接由TM统一管理,TM将使用两阶段提交协议来协调多RM之间的事务处理。

两阶段提交的过程可类比如下场景:

但是如果其中一方不愿意呢?

只有在双方都确认的条件下面,整个事务才能顺利提交,否则一旦一方反悔,事务就必须回滚到之前的状态

局部事务

如果当前事务中只有一个RM参与其中,我们就可以称当前事务为局部事务。

比如,在当前事务中只对一个数据库进行更新,或者只向一个消息队列中发送消息的情况,都属于局部事务。

因为局部事务只存在一个RM,因此没有必要引入相应的TP Monitor来帮助协调管理多个RM之间的事务处理。

通常情况下,相应的RM内部都有内置的事务支持,所以,在局部事务中,我们更倾向于之间使用RM的内置事务支持。

注意:

局部事务与全局事务的区分在于事务中涉及到的RM数量,而不是系统中实际有多少RM,因为即使系统中存在多个数据库(即RM),只要当前事务只更新一个数据库的数据,那么当前事务就应该算作局部事务,而不是全局事务。

对于局部事务而言,一般都直接使用RM内置的事务支持,当然也可以通过引入TP Monitor在分布式事务场景下进行事务管理(但是显然没必要)。

通过情况下,各个TP Monitor在实现的时候都会判断当前参与事务的RM数量,如果只有一个RM参与,那么会做一定优化处理,避免使用两阶段提交协议带来额外的性能损耗。

Java事务管理

Java平台的局部事务支持

在Java的局部事务场景中,系统里事务管理的具体处理方式,会随着所使用的数据访问技术的不同而各异。

我们不是使用专用的事务API来管理事务,而是通过当前使用的数据访问技术所提供的基于connection的api来管理事务。

要对数据库的访问过程中的事务进行管理,每种数据访问技术都提供了特定与它自身的事务管理API,比如JDBC是Java平台访问关系数据库最基础的API。

如果直接使用JDBC进行数据访问的话,我们可以将数据库连接的自动提交设置为false,改为手动提交来控制整个事务的提交或者回滚。

  public boolean giveMoney(String Giver,String Revicer,int money)  {
           ....
           try{
            conn = JDBCUtil.getConnection();
            //开启事务
            conn.setAutoCommit(false);
            ....
            //结束事务
            conn.commit();
            return true;
        } catch (SQLException throwables) {
            //事务进行回滚
                conn.rollback();
       ....
    }

Java平台分布式事务支持

Java平台上的分布式事务管理,主要是通过JTA或者JCA提供支持的。

基于JTA的分布式事务管理

JTA是Sun公司提出的标准化分布式事务访问的Java接口规范。不过,JTA规范定义的知识一套Java的接口定义,具体的实现留给了相应的提供商去实现,各个Java EE应用服务器需要提供对JTA的支持。另外,除了可以使用绑定到Java EE应用服务器的JTA实现之外,Java平台也存在几个独立的并且比较成熟的JTA实现产品,包括:

  • JOTM
  • Atomikos
  • JBoss Transactions
基于JCA的分布式事务管理

JCA规范主要面向EIS的集成,通过为遗留的EIS系统和JAVA EE应用服务器指定统一的通信标准,二者可以实现各种服务上的互通。

Java事务支持的缺陷

  • 事务处理逻辑与业务代码耦合,导致事务管理代码在数据访问层和业务服务层的到处散落;
  • 事务处理过程中抛出的异常不统一,应该抛出unchecked exception,但是有些事务管理代码抛出的依然是chcked exception,这将强制客户端代码捕捉并处理它。并且缺乏统一的事务异常管理体系;
  • 不同的数据源提供的事务管理api不同,因此需要一个更高级的抽象,帮助我们统一事务管理API。

显然,用过spring的小伙伴都体验过了spring的全套事务服务带来的舒爽体验,也解决了上面提到的这些问题,并且还更加强大,那么下面就来一起探究一下吧。

Spring事务王国

org.springframework.transaction.PlatformTransactionManager是Spring事务王国的核心接口,它规定了为应用程序提供事务界定的统一方式。

public interface PlatformTransactionManager extends TransactionManager {
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}

PlatformTransactionManager是整个事务抽象策略的顶层接口,Spring的事务框架针对不同的数据访问方式以及全局事务场景,提供相应的实现类。

如果让你给出一个PlatformTransactionManager的自定义实现类,how do you do it ?

这里先用JDBC数据访问方式的局部事务为例:我们通常是将事务管理放在Service层,而将数据访问逻辑放在Dao层。这样可以提高数据访问逻辑的重用性,并且在Service层根据相应的逻辑来决定是提交或者回滚事务。

因为JDBC的局部事务控制是需要通过同一个Connection来完成的,因此要保证两个DAO的数据访问处于同一个事务中,就需要保证它们使用的是同一个connection。

要完成这点,通常我们会采用connection-passing的方式,即为同一个事务中的各个dao的数据访问方法传递当前的Connection。

  • 注意,事务开启前要取消当前连接的自动提交,事务结束后,要恢复当前连接的自动提交

上面这种事务管控方式最大的问题在于事务代码无法摆脱connection的束缚,导致connection与当前业务代码耦合

我们应该将事务过程中用到的Connection实例放置于一个统一地点,这样就解除了事务管理代码和数据访问代码直接通过connection的耦合。

有一个办法是: 事务开始前取得一个connection,然后将这个connection绑定到当前调用线程。之后,数据访问对象在使用connection进行数据访问的时候,就可以从当前线程上获得这个事务开始时候绑定的connection实例。

当所有的数据访问都完成后,我们就可以将connection提交或者回滚事务,然后解除它到当前线程的绑定。

如果要让我们来自定义一个存放当前线程绑定的conn的类,那么大概会长下面这样:

public class TransactionResourceManager {
    private static ThreadLocal resources=new ThreadLocal();

    public static Object getResource(){
        return resources.get();
    }

    public static void bindResource(Object resource){
        resources.set(resource);
    }

    public static Object unbindResource(){
        Object resource = getResource();
        resources.set(null);
        return resource;
    }
}

对于我们要实现的针对JDBC的PlatformTransactionManager ,只需要在事务开始的时候,通过我们的TransactionResourceManager 将connection绑定到线程,然后再事务结束的时候解除绑定即可。

这里给出JdbcTransactionManager简单实现:

public class JdbcTransactionManager implements PlatformTransactionManager {
   private DataSource dataSource;

    public JdbcTransactionManager(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        try {
            Connection connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            TransactionResourceManager.bindResource(connection);
            return new DefaultTransactionStatus(connection,true,true,false,true,null);
        } catch (SQLException e) {
            throw new CannotCreateTransactionException("cannot get connection for tx",e);
        }
        
    }


    @Override
    public void commit(TransactionStatus status) throws TransactionException {
        Connection connection = (Connection) TransactionResourceManager.unbindResource();
        try {
            connection.commit();
        } catch (SQLException e) {
            throw new UnexpectedRollbackException("commit failed with SQLex",e);
        }finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }


    @Override
    public void rollback(TransactionStatus status) throws TransactionException {
        Connection connection = (Connection) TransactionResourceManager.unbindResource();
        try {
            connection.rollback();
        } catch (SQLException e) {
            throw new UnexpectedRollbackException("rollback failed with SQLex",e);
        }finally {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

当然上面给出的是简单的实现,spring可没有那么容易让你看透,上面给出的代码还存在以下问题:

  • 如何确保PlatformTransactionManager中方法按序调用,而不会因为错误或者没有调用相关方法,导致资源泄露和事务管理代码混乱;
  • 如果某个数据访问对象不需要上面提供的事务支持,那么该如何获得connection进行数据访问呢?

spring提供的DataSourceUtils类就是用来完成对connection的管理,DataSourceUtils会从类似TransactionResourceManager 的类中获取connection资源,如果当前线程之前没有绑定任何connection,那么它就通过数据访问对象的DataSource引用获取新的connection,否则就是要绑定的那个Connection

这就是为什么要强调,当我们使用spring提供的事务支持的时候,必须通过DataSourceUtils来获取连接,因为它提供了Spring事务管理框架在数据访问层需要提供的基础设置中不可或缺的一部分。

三大护法

Spring的事务王国,有三大护法,他们三个共同维护着事务的一致性,他们分别是:

  • PlatformTransactionManager: 负责界定事务边界
  • TransactionDefinition: 负责定义事务相关属性,包括隔离级别,传播行为
  • TransactionStatus: 记录从事务开启到事务结束期间的事务状态,我们也可以利用其对事务进行有限的控制
TransactionDefinition

TransactionDefinition主要定义了事务属性相关配置:

  • 事务的隔离级别
  • 事务的传播行为
  • 事务的超时时间
  • 是否为只读事务

TransactionDefinition内定义了如下五个常量用于标志可供选择的隔离级别:

  • ISOLATION_DEFAULT: 使用数据库默认的隔离级别
  • ISOLATION_READ_UNCOMMITTED: 对应read uncommited隔离级别,无法避免脏读,不可重复读和幻读。
  • ISOLATION_READ_COMMITTED: 对应read commited隔离级别,可以避免脏读,但无法避免不可重复读和幻读。
  • ISOLATION_REPEATABLE_READ: 对应repeatable read隔离级别,可以避免脏读和不可重复读,但不能避免幻读。
  • ISOLATION_REPEATABLE_READ: 对应serializable隔离级别,可以避免所有的脏读,不可重复读和幻读

事务的传播行为涉及到一个事务在执行过程中,调用涉及其他事务时,相关事务的表现行为,如下图所示:

TransactionDefinition为事务的传播行为定义了下面几种选择:

  • PROPAGATION_REQUIRED: 如果当前存在一个事务,则加入当前事务,如果不存在任何事务,则创建一个新的事务。总之,要至少确保在一个事务中运行。并且此传播行为也是默认的事务传播行为。
  • PROPAGATION_SUPPORTS: 如果当前存在一个事务,则加入当前事务,如果不存在事务,则直接执行。对于一些查询方法来说,PROPAGATION_SUPPORTS通过是比较合适的传播行为选择。
  • PROPAGATION_MANDATORY:强制要求当前存在一个事务,如果不存在,则抛出异常。如果某个方法需要事务支持,但自身又不管理事务提交或者回滚,那么比较适合PROPAGATION_MANDATORY。
  • PROPAGATION_REQUIRES_NEW: 不管当前是否存在事务,都会创建新的事务。如果当前存在事务,会将当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED: 不支持当前事务,而是在没有事务的情况下才会执行,如果当前存在事务,当前事务会被挂起
  • PROPAGATION_NEVER:永远不需要当前存在事务,如果存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则与PROPAGATION_REQUIRED行为类似,即创建新事务,在新创建的事务中执行。

注意区分PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW:

  • PROPAGATION_REQUIRES_NEW创建的新事务与外层事务属于同一个档次,即二者的地位是相同的。当新创建的事务运行时,外层事务将被挂起。
  • 而PROPAGATION_NESTED创建的嵌套事务则不一样,他是寄生于当前外层事务的,它的地位比当前外层事务的地位要小一号。当内部嵌套事务运行的时候,外层事务也是处理active状态。

TransactionDefinition提供了TIMEOUT_DEFAULT常量定义,用来指定事务的超时时间。该值默认为-1,这会采用当前事务系统默认的超时时间,如果 底层的transaction manager不支持TIMEOUT_DEFAULT,那么必须将TIMEOUT_DEFAULT设置为-1,否则会抛出异常

TransactionDefinition提供的最后一个功能是是否将要创建一个只读的事务,如果 底层的transaction manager不支持只读事务,那么就不会理睬该设定

基本的事务属性我都在底层接口规定好,但是当我们需要切换不同规定底层实现时,底层的transaction manager不一定支持所有的属性,也可能有自己特有的属性控制

TransactionDefinition继承结构图如下:

TransactionStatus

TransactionStatus是记录整个事务处理过程中的事务状态,更多的时候,我们将在编程式事务中使用该接口。

PlatformTransactionManager

public interface PlatformTransactionManager extends TransactionManager {
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;
}

PlatformTransactionManager的整个抽象体系基于策略模式实现,由PlatformTransactionManager对事务界定进行统一抽象,而具体的界定策略的实现则交由具体的实现类。

PlatformTransactionManager的实现类可以划分为面向局部事务和面向全局事务两个分支:

面向局部事务的PlatformTransactionManager实现类

Spring为各种数据访问技术提供了现成的PlatformTransactionManager实现支持。

别的都暂时不看,我们只看最熟悉的一个DataSourceTransactionManager,它对应的就是JDBC和MyBaits。

DataSourceTransactionManager实现剖析

在进行剖析之前,我们先理清楚Spring事务处理过程中涉及到的一些模型:

  • transaction Object: 承载当前事务的必要信息,DataSourceTransactionManager根据这些信息来决定如何处理当前事务
  • TransactionSynchronization: 可以注册到事务处理过程中的回调接口。当事务处理到某些过程处时,会回调该类中指定方法进行操作,如:事务处理完成后清理相应的系统资源操作(可类比bean的后置处理器)
  • TransactionSynchronizationManager: 通过该类来管理TransactionSynchronization,当前事务状态以及具体的事务资源。当然还有与事务相关的Synchronization的管理。

AbstractPlatformTransactionManager作为DataSourceTransactionManager的父类,以模板方法的形式封装了固定的事务处理逻辑,而将事务资源相关的操作以protected或者abstract方法的形式留给DataSourceTransactionManager来实现。

作为模板方法父类,AbstractPlatformTransactionManager替子类实现了一下固定的事务内部处理逻辑:

  • 判定是否存在当前事务,然后根据不同的判断结果执行不同的处理逻辑。
  • 结合是否存在当前事务的情况,根据TransactionDefinition中指定的传播行为的不同语义执行后继逻辑。
  • 根据情况挂起或者恢复事务
  • 提交事务之前检查readonly字段是否被设置,如果被设置的话,就回滚事务
  • 在事务回滚的情况下,清理并恢复事务状态
  • 如果事务的Synchronization处理active状态,在事务处理的规定位置处调用注册的Synchronization回调接口

文章转载:https://cjdhy.blog.csdn.net/article/details/125223905

面向全局事务的PlatformTransactionManager实现类

全局事务主要由JtaTransactionManager负责,JtaTransactionManager负责对各种JTA实现提供的分布式事务进行统一封装,只不过它的事务管理操作,最终都会委派给具体的JTA实现来完成。

posted @ 2022-10-08 17:52  雩娄的木子  阅读(504)  评论(0编辑  收藏  举报