JPA。从 Hibernate 迁移到 Eclipselink 时出现事务问题

发布于 2024-12-09 04:12:11 字数 4606 浏览 1 评论 0原文

标准 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 下表现完全不同呢?有什么想法吗?

更新

进一步调查表明问题出在实体映射中。在我的例子中,提到的两个实体(UserImplDirectoryImpl)都是从单个根类继承的,如下所示:

@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技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

半边脸i 2024-12-16 04:12:11

那么,如果没有刷新,你会得到约束错误吗?请包括异常和堆栈跟踪以及 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.

一梦浮鱼 2024-12-16 04:12:11

我想您会发现这两种方法实际上都遵循 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 节

3.2.2 持久化实体实例

通过调用其上的 persist 方法或通过
级联持久化操作。持久化的语义
应用于实体X的操作如下:

• 如果 X 是新实体,则它会成为托管实体。实体X将在事务提交时或之前输入数据库,或者作为刷新操作的结果......

进入数据库的实际时间是松散定义的。数据库提供外键的 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

3.2.2 Persisting an Entity Instance

A new entity instance becomes both managed and persistent by invoking the persist method on it or by
cascading the persist operation. The semantics of the persist
operation, applied to an entity X are as follows:

• If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation......

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.

深海不蓝 2024-12-16 04:12:11

我想需要更多细节(即发布您的实体代码),因为在我的情况下运行非常相似的代码没有任何问题。只要看一下,如果发现任何差异就给一个标志。

这是带有 EclipseLink 的 Glassfish 3.1.1。

EJB 代码:

@Stateless
public class MyEJB {

    @PersistenceContext
    EntityManager em;

    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    public void doSmth() {
        UserData ud = new UserData();
        em.persist(ud);

        Website w = new Website();
        w.setUser(ud);

        em.persist(w);

        System.out.println("!!!!!" + w);
    }   
}

实体编号 1:

@Entity
public class UserData implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    public Long getId() {
        return id;
    }

    @Override
    public String toString() {
        return "com.entities.UserData[ id=" + id + " ]";
    }
}

实体编号 2:

@Entity
public class Website implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(optional=false)
    private UserData user;

    public Long getId() {
        return id;
    }

    public void setUser(UserData u){
        user = u;
    }

    public UserData getUser() {
        return user;
    }

    @Override
    public String toString() {
        return "com.entities.Website[ id=" + id + ", " + user +" ]";
    }
}

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
   <persistence-unit name="TransactionsPU" transaction-type="JTA">
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
         <jta-data-source>jdbc/__default</jta-data-source>
         <exclude-unlisted-classes>false</exclude-unlisted-classes>
         <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
         </properties>
   </persistence-unit>
</persistence>

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:

@Stateless
public class MyEJB {

    @PersistenceContext
    EntityManager em;

    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    public void doSmth() {
        UserData ud = new UserData();
        em.persist(ud);

        Website w = new Website();
        w.setUser(ud);

        em.persist(w);

        System.out.println("!!!!!" + w);
    }   
}

Entity nr 1:

@Entity
public class UserData implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    public Long getId() {
        return id;
    }

    @Override
    public String toString() {
        return "com.entities.UserData[ id=" + id + " ]";
    }
}

Entity nr 2:

@Entity
public class Website implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(optional=false)
    private UserData user;

    public Long getId() {
        return id;
    }

    public void setUser(UserData u){
        user = u;
    }

    public UserData getUser() {
        return user;
    }

    @Override
    public String toString() {
        return "com.entities.Website[ id=" + id + ", " + user +" ]";
    }
}

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
   <persistence-unit name="TransactionsPU" transaction-type="JTA">
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
         <jta-data-source>jdbc/__default</jta-data-source>
         <exclude-unlisted-classes>false</exclude-unlisted-classes>
         <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
         </properties>
   </persistence-unit>
</persistence>

HTH.

手心的温暖 2024-12-16 04:12:11

我认为问题可能出在您的实体上。例如,我的用户实体是这样的:

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    private String login;
}

注意 @Id@Basic 注释。我没有看到您在 UserImpl 的属性中使用任何注释。

我建议您修改实体注释。

I think the problem is probably at your entities. For example, my user entity is something like this:

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    private String login;
}

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文