如何让 spring JdbcTemplate 读取_未提交?

Posted

技术标签:

【中文标题】如何让 spring JdbcTemplate 读取_未提交?【英文标题】:How can I get a spring JdbcTemplate to read_uncommitted? 【发布时间】:2012-07-25 18:32:24 【问题描述】:

首先,我不能使用声明性@Transactional 方法,因为应用程序有多个 JDBC 数据源,我不想对细节感到厌烦,但只要说 DAO 方法传递了正确的数据就足够了-source 来执行逻辑。所有 JDBC 数据源都具有相同的架构,它们是分开的,因为我要为 ERP 系统公开其余服务。

由于这个遗留系统,有很多我无法控制的长期锁定记录,所以我想要脏读。

使用 JDBC 我将执行以下操作:

private Customer getCustomer(DataSource ds, String id) 
    Customer c = null;
    PreparedStatement stmt = null;
    Connection con = null;
    try 
        con = ds.getConnection();
        con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
        stmt = con.prepareStatement(SELECT_CUSTOMER);
        stmt.setString(1, id);
        ResultSet res = stmt.executeQuery();
        c = buildCustomer(res);
     catch (SQLException ex) 
        // log errors
     finally 
        // Close resources
    
    return c;

好吧,我知道有很多样板。所以我试用了JdbcTemplate,因为我使用的是spring。

使用 JdbcTemplate

private Customer getCustomer(JdbcTemplate t, String id) 
    return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);

好多了,但它仍然使用默认的事务隔离。我需要以某种方式改变这一点。所以我考虑使用TransactionTemplate

private Customer getCustomer(final TransactionTemplate tt,
                             final JdbcTemplate t,
                             final String id) 
    return tt.execute(new TransactionCallback<Customer>() 
        @Override
        public Customer doInTransaction(TransactionStatus ts) 
            return t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
        
    );

但是这里如何设置事务隔离呢?我在回调或TransactionTemplate 的任何地方都找不到它来执行此操作。

我正在阅读 Spring in Action,第三版,其中解释了我所做的,尽管关于事务的章节继续使用带有注释的声明性事务,但如前所述,我不能将其用作我的 DAO 需要在运行时根据提供的参数(在我的例子中是国家代码)确定使用哪个数据源。

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

如果不使用 Spring 提供的“事务”抽象级别,我不确定您是否可以做到这一点。

构建您的 transactionTemplate 的更“无 xml”可能是这样的。

private TransactionTemplate getTransactionTemplate(String executionTenantCode, boolean readOnlyTransaction) 
    TransactionTemplate tt = new TransactionTemplate(transactionManager);
    tt.setReadOnly(readOnlyTransaction);
    tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    return tt;


在任何情况下,我都会“利用”@Transactional 注释,指定适当的事务管理器,并与单独的数据源绑定。我已经为多租户应用程序完成了此操作。

用法:

@Transactional(transactionManager = CATALOG_TRANSACTION_MANAGER, 
    isolation = Isolation.READ_UNCOMMITTED, 
    readOnly = true)
public void myMethod() 
  //....

bean 声明:

public class CatalogDataSourceConfiguration 
    
    @Bean(name = "catalogDataSource")
    @ConfigurationProperties("catalog.datasource")
    public DataSource catalogDataSource() 
        return DataSourceBuilder.create().build();
    

    @Bean(name = ENTITY_MANAGER_FACTORY)
    public EntityManagerFactory entityManagerFactory(
            @Qualifier("catalogEntityManagerFactoryBean") LocalContainerEntityManagerFactoryBean emFactoryBean) 
        return emFactoryBean.getObject();
    

    @Bean(name= CATALOG_TRANSACTION_MANAGER)
    public PlatformTransactionManager catalogTM(@Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory emf) 
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    

    @Bean
    public NamedParameterJdbcTemplate catalogJdbcTemplate() 
        return new NamedParameterJdbcTemplate(catalogDataSource());
    


【讨论】:

【参考方案2】:

在这里使用TransactionTemplate 可以帮助您,您需要适当地配置它。交易模板还包含交易配置。实际上TransactionTemplate 扩展了DefaultTransactionDefinition

所以在你的配置中的某个地方你应该有这样的东西。

<bean id="txTemplate" class=" org.springframework.transaction.support.TransactionTemplate">
  <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
  <property name="readOnly" value="true" />
  <property name="transactionManager" ref="transactionManager" />
</bean>

如果您随后将该 bean 注入到您的类中,您应该能够使用您之前发布/尝试过的基于 TransactionTemplate 的代码。

但是,可能有更好的解决方案可以清理您的代码。对于我从事的一个项目,我们的设置与您的类似(单个应用程序多个数据库)。为此,我们编写了一些 spring 代码,它们基本上在需要时切换数据源。更多信息可以找到here。

如果这对您的应用程序来说过于牵强或过度,您也可以尝试使用 Spring 的 AbstractRoutingDataSource,它基于查找键(在您的情况下为国家代码)选择要使用的正确数据源。

通过使用这两种解决方案中的任何一种,您都可以开始使用 springs declarative transactionmanagement 方法(这应该会大大清理您的代码)。

【讨论】:

这是否意味着我需要为每个数据库和每个隔离配置多个 txTemplate?因此,如果我想要 6 个数据库的可写隔离和只读脏隔离,我需要 12 个 txTemplates? 要么,要么在需要时自己构建它们,这将要求您传入事务管理器并相应地设置配置。我想最好的解决方案是使用 AbstractRoutingDataSource 这样你就可以利用 springs 声明式事务管理(并在运行中清理你的代码)。【参考方案3】:

定义一个代理数据源,类为org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy,并设置事务隔离级别。通过 setter 或构造函数注入实际的数据源。

豆>

【讨论】:

【参考方案4】:

我目前已经通过直接使用DataSourceTransactionManager 解决了这个问题,尽管看起来我并没有像我最初希望的那样节省尽可能多的样板。别误会,它更干净,虽然我还是忍不住觉得一定有更简单的方法。我不需要读取事务,我只想设置隔离。

private Customer getCustomer(final DataSourceTransactionManager txMan,
                             final JdbcTemplate t,
                             final String id) 
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);

    TransactionStatus status = txMan.getTransaction(def);
    Customer c = null;
    try 
        c = t.queryForObject(SELECT_CUSTOMER, new CustomerRowMapper(), id);
     catch (Exception ex) 
        txMan.rollback(status);
        throw ex;
    
    txMan.commit(status);
    return c;

我仍然会暂时不回答这个问题,因为我真的相信一定有更好的方法。

参考Spring 3.1.x Documentation - Chapter 11 - Transaction Management

【讨论】:

这是一个古老的问题和答案,所以我只是想是否有解决您描述的问题的新方法。我认为,新版本事务注释中的新事务管理器属性解决了问题Spring 3.1 Transactional 那么提交和回滚是否会用于这个只读查询?它没有进行插入或更新,只是想澄清一下,因为我不知道 为什么读取未提交查询需要回滚?

以上是关于如何让 spring JdbcTemplate 读取_未提交?的主要内容,如果未能解决你的问题,请参考以下文章

Spring jdbcTemplate

Spring 模板技术JdbcTemplate

Spring 模板技术JdbcTemplate

spring 如何获取 jdbctemplate

Spring对JDBC的模板支持——JdbcTemplate

如何使用 Spring Framework 中的 JdbcTemplate 类执行 INSERT 语句