【问题标题】:How can I get a spring JdbcTemplate to read_uncommitted?如何让 spring JdbcTemplate 读取_未提交?
【发布时间】: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 需要在运行时根据提供的参数(在我的例子中是国家代码)确定使用哪个数据源。

任何帮助将不胜感激。

【问题讨论】:

    标签: java spring jdbc jdbctemplate spring-jdbc


    【解决方案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
        • 那么提交和回滚是否会用于这个只读查询?它没有进行插入或更新,只是想澄清一下,因为我不知道
        • 为什么读取未提交查询需要回滚?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-01
        • 2011-05-12
        • 2010-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-13
        相关资源
        最近更新 更多