如何从EJB3扩​​展持久化上下文访问@Lob字段

时间:2012-07-20 15:16:22

标签: postgresql java-ee ejb-3.0 jpa-2.0 blob

我们正在使用 EJB3 bean和 JPA2 注释在 JaveEE6 中开发项目。

我们有一些使用扩展持久化上下文的有状态EJB3 bean,以便在前面显示数据库实体(通过一些接口,无需DTO)。

典型用法如下:

  • 所有方法都没有事务,因为我们不想立即提交用户修改
  • 通过非事务性方法,我们正在加载附加到扩展上下文的实体
  • 只有一个保存方法是事务性的:在检查用户数据之后,该实体将被提交并保存在数据库中。

使用MySQL数据库,一切正常。

唉,在 Postgres 上,在非事务性方法中加载了@Lob字段会出现问题。 JDBC似乎禁止在事务外部进行Lob访问,抛出: 的 org.hibernate.exception.GenericJDBCException: Large Objects may not be used in auto-commit mode


作为stackoverflower pointed,Lob可以在多个记录中,因此需要事务来保持一致性。

autocommit中将persistence.xml设置为true根本不起作用,还should not be done

我无法使方法成为事务性的,因为我们不希望在调用结束时提交任何内容。那么,有谁知道我怎么能简单地访问Lob?

我们想象的黑客解决方案是将Lob移动到另一个实体中,然后添加一个读取Lob内容的事务方法,以便我们可以使用它。我觉得很脏......

3 个答案:

答案 0 :(得分:2)

您似乎认为,除非实体已分离,否则将自动提交对JPA上下文中加载的实体的更改。事实并非如此确实如何起作用。但是,即使您修改附加实体并刷新,或者合并分离的实体,rollback也可确保更改 1 对其他事务不可见。

在执行只读操作时打开事务是无害的 - 并且通常是一个好主意 - 只要你不打开它太长时间 2 。如果您想保证没有数据写入并且您正在使用JTA,请使用setRollbackOnly()上的SessionContext来确保它。对于手动JPA事务管理,只需确保在完成后调用rollback()上的EntityTransaction,而不是提交。

就个人而言,我建议在“getLob”方法中使用新事务,并在方法结束时将其回滚。如果您的数据库不支持嵌套事务(很少),这通常会导致从池中提取新连接以执行此工作。

如果您正在使用JTA和容器管理的事务,请尝试:

@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();
    }

}

或者,如果您不介意读取LOB中止任何周围事务的错误,请使用TransactionAttributeType.REQUIRES而不是REQUIRES_NEW而不是setRollbackOnly()。你无法改变任何东西,所以什么都不会被提交。如果一个尚未打开,它将打开一个新事务,否则加入现有事务,这样您就可以获得对LOB的一致读取。唯一的缺点是一些数据库错误将中止整个JTA事务。

如果您在非JTA环境中使用用户管理的事务,只需获取新的EntityManager,获取EntityTransaction,使用em.find(...)加载包含LOB的实体的新副本等。


1 。好的,所以在大多数数据库中都有一些事务免除对象类型,比如PostgreSQL SEQUENCE和相关的SERIAL伪类型,咨询锁等,即使是回滚的事务也会受到影响。事务也可以“写入”数据库,意思是保持对可能阻止其他操作的资源的锁定。对于实际的数据,它是安全的。

2 。如果可以,请避免将tx保持打开状态超过几秒钟,因为长时间运行的事务会导致某些数据库出现性能问题,并且会使连接更加混乱。避免交易在“用户思考时间”开放 - 当你等待用户做某事时 - 他们可能会做白日梦,午餐,度假或登月......留下你的穷人数据库和连接pooler等待他们返回。

答案 1 :(得分:0)

尝试getEntityManager().flush();

这会写入数据库,但不会提交当前事务。假设您的隔离级别为“已提交读”,则在实际提交事务之前,您不会在其他查询中看到更新。请记住,你会锁定你触摸过的行......

答案 2 :(得分:0)

出于某些架构原因,我们选择在另一个Lob中设置Entity字段,并通过@Stateless bean读取/写入。

实体:

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

    @Lob
    private String content;

    public String getContent()
    {
        return content;
    }

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

服务:

@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;
    }
}

还有一个直接包含高潮但没有更多的课程:

@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;
    }
}

实体没有LobEntity本身,以便让开发人员像傻瓜一样试图访问它:如果我们想要来自非事务性上下文的内容,我们就会使用{{1} }。当我们想要保存已编辑的内容时,我们也使用相同的bean。