You appear to be under the impression that changes to entities loaded in a JPA context get automatically committed unless the entity is detached. This is not the case indeed how it works apparently. However, even if you modify attached entities and flush, or you merge detached entities, a rollback
ensures the changes1 are never visible to other transactions.
It is harmless - and often a good idea for consistency - to have a transaction open when performing read-only operations, so long as you don't keep it open for too long2. If you want to guarantee that no data gets written and you're using JTA, just use setRollbackOnly()
on the SessionContext
to make sure of it. For manual JPA transaction management just make sure you call rollback()
on the EntityTransaction
when you're done, rather than committing.
Personally I would recommend using a new transaction in your "getLob" method and rolling it back at the end of the method. If your DB doesn't support nested transactions (few do) this'll usually result in a new connection being fetched from the pool to execute this work.
If you're using JTA and container managed transactions, try:
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class LobTest {
@PersistenceContext
private EntityManager em;
@Resource
private SessionContext sctx;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public byte[] getLob() {
// Get your LOB content by fetching a new copy of the entity from the DB
// by ID, avoiding the need to split the LOB out. Note that you lose
// tx consistency guarantees between the LOB and the rest of the entity by
// doing this.
// then after loading the LOB:
sctx.setRollbackOnly();
}
}
Alternately, if you don't mind an error reading the LOB aborting any surrounding transaction, use TransactionAttributeType.REQUIRES
instead of REQUIRES_NEW
and don't setRollbackOnly()
. You can't changing anything, so nothing's going to get committed. It'll open a new transaction if one isn't already open, and otherwise join the existing transaction, so you get a consistent read of your LOB. The only downside is that some database errors will abort the whole JTA transaction.
If you're using user managed transactions with a non-JTA environment, just obtain a new EntityManager, get an EntityTransaction, use em.find(...)
to load new copy of the LOB containing entity, etc.
1. OK, so there are a few transaction-exempt object types in most databases, like PostgreSQL SEQUENCE
s and the associated SERIAL
pseudo-type, advisory locks, etc that are affected even by transactions that roll back. A transaction can also "write" to the database in the sense of holding locks on resources that may prevent other operations, too. For actual data, it's safe.
2. Just avoid keeping tx's open for more than a few seconds if you can because long running transactions cause performance issues on some databases and they tie up the connection pooler. Avoid keeping transactions open over "user think time" - time when you're waiting for the user to do something - and they might go to off daydreaming, or to lunch, or on holiday, or to the moon... leaving your poor database and connection pooler waiting for their return.