【问题标题】:Java Database Transaction ManagementJava 数据库事务管理
【发布时间】:2022-01-24 21:46:58
【问题描述】:

我想要一个基于 Java 的事务管理器,它可以在 Web 环境中处理嵌套的数据库事务(来自不同类的数据库调用可以共享一个事务,并且线程安全)。

我相信这是可能的,因为我记得 C# 有一些大致相当于这个的东西:

namespace ConsoleApp
{
    class Program
    {
        public static readonly String CONNECTION_STRING = "x";

        static void Main(String[] args)
        {
            using (var transaction = new TransactionScope())
            {
                (new Person()).insertRecords();
                (new Hobbies()).insertRelatedRecords(); // Can see Person records
                transaction.Dispose();
            }
            (new Hobbies()).insertRelatedRecords(); // Can't see Person records
        }
    }

    public class Person
    {
        public void insertRecords() {
            using (SqlConnection conn = new SqlConnection(Program.CONNECTION_STRING)) {
                conn.Open();
                // insert records
            }
        }
    }

    public class Hobbies
    {
        public void insertRelatedRecords() {
            using (SqlConnection conn = new SqlConnection(Program.CONNECTION_STRING)) {
                conn.Open();
                // insert records
            }
        }
    }
}

在上面的例子中,我希望说明我练习代码的意图,“Person 和 Hobbies 必须都成功完成事务,否则回滚他们的更改。Hobbies 应该能够看到 Person 插入的数据因为它首先发生在交易中。”重要的是,这些发生在不同的类或方法中,但由父级管理,而无需传递连接或事务对象。

我的应用程序当前在 Tomcat 上运行,并使用 Oracle 池连接(OracleDataSource 类)创建数据库连接。它不使用 Tomcat 的数据库上下文。所有连接都使用一个用户/架构/登录名。

实际上,我只有 Java 库可供使用,因为请求新库需要几个月的时间,而且请求经常被拒绝。我现在确实有 Oracle 和 Tomcat,但将来可能会改变,所以我的解决方案必须是纯 Java 的。没有添加 Spring 或 Hibernate 的计划。

这是应用程序中 Java 类结构的简约版本:

public class Program {

    public static void main(String[] args) 
    {
        try {
            Program program = new Program();
            (program.new Person()).insertRecords();
            (program.new Hobbies()).insertRelatedRecords();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    // Core classes //
    
    public static class DbDataSource 
    {
        private static DataSource dataSource;
        
        public static synchronized DataSource getDataSource() throws SQLException {
            if (dataSource == null) {
                OracleDataSource ods = new OracleDataSource();
                ods.setDriverType("x");
                ods.setURL("x");
                ods.setUser("x");
                ods.setPassword("x");
                dataSource = ods;
            }
            return dataSource;
        }
    }
    
    public class DbResource implements AutoCloseable
    {
        private Connection connection;
        
        public synchronized Connection getConnection() throws SQLException {
            if (connection == null) {
                connection = DbDataSource.getDataSource().getConnection();
            }
            return connection;
        }

        @Override
        public void close() throws Exception {
            if (connection != null) {
                connection.close();
                connection = null;
            }
        }
    }
    
    // Data base classes //
    
    public class DbClass 
    {
        private DbResource db;
        
        protected synchronized DbResource getDatabaseResource() {
            if (db == null) {
                db = new DbResource();
            }
            return db;
        }
    }
    
    // Data classes //
    
    public class Person extends DbClass
    {
        public void insertRecords() throws Exception {
            try (DbResource db = this.getDatabaseResource()) {
                // insert records
            }
        }
    }
    
    public class Hobbies extends DbClass
    {
        public void insertRelatedRecords() throws Exception {
            try (DbResource db = this.getDatabaseResource()) {
                // insert records related to Person
            }
        }
    }

}

我的目标是在不更改 Data 类的情况下实现这一点(重写 Data 基类很好)。这个想法是让代码类似于 C# 中的事务工作方式(但我也欢迎提出更好的解决方案)。类似于以下内容:

public class Program {

    public static void main(String[] args) {
        try {
            try (DbTransaction transaction = new DbTransaction()) {
                Program program = new Program();
                (program.new Person()).insertRecords();
                (program.new Hobbies()).insertRelatedRecords(); // Can see person records
                transaction.rollback();
            }
            (program.new Hobbies()).insertRelatedRecords(); // Can't see person records
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    // ... rest of the class ...
}

我目前的思路包括在每次创建事务对象时存储一个静态的 List 或 Map,并在调用 transaction.commit() 时将它们弹出(或者对象被释放并且 AutoCommit 开启)。但是,这只适用于单线程应用程序(如许多桌面应用程序)。我可以通过使用 Map 将其限制为当前的 Web 会话,但这在会话中仍然不是线程安全的。如果用户在加载时打开了多个页面,这将无法隔离它们。

对于每个范围/代码堆栈都需要唯一事务列表的 Web 应用程序,我不确定如何解决这个问题。我不相信有任何方法可以在当前范围内请求实例化对象(包括父类)。

池连接使这更加复杂,我不能保证任何两个调用共享同一个连接。但是,如果我能找到一个多线程解决方案,我就可以单独担心池连接。

【问题讨论】:

    标签: java c# database multithreading transactions


    【解决方案1】:

    Oracle 客户端已经包含此功能。在您的 Oracle 连接上,运行 .setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED) 方法以允许在事务中进行脏读。然后,禁用自动提交模式以允许您使用setAutoCommit(false) 手动提交或回滚连接上的操作。或者,您也可以使用保存点。

    所有这些都在Oracle's JDBC tutorial中有详细说明。

    【讨论】:

    • 谢谢!是的,这些都是不错的选择。不幸的是,我的数据库(或我的驱动程序?)不支持该隔离级别。保存点确实有效,所以我将尝试这样做。我还有一个更大的问题是如何共享连接变量(忽略池问题)而不将变量传递给每个方法。
    • @Rapps - 你可以声明一个公共静态连接变量。但是,这不适用于任何类型的并行处理,例如在处理多个请求的 Web 服务器上。我实际上建议将连接传递给您的较低级别的函数(或包含它们的类构造函数)。这将使您的代码更加健壮。如果您不熟悉它,请深入了解依赖注入的概念,因为这是一个很好的做法。
    • @Rapps - 另外,我强烈推荐 Spring 或 Spring Boot。这比手动做这些事情要容易得多。
    • 是的,公共静态连接变量是一个有点创意的选项。我会使用 map 所以我可以通过某个键唯一标识每个连接(我在想会话 id + 线程 id)。关于依赖注入,我的继承链中确实有一些与之等效的东西。数据类都从继承的方法中获取连接。依赖注入和 Spring/Spring Boot 都需要编写一半的应用程序,这不是一个真正的选择。该应用程序大约有 15 年的历史,所以它相当大。
    • 就个人而言,我建议将连接从最高层向下传递到每个较低层,而不是使用地图。 map 方法必须担心线程安全访问并处理无法找到连接的情况。由于您已经拥有最高级别的连接,因此只需将其添加到每个较低类的构造函数或方法中。显然你不必这样做,但我想如果你这样做了,你会感谢自己的。
    猜你喜欢
    • 2012-03-08
    • 2021-09-30
    • 1970-01-01
    • 2010-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-08
    • 1970-01-01
    相关资源
    最近更新 更多