Transaction is not completely rolled back after se

2019-07-22 02:56发布

Let's say I have an entity bean AccountBean with version field (javax.persistence.Version annotation). During transaction my application modifies this entity and performs database operations on other entities (inserts and updates rows). Some of those entity beans have @Version field but not all of them.

When the same AccountBean entity is modified concurrently by 2 threads, OptimistickLockException is thrown and (at least according to server log) the transaction is rolled back. However, only changes made to the conflicted AccountBean entity are actually rolled back - everything else is committed to database.

**EDIT: ** I added simple source code to illutrate the issue; The application is a REST web service; Two test threads call concurrently operation "update" with the same account id. Once again the OLE is thrown and yet the supposedly rolled back transaction commits to data base new AccountHistory entity :/ Since transactions are managed by container the transaction is started when method update is called and commited when it retursn value; That's also when OLE is thrown.

//UpdateAccount.java
@Stateless
@Path("account")
public class UpdateAccount {

    @PersistenceContext
    EntityManager em;

    @Path("update")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public String update(Long accountId) {
        Account account = em.find(Account.class, accountId);
        if(null == account) {
            return "account not found";
        } else {
            return executeUpdate(account);
        }
    }

    String executeUpdate(Account account) {
        Integer newValue = account.getValue() + 1;

        em.persist(getAccountHistory(account, newValue));
        account.setValue(newValue);

        return "ok";
    }

    AccountHistory getAccountHistory(Account account, Integer newValue) {
        AccountHistory history = new AccountHistory();
        history.setId(new Date().getTime());
        history.setAccount(account);
        history.setValueBefore(account.getValue());
        history.setValueAfter(newValue);

        return history;
    }
}

//Account.java
@Entity
public class Account {

    @Id
    private Long id;

    @Column
    private Integer value;

    @Version
    private Long version;

    (...)//getters, setters etc
}

//AccountHistory.java
@Entity
public class AccountHistory {

    @Id
    private Long id;

    @Column
    private Integer valueBefore;

    @Column
    private Integer valueAfter;

    @ManyToOne
    @JoinColumn(name = "idaccount")
    private Account account;

    (...)//getters, setters etc
}
  1. Am I wrong to expect all the changes rolled back?
  2. Can I manually force complete roll back? I tried manually managing the transaction, catching OLE and than calling rollback (as described on Adam Bien's blog). However, when I catch the exception the transaction is already marked as rolled back.

I deploy my application on jboss-eap-6.1/jboss-as-7.1.1Final with JRE 1.7, and use Hibernate (version defaults for those servers). My persistence.xml file is as simple as it gets. I haven't set any extra properties.

1条回答
贼婆χ
2楼-- · 2019-07-22 03:44

When the transaction roll backs, all the changes made within that transaction will be roll backed as well.

For local transaction this is handled by the JDBC connection, and for global ones by rolling back all enlisted JDBC transactions.

So, Hibernate doesn't control what gets roll backed or not. It's the underlying transaction manager that does the trick.

The only case when some changes are committed and some are roll-backed is when your service code uses multiple transactions (e.g nested transactions, REQUIRES_NEW) or when there are two consecutive transactional services being called from a non-transactional one. So if the second rolls back the first is committed anyway, because the first service is not transactional, hence any successive service call is enlisted in it's own transaction.

查看更多
登录 后发表回答