3

We're developing a project in JaveEE6, using EJB3 beans and JPA2 annotations.

We have some stateful EJB3 beans that use extended persistence context in order to display the database entities to the front (through some interfaces, removing the need of DTO).

The typical use is something like:

  • all methods are without transaction, as we don't want to immediately commit user modification
  • through a non-transactional method, we're loading the entity, attached to the extended context
  • only a save method is transactional: after checking the user data, the entity is then committed and persisted in database.

With a MySQL database, everything works just fine.

Alas, on Postgres, a problem occurs with @Lob field loaded in non-transactional methods. JDBC seems to forbid Lob access outside transaction, throwing a: org.hibernate.exception.GenericJDBCException: Large Objects may not be used in auto-commit mode


As a stackoverflower pointed, a Lob can be in multiple records, so it needs a transaction to maintain consistency.

Set autocommit to true in persistence.xml doesn't work at all, and also should not be done.

I cannot make the method transactional, as we don't want to commit anything at the end of the call. So, does anyone know how can I simply access the Lob?

A hack solution we imagine would be to move the Lob in another entity, then add a transactional method that read the Lob content so we can use it. Quite dirty I think...

Community
  • 1
  • 1
Xavier Portebois
  • 3,354
  • 6
  • 33
  • 53

3 Answers3

2

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 SEQUENCEs 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.

Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • It's a solution quite like the one we thought about (add a transactional method that read the Lob content so we can use it). I think if no miracle happen we'll go for it, as it's the best we can find. – Xavier Portebois Jul 25 '12 at 08:05
  • @XavierPortebois It's a bit of a pity that PgJDBC can't make this more transparent for you with a way to get the whole LOB, taking care of the transaction management behind the scenes. Maybe you should look into the PgJDBC sources? It's open source after all, and you may well be able to enhance it to fit your needs. – Craig Ringer Jul 25 '12 at 13:53
  • 1
    Wow. While writing this up I discovered that JTA bean managed transactions have no equivalent of `REQUIRES_NEW`. They can't suspend and resume transactions. If you want that functionality you *have* to use container managed transactions. – Craig Ringer Jul 26 '12 at 00:00
  • @XavierPortebois Added explanatory intro to answer after re-reading your question. – Craig Ringer Jul 26 '12 at 09:10
  • @CraigRinger: You say, that loaded entities are _not_ persisted automatically. Either this is wrong or at least misleading: If an entity is in MANAGED state, then JPA will persist that entity during the next commit (or sooner depending on flush mode). Using Hibernate Sessions of course is another thing. – A.H. Jul 26 '12 at 09:22
  • Thanks @CraigRinger. Because of some architecture reasons, we will go for a solution with lobs in separated entities and accessed through a different `entity manager`. As the transaction will be closed as soon as we read the lob content, we don't have to rollback the transaction, and we are sure the "consumer" will only have read access to the content. – Xavier Portebois Jul 26 '12 at 09:44
  • @A.H. Seriously? (writes small test). My apologies. I've clearly never worked with JPA without detaching entities. Thankyou, I don't know how I missed that. Nonetheless, if they roll back at the end it matters little; the changes might get flushed to the DB but will be rolled back. – Craig Ringer Jul 27 '12 at 03:33
0

Try getEntityManager().flush();

This writes to the database, but doesn't commit the current transaction. Assuming your isolation level is "read committed" you won't see the update in other queries until you actually commit the transaction. Keep in mind you'll be holding locks on the rows you've touched...

Jonathan S. Fisher
  • 8,189
  • 6
  • 46
  • 84
0

For some architectural reasons, we chose to set the Lob field in another Entity and read/write it through a @Stateless bean.

The entity:

@Entity
@Access(AccessType.FIELD)
public class LobEntity
{
    [...]

    @Lob
    private String content;

    public String getContent()
    {
        return content;
    }

    public void setContent(String content)
    {
        this.content = content;
    }
}

The service:

@Stateless
@LocalBean
public class LobService 
{
    @PersistenceContext
    private EntityManager em;

    public String readLob(Long lobId)
    {
        LobEntity lobEntity = em.find(LobEntity.class, lobId);
        return lobEntity.getContent();
    }

    public LobEntity writeNewLob(String content)
    {
        LobEntity lob = new LobEntity(content);
        em.persist(lob);
        return lob;
    }
}

And a class that directly contained the lob but no more:

@Entity
@Access(value = AccessType.FIELD)
public class MyEntity
{
    [...]

    protected Long contentLobId;
    @Transient
    protected String editableContent;

    public Long getContentLobId()
    {
        return contentLobId;
    }

    public void setContentLobId(Long contentLobId)
    {
        this.contentLobId = contentLobId;
    }

    public String getEditableContent()
    {
        return editableContent;
    }

    public void setEditableContent(String editableContent)
    {
        this.editableContent = editableContent;
    }
}

The entity doesn't have the LobEntity itself in order to avoir developers to try to access it like fools: if we want the content from a non-transactional context, we're using the LobService. And when we want to save the edited content, we're also using the same bean.

Xavier Portebois
  • 3,354
  • 6
  • 33
  • 53