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

时间:2011-10-12 09:37:45

标签: hibernate jpa glassfish eclipselink glassfish-3

标准Java EE环境:从JSF bean调用的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时才有效,这显然是错误的。在测试其他方法时,我发现只有当我关闭服务器时,其他一些更改才会刷新到DB。

所有上述情况都发生在全新的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

4 个答案:

答案 0 :(得分:2)

那么,没有刷新你会得到一个约束错误?请包含异常和堆栈跟踪以及SQL日志,还包括如何映射关系以及如何定义ID。

你有双向关系吗? EclipseLink可能正在尝试解决导致错误的双向关系,您可能需要删除非空约束以允许插入表,或使用自定义程序定义约束依赖关系,或修复双向关系映射。

更新

发生错误是因为您已将子用户表的约束定义为未定义Id的父根表。默认情况下,EclipseLink将写入保留为没有引用来优化事务并避免数据库死锁的辅助表。

从技术上讲,EclipseLink应该找到对User的引用而不是这样做,你是否在最新版本,它可能已经修复,否则记录一个bug并投票给它。 您可以使用DescriptorCustomizer并在根描述符上设置属性setHasMultipleTableConstraintDependecy(true)来解决此问题。

此外,您似乎让所有类共享相同的Root表,似乎只定义了id,这可能不是一个好主意。你可能最好使Root成为@MappedSuperclass并且只有具体子类的表。

答案 1 :(得分:0)

我认为你会发现这两种方法都符合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将在事务提交时或之前输入数据库,或者作为刷新操作的结果......

进入数据库的实际时间是松散定义的。 DB提供外键的ID,因此需要了解数据。

调用flush可能会感觉很糟糕,但它也应该在Hibernate中工作。它将数据写入数据库,但不会为EntityManager删除它,因此实体保持连接状态。

希望这有帮助。

答案 2 :(得分:0)

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

这是带有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);
    }   
}

实体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 + " ]";
    }
}

实体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。

答案 3 :(得分:0)

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

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

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

我建议你修改你的实体注释。