Phantom read

Phantom read happens when data is read more times and unexpectedly goes away or when data is read more times and unexpectedly a different value is shown. For example, in transaction A, through a query you get a set result. Before transaction A is completed, transaction B adds a row that matches the query executed in transaction A, and this row is committed before the completion of transaction A. If transaction A executes the same query again, it will get a different data. This situation is called phantom read because new data not created by transaction A has appeared in the resource. To simulate this read, we must execute a query. Consider the named query of the account entity:

@Entity
@NamedQueries({ @NamedQuery(name = "SelectAll", query = "SELECT e FROM Account e") })
public class Account {...}

Here is a runnable to execute the query in the first transaction:

@Transactional(value = REQUIRES_NEW)
public class QueryReadAccount implements Runnable {
@Override
public void run() {
...
CountDownLatch latch = new CountDownLatch(1);
latch.await(firstWaitTime, MILLISECONDS);
firstResult = entityManager.createNamedQuery("SelectAll").getResultList().size(); latch.await(secondWaitTime, MILLISECONDS);
secondResult = entityManager.createNamedQuery("SelectAll").getResultList().size();
...
}
}

Here is a runnable that inserts a new account in the second transaction:

@Transactional(value = REQUIRES_NEW)
public class QueryWriteAccount implements Runnable {
@Override
public void run() {
...
CountDownLatch latch = new CountDownLatch(1);
Account account = new Account();
account.setNumber(accountNumber);
account.add(amount);
entityManager.persist(account);
latch.await(waitTime, MILLISECONDS);
result = account.getCredit();
...
}
}

Now, start the two transactions with the managed executor service:

@Inject
private QueryReadAccount queryReadAccount;
@Inject
private QueryWriteAccount queryWriteAccount;
...
queryReadAccount.setFirstWaitTime(0);
queryReadAccount.setSecondWaitTime(2000);
queryWriteAccount.setAccountNumber(953);
queryWriteAccount.setAmount(456.77);
queryWriteAccount.setWaitTime(1000);
defaultExecutor.submit(queryWriteAccount);
defaultExecutor.submit(queryReadAccount);

The operations can be simplified so:

  • The first transaction starts and we have a first query of all accounts
  • The second transaction starts and an insert of a new account is done
  • The second transaction ends
  • A second read of all accounts is done in the first transaction
  • The first transaction ends

The results of these operations change according the configured transaction isolation level that we will introduce in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.190.102