Hibernate + Spring + MySQL:在一个事务中完成的更改有时在下一个事务中不可见

时间:2018-06-15 04:36:11

标签: java mysql spring hibernate spring-transactions

我们在Tomcat服务器上运行基于REST的Web应用程序,该服务器使用Hibernate + Spring并连接到在GCP中运行的MySQL DB。最近我们设置了这些服务器的集群,前面有一个负载平衡器,以支持增加的负载。在此之后,我们发现在某些情况下,即使第二个事务仅在第一个事务完成后才开始,一个事务中的更改也不会反映在同一服务器上的下一个事务中。

详细说明: 在时间t1,在服务器S1上调用REST方法来为帐户充值。这只是调用服务层中的方法。服务层和DAO层类如下所示。从日志中我可以看到更新前帐户余额为X,更新后更改为Y. 此REST调用完成后,还有另一个调用来获取帐户数据。但是此方法中的日志显示旧的余额X.此REST调用到达同一服务器S1。我的代码有什么问题?为什么在第一个REST调用中完成的更改在第二个REST调用中不可见?

P.S:如果重要,我们在MySQL DB中使用REPEATABLE_READ的默认事务隔离级别。

@Service
public class AccountManager
{
    public Account getAccount(long accountId) throws AccountException 
    {
        return accountDAO.getAccount(accountId);
    }

    @Transactional(propagation=Propagation.REQUIRED,rollbackFor={Exception.class})
    public double rechargeAccount(long accountId, int amount, String orderId, String source) throws AccountException
    {   
        accountDAO.lockAccount(accountId);
        ...
        ... // some validations

        AccountTransaction rechargeTransaction = new AccountTransaction(...);               
        double newBalance = makeTransactionOnAccount(rechargeTransaction);
        return newBalance;
    }

    private double makeTransactionOnAccount(AccountTransaction accountTransaction) throws AccountException
    {
        ...
        newBalance= depositAmount(accountTransaction);
        accountDAO.createAccountTransaction(accountTransaction);
        return newBalance;
    }

    private double depositAmount(AccountTransaction accountTransaction)  
    {
        double balance = Math.floor((accountTransaction.getValue())* 100) / 100;
        accountDAO.increaseBalance(accountTransaction.getAccountId(), balance); 
        return accountDAO.getBalance(accountTransaction.getAccountId());
    }
}

@Component
public class AccountDAOImpl implements AccountDAO
{
    @Override
    public void lockAccount (long accountId) throws AccountException
    {
        log.debug("Locking account {} for update", accountId);
        // Pessimistic locking
        Session session = sessionFactry.getCurrentSession();
        session.load(Account.class, accountId, LockOptions.UPGRADE);
    }

    @Override
    public void increaseBalance(long accountId, double valueToBeIncreased) throws AccountException
    {
        try
        {
            Session currentSession = sessionFactry.getCurrentSession();
            Account account = getAccount(accountId);
            account.setBalance(account.getBalance() + valueToBeIncreased);
            currentSession.saveOrUpdate(account);
            currentSession.flush();
            String updateAccountBalanceSQL = getUpdateAccountBalanceSQL(accountId, account.getBalance());
            imptTxnLogger.info("{}", updateAccountBalanceSQL);
        }
        catch (HibernateException he)
        {
            log.error("Increasing balance failed",he);
            throw new AccountException(AccountException.ACCOUNT_UPDATE_FAILED, he.getCause());
        }
    }

    @Override
    public Account getAccount(long accountid)  
    {
        try
        {
            Account account = (Account)sessionFactry.getCurrentSession().get(Account.class, accountid);
            log.debug("Account = {}", account);
            return account; 
        } 
        catch (HibernateException he)
        {
            log.error("getting  account details failed",he);
            throw new AccountException(AccountException.ACCOUNT_READ_FAILED,he.getCause());
        }
    }

    @Override
    public double getBalance(long accountId) 
    {
        Account account = getAccount(accountId);
        return account.getBalance();
    }
}

0 个答案:

没有答案