Jakarta ee 在数据库中原子读写字段的最佳实践

Jakarta ee 在数据库中原子读写字段的最佳实践,jakarta-ee,Jakarta Ee,我来自Java桌面应用程序背景。我想知道J2EE中的最佳实践是什么,在数据库中读写一个字段。现在,以下是我所做的 // In Servlet. synchronized(private_static_final_object) { int counter = read_counter_from_database(); counter = some_calculation_that_shall_be_done_outside_database(counter); write

我来自Java桌面应用程序背景。我想知道J2EE中的最佳实践是什么,在数据库中读写一个字段。现在,以下是我所做的

// In Servlet.
synchronized(private_static_final_object)
{
    int counter = read_counter_from_database();
    counter = some_calculation_that_shall_be_done_outside_database(counter);
    write_counter_back_to_database(counter);
}
然而,我怀疑上述方法将一直有效

根据我的观察,如果我同时有多个web请求,我将使用不同的线程在servlet的单个实例中执行代码。当不同的线程web请求都引用相同的“私有\静态\最终\对象”时,上述方法应能正常工作

然而,我的猜测是“servlet的单个实例”并不能保证。与一段时间之后一样,servlet的前一个实例可能会被破坏,并创建另一个新的servlet实例

我在JDO也遇到过。我不确定他们是否能解决这个问题

// In Servlet.
Transaction tx = pm.currentTransaction();
tx.begin();
    int counter = read_counter_from_database();  // Line 1
    counter = some_calculation_that_shall_be_done_outside_database(counter);// Line 2                 
    write_counter_back_to_database(counter);     // Line 3
tx.commit();
只有当线程A以原子方式执行第1行到第3行时,代码才能保证,只有线程B才能继续以原子方式执行第1行到第3行吗

因为我不希望发生以下情况

  • 将数据库中的读取计数器作为0执行线程
  • 线程A在计数器0上执行计算
  • 线程B从数据库中读取计数器为0
  • 线程将计数器0的计算结果(假设结果为42)写入数据库
  • 线程B对计数器0执行计算
  • 线程B将计数器0的计算结果(假设结果为42)写入数据库
  • 我的愿望是

  • 将数据库中的读取计数器作为0执行线程
  • 线程A在计数器0上执行计算
  • 线程将计数器0的计算结果(假设结果为42)写入数据库
  • 线程B从数据库读取计数器as 42
  • 线程B在计数器42上执行计算
  • 线程B将计数器42的计算结果(假设结果为55)写入数据库

  • 谢谢。

    为什么不把你的两个想法简单地结合起来呢

    synchronized(private_static_final_object)
    {
        Transaction tx = pm.currentTransaction();
        tx.begin();
            int counter = read_counter_from_database();  
            counter++;                                   
            write_counter_back_to_database(counter);    
        tx.commit();
    }
    
    您可以通过代码中的关键部分和db事务中的原子性实现线程同步

    单独使用synchronized关键字不会“保护”db事务原子性

    另一个选项:

    对于您来说,最安全的方法是在db引擎中的存储过程中的事务中执行此操作,尽管根据rexem的观察,它仍然不完美。拉取数据,增加数据量,然后将其保存回去,会导致错误数据的可能性过大。

    这:

    Transaction tx = pm.currentTransaction();
    tx.begin();
    
    int counter = read_counter_from_database();  // Line 1
    counter++;                                   // Line 2
    write_counter_back_to_database(counter);     // Line 3
    
    tx.commit();
    
    …不安全

    数据库采用所谓的隔离级别,在这种级别上,可以在提交插入/更新时读取数据。它使读取速度更快,但有可能获取过时的数据。当您的计数器变量递增时,我可以提交我的insert-您最多可能会遇到主键或唯一键验证错误,最坏可能是坏数据


    我的建议是让相应的数据库实用程序处理这些情况,因为它们是安全的。对于甲骨文来说,这是一个序列。sqlserver称之为IDENTITY;MySQL称之为autoincrement…

    不确定这是否适用于您的用例,但以下情况在数据库级别都是可能的:

    1. begin transaction
    2. update counter = counter+1
    3. read value
    4. commit
    
    当一个线程执行2时,其他线程将在2之前阻塞,直到第一个线程执行提交

    编辑:如果无法在数据库上进行更新,则必须使用select for update语句样式来锁定其他语句的执行:

    1. begin transaction
    2. select counter for update
    3. calculate new counter value in application layer
    4. update counter on database
    5. commit
    

    现在,当另一个线程位于2和5之间时,线程将始终在步骤2阻塞。整个操作将是原子的。MySQL InnoDB的更新语法选择可以在以下位置找到:

    这很有意义,但我不确定Yan所追求的是什么身份。缺少详细信息,但重点仍然是列出的设置本身存在缺陷。计数器++只是一个例子。然而,大多数情况下,计算不能由数据库本身完成。请参考我精炼的问题。