JPA。从 Hibernate 迁移到 Eclipselink 时出现事务问题
标准 Java EE 环境:从 JSF beans 调用 JPA + EJB。服务器:glassfish 3.1.1
使用 Hibernate 作为 JPA 提供程序开发、测试和部署的代码,拒绝使用 Eclipselink 持久化实体 - glassfish 中的默认 JPA 提供程序:
这是我从 JSF bean 调用的代码:
@Singleton
@Startup
public class SecurityService {
@PersistenceContext(unitName = "unit01")
private EntityManager em;
@TransactionAttribute(value = TransactionAttributeType.REQUIRED)
public String createNewUser(String login) {
UserImpl newUser = new UserImpl();
newUser.setLogin(login);
em.persist(newUser);
//em.flush() ------> works fine when uncommented
DirectoryImpl userHome = new DirectoryImpl();
userHome.setOwner(newUser);
em.persist(userHome); // Throws exception due to FK violation
//
//
正如所评论的,该代码仅有效如果我在保留新用户后刷新 EntityManager,这显然是错误的。在测试其他方法时,我发现只有当我关闭服务器时,其他一些更改才会刷新到数据库。
以上所有内容都发生在全新的 GF 安装中,采用纯 Java EE 设计,没有任何 Spring 或任何框架。
这是我的 persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="unit01" transaction-type="JTA">
<jta-data-source>jdbc/Unit01DS</jta-data-source>
<properties>
<property name="eclipselink.logging.level" value="FINE" />
</properties>
</persistence-unit>
</persistence>
这是我创建数据源的方式:
asadmin create-jdbc-connection-pool --datasourceclassname com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource --restype javax.sql.ConnectionPoolDataSource --property "User=u1:Password=xxx:URL=jdbc\:mysql\://localhost/dbname" Unit01DS
asadmin create-jdbc-resource --connectionpoolid Unit01DS jdbc/Unit01DS
仅此而已,没有使用其他配置文件/选项。那么为什么我的代码在 Hibernate 下运行良好而在 Eclipselink 下表现完全不同呢?有什么想法吗?
更新
进一步调查表明问题出在实体映射中。在我的例子中,提到的两个实体(UserImpl
和 DirectoryImpl
)都是从单个根类继承的,如下所示:
@Entity
@Table(name = "ob10object")
@Inheritance(strategy = InheritanceType.JOINED)
public class RootObjectImpl implements RootObject {
private Long id;
@Id
@Column(name = "ob10id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
//
UserImpl
是根实体并且没有对其他实体的引用
@Entity
@Table(name = "as10user")
@DiscriminatorValue(value = "AS10")
public class UserImpl extends RootObjectImpl implements User {
private String login;
//
//
更复杂的情况是 DirectoryImpl
:
@Entity
@Table(name = "st20directory")
@DiscriminatorValue(value = "ST20")
public class DirectoryImpl extends AbstractStorageNode implements Directory {
// Inside there are also no references to other entities
其中 AbstractStorageNode
也扩展了根对象:
@Entity
@Table(name = "st10storage_node")
public abstract class AbstractStorageNode extends RootObjectImpl implements StorageNode {
private Set<AbstractStorageNode> childNodes;
private StorageNode parentNode;
private User owner;
@OneToMany(mappedBy = "parentNode")
public Set<AbstractStorageNode> getChildNodes() {
return childNodes;
}
@ManyToOne(targetEntity = AbstractStorageNode.class)
@JoinColumn(name="st10parent", nullable = true)
public StorageNode getParentNode() {
return parentNode;
}
@ManyToOne(targetEntity = UserImpl.class)
@JoinColumn(name = "as10id") // this is FK to `UserImpl` table.
public User getOwner() {
return owner;
}
// Setters omitted
现在这是生成的 sql:
// Creating user:
INSERT INTO ob10object (field1, field2, ob10discriminator) VALUES ('v1', 'v2', 'AS10')
SELECT LAST_INSERT_ID()
// Creating Directory:
INSERT INTO ob10object (field1, field2, ob10discriminator) VALUES ('v11', 'v22', 'ST20')
SELECT LAST_INSERT_ID()
INSERT INTO st10storage_node (st10name, as10id, st10parent, ob10id) VALUES ('home', 10, null, 11)
现在我看看会发生什么: eclipselink 不会将数据插入到 User 表(as10user)中,导致最后一个查询中发生 FK 冲突。但我仍然不知道为什么会发生这种情况。
更新2
以下是例外情况:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`dbname`.`st10storage_node`, CONSTRAINT `F_ST10_AS10` FOREIGN KEY (`as10id`) REFERENCES `as10user` (`ob10id`))
Error Code: 1452
Standard Java EE environment: JPA + EJB called from JSF beans. Server: glassfish 3.1.1
Code which is developed, tested and deployed with Hibernate as JPA provider, refuses to persist entities with Eclipselink - default JPA provider in glassfish:
Here is the code I call from JSF bean:
@Singleton
@Startup
public class SecurityService {
@PersistenceContext(unitName = "unit01")
private EntityManager em;
@TransactionAttribute(value = TransactionAttributeType.REQUIRED)
public String createNewUser(String login) {
UserImpl newUser = new UserImpl();
newUser.setLogin(login);
em.persist(newUser);
//em.flush() ------> works fine when uncommented
DirectoryImpl userHome = new DirectoryImpl();
userHome.setOwner(newUser);
em.persist(userHome); // Throws exception due to FK violation
//
//
As commented, the code works only if I flush EntityManager after persisting new user, which is obviously wrong. While testing other methods I've found that some other changes are flushed to DB only when I shutdown the server.
All above happens in a fresh GF installation, in pure Java EE design without any Spring or whatever frameworks.
Here is my persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="unit01" transaction-type="JTA">
<jta-data-source>jdbc/Unit01DS</jta-data-source>
<properties>
<property name="eclipselink.logging.level" value="FINE" />
</properties>
</persistence-unit>
</persistence>
Here is how I create data source:
asadmin create-jdbc-connection-pool --datasourceclassname com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource --restype javax.sql.ConnectionPoolDataSource --property "User=u1:Password=xxx:URL=jdbc\:mysql\://localhost/dbname" Unit01DS
asadmin create-jdbc-resource --connectionpoolid Unit01DS jdbc/Unit01DS
That's all, no other configuration files/options used. So why my code works fine under Hibernate and behaves absolutely differently under Eclipselink? Any ideas?
UPDATE
Further investigation has shown that problem lies somewhere in entity mappings. In my case both mentioned entities (UserImpl
and DirectoryImpl
) are inherited from a single root class as show below:
@Entity
@Table(name = "ob10object")
@Inheritance(strategy = InheritanceType.JOINED)
public class RootObjectImpl implements RootObject {
private Long id;
@Id
@Column(name = "ob10id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
//
UserImpl
is a direct subclass of the root entity and has no references to other entities
@Entity
@Table(name = "as10user")
@DiscriminatorValue(value = "AS10")
public class UserImpl extends RootObjectImpl implements User {
private String login;
//
//
A bit more complicated case is DirectoryImpl
:
@Entity
@Table(name = "st20directory")
@DiscriminatorValue(value = "ST20")
public class DirectoryImpl extends AbstractStorageNode implements Directory {
// Inside there are also no references to other entities
Where AbstractStorageNode
also extends root object:
@Entity
@Table(name = "st10storage_node")
public abstract class AbstractStorageNode extends RootObjectImpl implements StorageNode {
private Set<AbstractStorageNode> childNodes;
private StorageNode parentNode;
private User owner;
@OneToMany(mappedBy = "parentNode")
public Set<AbstractStorageNode> getChildNodes() {
return childNodes;
}
@ManyToOne(targetEntity = AbstractStorageNode.class)
@JoinColumn(name="st10parent", nullable = true)
public StorageNode getParentNode() {
return parentNode;
}
@ManyToOne(targetEntity = UserImpl.class)
@JoinColumn(name = "as10id") // this is FK to `UserImpl` table.
public User getOwner() {
return owner;
}
// Setters omitted
Now here is the generated sql:
// Creating user:
INSERT INTO ob10object (field1, field2, ob10discriminator) VALUES ('v1', 'v2', 'AS10')
SELECT LAST_INSERT_ID()
// Creating Directory:
INSERT INTO ob10object (field1, field2, ob10discriminator) VALUES ('v11', 'v22', 'ST20')
SELECT LAST_INSERT_ID()
INSERT INTO st10storage_node (st10name, as10id, st10parent, ob10id) VALUES ('home', 10, null, 11)
Now I see what happens: eclipselink does not insert data into User table (as10user), causing FK violation in the last query. But still I have no idea why is that happening.
UPDATE 2
Here is the exception:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`dbname`.`st10storage_node`, CONSTRAINT `F_ST10_AS10` FOREIGN KEY (`as10id`) REFERENCES `as10user` (`ob10id`))
Error Code: 1452
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
![扫码二维码加入Web技术交流群](/public/img/jiaqun_03.jpg)
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
那么,如果没有刷新,你会得到约束错误吗?请包括异常和堆栈跟踪以及 SQL 日志,还包括如何映射关系以及如何定义 id。
你们有双向关系吗? EclipseLink 可能正在尝试解决导致错误的双向关系,您可能需要删除非空约束以允许插入到表中,或者使用定制器定义约束依赖项,或者修复双向关系映射。
更新
发生错误的原因是您为子 User 表定义了约束,而不是为定义 Id 的父 Root 表定义了约束。默认情况下,EclipseLink 会延迟写入没有引用的辅助表,以优化事务并避免数据库死锁。
从技术上讲,EclipseLink 应该找到对 User 的引用,而不是这样做,您使用的是最新版本吗?它可能已被修复,否则记录一个错误并为其投票。
您可以通过使用 DescriptorCustomizer 并在根描述符上设置属性 setHasMultipleTableConstraintDependecy(true) 来解决该问题。
另外,您似乎让所有类共享同一个根表,该根表似乎只定义了 id,这可能不是一个好主意。您可能最好将 Root 设置为 @MappedSuperclass 并且仅具有具体子类的表。
So, without the flush you get a constraint error? Please include the exception and stack trace and SQL log, also include how you are mapping the relationship and how you are defining the ids.
Do you have a bi-directional relationship? EclipseLink may be trying to resolve a bi-directional relationship causing your error, you may need to remove a not-null constraint to allow insertion into your table, or define the constraint dependency using a customizer, or fix your bidirectional relationship mapping.
Update
The error is occurring because you have the constraint defined to the child User table not to the parent Root table where the Id is defined. By default EclipseLink deffers the write into secondary tables that have no references to optimize the transaction and avoid database deadlocks.
Technically EclipseLink should be finding the reference to User and not doing this, are you on the latest release, it may have been fixed, otherwise log a bug and vote for it.
You can resolve the issue by using a DescriptorCustomizer and setting the property setHasMultipleTableConstraintDependecy(true) on the Root descriptor.
Also, you seem to have all of your classes sharing the same Root table that seems to only define the id, this is probably not a good idea. You might be better off making Root a @MappedSuperclass and only have table for the concrete subclasses.
我想您会发现这两种方法实际上都遵循 JPA 2 规范。 http://download.oracle.com/auth/otn-pub/jcp/persistence-2.0-fr-eval-oth-JSpec/persistence-2_0-final-spec.pdf?e=1318430009& h=d3d726bc7bec224bd8f803aba90d1f13
参见第 3.2.2 节
进入数据库的实际时间是松散定义的。数据库提供外键的 ID,因此需要了解数据。
调用flush可能会让人感觉很糟糕,但它在Hibernate中也应该可以工作。它将数据写入数据库,但不会为 EntityManager 删除数据,因此实体仍保持附加状态。
希望这有帮助。
I think you will find that both approaches actually adhere to the JPA 2 spec. http://download.oracle.com/auth/otn-pub/jcp/persistence-2.0-fr-eval-oth-JSpec/persistence-2_0-final-spec.pdf?e=1318430009&h=d3d726bc7bec224bd8f803aba90d1f13
See Section 3.2.2
The actual time of the entry into the database is loosely defined. The DB provides the ID for the foreign key so needs to know about the data.
Calling flush may feel bad but it should work in Hibernate too. It writes the data to the DB but does not remove it for the EntityManager so the entity remains attached.
Hope this helps.
我想需要更多细节(即发布您的实体代码),因为在我的情况下运行非常相似的代码没有任何问题。只要看一下,如果发现任何差异就给一个标志。
这是带有 EclipseLink 的 Glassfish 3.1.1。
EJB 代码:
实体编号 1:
实体编号 2:
persistence.xml
HTH。
I guess that more details will be needed (i.e. post your Entities code), as quite similar code runs in my case without any problem. Just take a look and give a sign if you spot any differences.
It's Glassfish 3.1.1 with EclipseLink.
EJB Code:
Entity nr 1:
Entity nr 2:
persistence.xml
HTH.
我认为问题可能出在您的实体上。例如,我的用户实体是这样的:
注意 @Id 和 @Basic 注释。我没有看到您在 UserImpl 的属性中使用任何注释。
我建议您修改实体注释。
I think the problem is probably at your entities. For example, my user entity is something like this:
Notice the @Id and @Basic annotations. I don't see you using any annotation at UserImpl's attributes.
I suggest you revise your entitys annotations.