3

我们正在使用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 所指出的,一个 Lob 可以在多个记录中,因此它需要一个事务来保持一致性。

设置autocommit为 true inpersistence.xml根本不起作用,也不应该做

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

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

4

3 回答 3

2

您似乎认为对 JPA 上下文中加载的实体所做的更改会自动提交,除非该实体已分离。事实并非如此,它显然是如何工作的。但是,即使您修改附加的实体并刷新,或者合并分离的实体,a也rollback可以确保更改1对其他事务永远不可见。

在执行只读操作时打开事务是无害的,而且通常是保持一致性的好主意,只要您不要将其打开太久2。如果你想保证没有数据被写入并且你正在使用 JTA,只需使用setRollbackOnly()onSessionContext来确保它。对于手动 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 . 好的,所以在大多数数据库中都有一些事务豁免对象类型,例如 PostgreSQLSEQUENCE和相关的SERIAL伪类型、咨询锁等,即使受到回滚事务的影响。事务也可以“写入”到数据库,因为它持有资源锁,这也可能阻止其他操作。对于实际数据,它是安全的。

2 . 如果可以的话,请避免让 tx 保持打开状态超过几秒钟,因为长时间运行的事务会导致某些数据库出现性能问题,并且会占用连接池。避免在“用户思考时间”(您正在等待用户做某事的时间)保持交易开放,他们可能会做白日梦,或者去吃午饭,或者去度假,或者去月球......离开你的穷人数据库和连接池等待它们的返回。

于 2012-07-25T00:41:53.000 回答
0

尝试getEntityManager().flush();

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

于 2012-07-24T19:29:08.687 回答
0

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

实体:

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

还有一个直接包含 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自身,以防止开发人员像傻瓜一样尝试访问它:如果我们想要来自非事务上下文的内容,我们正在使用LobService. 而当我们想要保存编辑过的内容时,我们也使用了同一个 bean。

于 2012-07-27T09:40:50.393 回答