EntityManager.merge() does not create table record
The following code fails to create a new record reliably.
TransferRecord transfer = new TransferRecord();
transfer.setTransferId(metaData.getTransferId());
transfer.setUserName(metaData.getUserId().getUserName());
transfer.setCancelled(false);
em.merge(transfer);
em.flush();
Here's the code for the transfer record:
/***
* Contains a record of an ongoing transfer.
* @author anchapma
*
*/
@Entity
@Table(name = "TransferRecord")
public class TransferRecord implements Serializable
{
/**
* Generated static unique ID.
*/
private static final long serialVersionUID = -8131586518447878482L;
/***
* The id of the transfer.
*/
private String transferId;
/***
* The name of the user who ownes the transfer.
*/
private String username;
/***
* Has this transfer been cancelled?
*/
private boolean cancelled;
/***
* Constructor.
*/
public TransferRecord()
{
this.transferId = "";
this.username = "";
this.cancelled = false;
}
/***
* Gets the transfer ID.
*
* @return The transfer ID.
*/
@Id
@Column(name = "TransferID", unique = true, nullable = false, length = 50)
public String getTransferId()
{
return this.transferId;
}
/***
* Sets the transfer ID.
* @param transferId The new transfer ID.
*/
public void setTransferId(String transferId)
{
this.transferId = transferId;
}
/***
* Gets the username.
*
* @return The username.
*/
@Column(name = "UserName", nullable = false, length = 50)
public String getUserName()
{
return this.username;
}
/***
* Sets the username.
* @param username The new username.
*/
public void setUserName(String username)
{
this.username = username;
}
/***
* Gets whether or not the transfer has been cancelled.
*
* @return True if the transfer has been cancelled.
*/
@Column(name = "Cancelled", null开发者_如何学JAVAable = false, length = 50)
public boolean getCancelled()
{
return this.cancelled;
}
/***
* Sets whether or not the transfer has been cancelled.
* @param cancelled True if the transfer has been cancelled.
*/
public void setCancelled(boolean cancelled)
{
this.cancelled = cancelled;
}
}
I think that what is happening is the record gets added to the database after a delay. My code uses the existence or absence of a TransferRecord record as a flag. It therefore needs the data to show up in the table immediately.
Am I likely to be right in my assumption? If so, is there a way to force the em.flush()
call to wait until it has written out the record before returning?
The accompanying comment to the question states -
"The calling bean is stateless and executes for many seconds. The data doesn't show up to other database users until the bean's operation ends."
This described behavior has nothing to do with flushing the persistence context associated with the EntityManager. It has a lot to do with the transaction isolation level associated with the transaction. The transaction associated with the stateless session bean (SLSB) actually commits data to the database, only on returning from the bean method, as each SLSB method might be associated with the default transaction attribute of REQUIRED
(which uses an existing transaction or starts a new one); the resulting behavior is that the transaction is terminated via a rollback in the SLSB method or via a commit on return from the method.
This behavior affects reads performed by other clients and transactions since the resulting outcome depends on the isolation level of the current pertinent transaction, which in turn depends on the isolation level specified on the database connection pool. For most databases and associated connection pools, the transaction isolation level happens to be READ COMMITTED
, and therefore other transactions can read only data committed by the pertinent transaction (i.e. on SLSB method return). This is desirable behavior in most circumstances, for you do not wish to read data that is uncommitted (and may later be rolled back) resulting in the possibility of dirty reads.
If you do intend to perform dirty reads, you must configure the JDBC connection pool to allow dirty reads, or in other words, set the transaction isolation level of the pool to READ UNCOMMITTED
. Configuration changes would vary from container to container, and would also be subject to support of the READ UNCOMMITTED
isolation level by the database; Oracle for instance, does not allow you to set the isolation level to READ UNCOMMITTED
, and may set it to the next higher isolation level supported by it (READ COMMITTED
).
If you want to avoid mucking around with transaction isolation levels, consider splitting the method calls across several transactions. You can achieve this by creating a new business method in separate SLSB that requires a new transaction to be created on every invocation (REQUIRES_NEW
). The Pseudo-code example is as outlined below:
@Stateless
@Local(X.class)
public class SLSBX implements X {
@EJB Y y;
@TransactionAttribute(TransactionAttributeType.REQUIRED) // start a new transaction to do work
public void sampleMethod() {
// suspends the existing transaction on invoking Y.anotherSampleMethod()
Y.anotherSampleMethod();
// continue doing work in the current transaction. Work done in anotherSampleMethod() would have either committed or rolled back.
}
}
@Stateless
@Local(Y.class)
public class SLSBY implements Y {
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) // Suspends the existing transaction and creates a new transaction that terminates on method return
public void anotherSampleMethod() {
// do some stuff
}
}
This approach is of course, recommended only if the business nature of the transaction allows for adoption of such an approach. Typically, one must scope all business activities that are part of a business transaction, into an actual transaction.
精彩评论