我们在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();
}
}