【问题标题】:How access a @Lob field from an EJB3 extended persistence context如何从 EJB3 扩展持久性上下文访问 @Lob 字段
【发布时间】:2012-07-19 21:29:28
【问题描述】:

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

我们有一些有状态的 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可以在多条记录中,所以需要一个事务来保持一致性。

persistence.xml 中将autocommit 设置为true 根本不起作用,should not be done 也是如此。

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

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

【问题讨论】:

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


    【解决方案1】:

    试试getEntityManager().flush();

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

    【讨论】:

      【解决方案2】:

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

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

      我个人建议在您的“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 SEQUENCEs 和关联的 SERIAL 伪类型、咨询锁等,即使受到回滚事务的影响。事务也可以“写入”到数据库,因为它持有资源锁,这也可能阻止其他操作。对于实际的数据,它是安全的。

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

      【讨论】:

      • 这是一个和我们想的非常相似的解决方案(添加一个读取 Lob 内容的事务方法,以便我们可以使用它)。我想如果没有奇迹发生,我们会努力的,因为这是我们能找到的最好的。
      • @XavierPortebois 有点遗憾的是,PgJDBC 无法通过获取整个 LOB 的方式让您更透明,并负责幕后的事务管理。也许您应该查看 PgJDBC 源代码?毕竟它是开源的,您可以根据自己的需要对其进行增强。
      • 哇。在写这篇文章时,我发现 JTA bean 管理的事务没有 REQUIRES_NEW 的等价物。他们不能暂停和恢复交易。如果您想要该功能,您必须使用容器管理的事务。
      • @XavierPortebois 添加了解释性介绍以在重新阅读您的问题后回答。
      • @CraigRinger:你说加载的实体不会自动持久化。这要么是错误的,要么至少是误导性的:如果实体处于 MANAGED 状态,则 JPA 将在下一次提交期间保留该实体(或更快,取决于刷新模式)。当然,使用 Hibernate Sessions 是另一回事。
      【解决方案3】:

      出于某些架构原因,我们选择在另一个Entity 中设置Lob 字段并通过@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;
          }
      }
      

      还有一个直接包含 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。

      【讨论】:

        猜你喜欢
        • 2010-09-07
        • 1970-01-01
        • 2016-05-02
        • 2016-08-11
        • 2012-02-23
        • 1970-01-01
        • 2017-02-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多