Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate self-reference entity as non null column

I'm using Hibernate 5 and Spring 3.2.4. I'm deigning a User entity in which I want to include a reference to the user that has created the entity - so a self reference. The self reference itself isn't too problematic, but I want to specify the field as non null. Is this possible? How do I create the first entry in the DB if the field is non null as there referenced entity does not already exist?

Ex:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long User.id;

    @NotNull
    private String username;

    private String password;

    @NotNull
    private User user;


    // getters and setters omitted for brevity
}

If I try:

User u = new User();
u.setUsername("username");
u.setCreatedBy(u);

and try to persist u, I get the following error message:

Caused by: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: com.domain.User.createdBy -> com.domain.User

I understand that Hibernate is complaining that it cannot reference a transient instance (ie: u), but I cannot persist u unless I have an non-null User that I can reference. But in an empty DB, there is no such entry.

Is this kind of configuration impossible to do? Or is there a way around this?

like image 249
Eric B. Avatar asked Nov 29 '13 04:11

Eric B.


2 Answers

I don't understand this Roo annotations and I don't use Hibernate-specific annotations, only JPA. I don't have any issues with self references. But I have some hints fo you:

  1. As mentioned before, use @ManyToOne annotation.
  2. AFAIK, @NotNull annotation (or nullable field in @Column) does not affect mapping, only DDL generation. I don't use DDL generation from domain model, do I never specify this. Instead I use optional field of @ManyToOne.
  3. What identifier generation strategy you use? If autoincrement, self-references are impossible with NOT NULL constraint. So either use sequence-based identifier generator or remove constraint. I would use first.
  4. As I mentioned, set optional field of @ManyToOne to false, when you have NOT NULL constraint. Otherwise Hibernate attempts to make two queries: insert with createdBy_id set to NULL and then update createdBy_id. And the first query fails with NOT NULL contraint enabled.
like image 163
Alexey Andreev Avatar answered Oct 25 '22 00:10

Alexey Andreev


I found a solution for this. You must use a Sequence generator for your ID (instead of the default auto generated IDs). Then it works.

@Entity
public class UserModel {
  /**
   * We must use a sequence for generating IDs,
   * because of the self reference .
   * https://vladmihalcea.com/2014/07/15/from-jpa-to-hibernates-legacy-and-enhanced-identifier-generators/
   */
  @Id
  @GeneratedValue(generator = "sequence", strategy=GenerationType.SEQUENCE)
  @SequenceGenerator(name = "sequence", allocationSize = 10)
  private Long id;

  /** reference to a parent user, e.g. the manager */
  @ManyToOne(optional = false)
  @NotNull
  UserModel parentUser;

  [...]

The reason is the following: When Hibernate tries to insert a new User it also tries to validate the reference to the parentUser. But that will fail, for the first user we want to insert, or will also fail when a user references himself.

But when IDs are generated with a sequence, then the new/next ID is already known at the time of insert.

like image 31
Robert Avatar answered Oct 25 '22 00:10

Robert