更新一堆行是基于事务还是基于行?

时间:2015-09-29 19:52:28

标签: mysql jpa concurrency eclipselink jpql

我有一个需要不断重新计算的列的表,我希望这个表可以扩展。用户也必须能够在其上书写。

如果没有服务器和并发用户,很难测试这类事情,至少我不知道如何做。 这两个选项中有一个可行吗?

@ApplicationScoped
public class Abean {
   @EJB
   private MyService myService;
   @Asynchronous
   public void computeTheData(){
      long i = 1;
      long numberOfRows = myService.getCountRows(); // gives the number of row in the table
      while(i<numberOfRows){
        myService.updateMyRow(i);
      }
      computeTheData(); // recursion so it never stops, I'm wondering if this wouldn't spawn more threads and if it would be an issue.
   }
}

public class MyService implements MyServiceInterface{
    ...
    public void updateMyRows(int row){
       Query query = em.createQuery("SELECT m FROM MyEntity WHERE m.id=:id");
       Query.setParameter("id", row);
       List<MyEntity> myEntities = (MyEntity) query.getResultList();
       myEntity.computeData();
    }
}

VS

@ApplicationScoped
public class Abean {
   @EJB
   private MyService myService;
   @Asynchronous
   public void computeTheData(){
      myService.updateAllRows();
   }
}

public class MyService implements MyServiceInterface{
        ...
    public void updateAllRows(int page){
       Query query = em.createQuery("SELECT m FROM MyEntity");
       List<MyEntity> myEntities = (MyEntity) query.getResultList();
       myEntity.computeData();
    }
}

这是否可行?我使用的是mysql,表的引擎是innoDB。

1 个答案:

答案 0 :(得分:1)

您应该在更新之前使用悲观锁定来锁定已修改的行,以便用户手动修改不会与后台更新冲突。如果您没有使用锁定,则有时会回滚用户的修改,如果它们与修改了同一行的后台作业发生冲突。

此外,如果使用悲观锁定,如果用户的事务等待获取锁定的时间超过超时,则用户可能会遇到回滚。为防止这种情况,您应该尽可能缩短使用悲观锁的所有事务。因此,后台作业应为每行或一小组行创建一个新事务,如果它可能运行的时间超过合理时间。只有在事务完成后才会释放锁(用户将等到锁被释放)。

MyService的样子示例,在单独的事务中运行每个更新(实际上,您可以在单个事务中批量运行多个更新,将list的列表或范围作为参数传递给updateMyRows):

public class MyService implements MyServiceInterface{
        ...
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) // this will create a new transaction when running this method from another bean, e.g. from Abean
    public void updateMyRows(int row){
       TypedQuery<MyEntity> query = em.createQuery(SELECT m FROM MyEntity WHERE m.id=:id", MyEntity.class);
       query.setParameter("id", row);
       query.setLockMode(LockModeType.PESSIMISTIC_WRITE); // this will lock all entities retrieved by the query
       List<MyEntity> myEntities = query.getResultList();
       if (!myEntities.isEmpty()) {
         myEntities.get(0).computeData();
       }
    }
}

当你在where where条件中只使用id时,你可以考虑em.find(row, MyEntity.class, LockModeType.PESSIMISTIC_WRITE).computeData()而不是使用查询(在em.find()之后添加空指针检查)

其他说明:

从问题中如何触发后台作业尚不清楚。正如您在示例中所写的那样,无限地运行作业一方面不会创建额外的线程(因为您在同一个bean上调用了methond,注释不会被递归地考虑)。另一方面,如果存在异常,则后台作业应该至少处理异常,以便不会停止。您可能还希望在后续执行之间添加一些等待时间。

最好将后台作业作为计划作业运行。一种可能的选项是@Schedule注释,而不是@Asynchronous。您可以指定作业将在后台执行的频率。然后,在作业开始时检查以前的执行是否结束是很好的。 Java EE 7的另一个选择是使用ManagedScheduledExecutorService以指定的时间间隔定期触发后台作业。