【问题标题】:JPA: How to add the first entity of recursive non-null relationJPA:如何添加递归非空关系的第一个实体
【发布时间】:2011-07-27 13:06:57
【问题描述】:

如何添加递归非空关系的第一个实体?

尝试对用户实体使用我的默认审计守卫 (EntityListener) 时会出现问题。 Hibernate 无法插入第一个用户。例子是情况的简化:

@Entity
class User {
    @Id @GeneratedValue
    private Long id;
    @Basic
    private String name;
    @ManyToOne(optional=false)
    @JoinColumn(nullable=false,updatable=false)
    private User createdBy;

    // getters & setters
    // equals & hashcode are based on name
}

我尝试过类似的方法:

User user = new User();
user.setName( "Some one" );
user.setCreatedBy( user );

休眠失败,根异常是:

 com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert the value NULL into 
column 'createdby_id', table 'mydb.user'; column does not allow nulls. INSERT fails.

我知道几种解决方法:手动插入第一个实体(SQL 插入)或设置 nullable=true。首先很烦人,其次是完整性故障点(需要自定义审计侦听器和数据库触发器)。有更好的选择吗?或者,换句话说:是否有仅限 JPA 的解决方案?

我的平台是:SQL Server 2008 和 Hibernate 3.6 作为 JPA2 提供程序。

编辑:起初,我使用的是persist()。我尝试了merge(),这可以做出更明智的猜测,但没有区别。我也试过CascadeType.MERGE

【问题讨论】:

    标签: hibernate jpa recursion jpa-2.0


    【解决方案1】:

    not null 约束要求您在引用字段中插入 user.id,但如果您已将 id 字段设置为自动生成,则该值在插入之后才知道。为了解决这个问题,您可以使用序列来生成@Id

    create sequence SEQ_USER
    

    并通过获取该序列的下一个值并将两个字段设置为该值来插入第一个用户。使用来自 JPA 的本机 SQL 查询,如下所示:

    User u = new User();
    u.setName("First User");
    Query q = em.createNativeQuery("VALUES nextval for SEQ_USER");
    Long nextId = Long.valueOf((Integer) q.getSingleResult());
    q = em.createNativeQuery("insert into user (id, name, created_by) values (?, ?, ?)");
    q.setParameter(1, nextId).setParameter(2, u.getName()).setParameter(3, nextId);
    q.executeUpdate();
    // don't forget to bring u into persistence context
    u = em.find(User.class, nextId);
    

    这有点笨拙,但只能调用一次。所有其他用户将以正常方式持久化(使用 SEQ_USER 作为 @Id 的生成器序列)

    或者,您可以编写自己的 SequenceGenerator 来做到这一点:

    public class UserIdGenerator implements IdentifierGenerator {
    
      @Override
      public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
    
        User o = (User) object;
        Connection connection = session.connection();
        try {
          PreparedStatement ps = connection.prepareStatement("VALUES nextval for SEQ_USER");
          ResultSet rs = ps.executeQuery();
          if (rs.next()) {
            return rs.getLong(1);
          }
        } catch (SQLException e) {
          log.error("Unable to generate Id: " + e.getMessage(), e);
        }
        return null;
      }
    }
    

    在用户中,您可以将该生成器设置为:

    @Id
    @GenericGenerator(name = "idSource", strategy = "com.example.db.UserIdGenerator", parameters = { @Parameter(name = "sequence", value = "SEQ_USER") })
    @GeneratedValue(generator = "idSource")
    private Long id;
    

    然后

    User u = new User();
    u.setName("First User");
    u.setCreatedBy(u);
    em.persist(u);
    

    按预期工作。

    【讨论】:

    • 我更新了原始帖子:我已剥离 @GeneratedValue。正如您所提到的,生成的身份是问题所在。但是 SQL Server 中没有序列。 SQL 插入看起来像:insert into user ( name, createdBy ) values ('john doe',scope_identity());。但真正的 User 类有 13 个非空列...
    • 您可以尝试模拟一个序列,参见。这个:stackoverflow.com/questions/282943/…。或者您可以将 id 生成完全从数据库中取出并进入 JPA 层,可能使用 UUID 作为 User 表的主键,如下所述:onjava.com/pub/a/onjava/2006/09/13/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-09
    • 2014-04-29
    • 2020-10-19
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    相关资源
    最近更新 更多