我们正在使用 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内容的事务方法,以便我们可以使用它。我觉得很脏......
答案 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。