0

I have the following 2 entities:

@Entity
@Table(name="user")
public class User {
    @Id
    @Column("id")
    private long id;

    @Column(name="code", nullable = false)
    private String code;

    @Column(name="session_id", nullable = false)
    private long sessionId;
}

@Entity
@Table(name="task")
public class Task {
    @Id
    @Column("id", nullable = false)
    private long id;

    @ManyToOne(optional = false)
    @JoinColumn(name="primary_user_code", referencedColumnName = "code", nullable = "false")
    private User primaryUser;

    @ManyToOne(optional = true)
    @JoinColumn(name="secondary_user_code", referencedColumnName = "code", nullable = "true")
    private User secondaryUser;
}

The problem is that the two User objects in Task has to have the same sessionId. Is there a way for this to be enforced using hibernate annotations? Or do I have just to bite the bullet and enforce it in code?

I tried looking into the @Where and @WhereJoinTable annotations, but according to this hibernate bug report, link, it is not supported for @ManyToOne


Update

I should have probably mentioned that User already exists and it cannot be changed. What I control is the Task class which I am adding.

The code is not unique, and together with the session_id it identifies a unique User. The session_id refers to a separate table but it is not annotated with a @OneToOne relationship. It is just a plain column and the relationship with the Session table is handled in code. Essentially each User has a code, and linked to multiple Sessions only one of which can be active.

For the Task table that I am adding, I wanted to annotate it if possible

  • Not sure what you're trying to achieve here, but I think what you've written is totally fine. You only have to worry about the foreign key columns which is added to your TASK table. For 2 related User entities, you have 2 diff. foreign key column names, namely primary_user_code and secondary_user_code which wouldn't have name conflicts. The sessionId attribute of User entity won't get mapped to the TASK table, so there's no name conflict. – Ish Oct 03 '17 at 07:37
  • 1
    Not directly related to the question but your foreign key points to User's `code` column. Is this column unique? As per your question, I don't think your requirement can be solved by standard JPA. You'll need to do the check before persisting / merging the involved entities – Al-un Oct 03 '17 at 09:57
  • This will only work on most RDBMs if you add a unique=true to the column definition. Even then I think it is bad practice: FK should always point to PKs. – fhossfel Oct 03 '17 at 10:06

2 Answers2

0

This is not possible in classical SQL. Check constraints are applied per row and since the two session_ids appear in two different rows a check constraints will not help. In Oracle you can have a fast on commit refreshing MV with a constraint for such situations but that is out of scope here.

You best bet is to check this condition on the Java side by using the @PrePersist @PreUpdate callbacks.

@PrePersist @PreUpdate
public void checkSessionId() {
   if (primaryUser.getSessionId() != secondaryUser.getSessionId()) 
        throw new IllegalStateException("Mismatch of sessionId between primary and secondaryIser"); // Better to define your own exception here

}

Of course, the tricky part is to handle the exception correctly. The entityManager will mark the transaction as rollback. So this should really be your last line of defense in case the check in the business logic has failed.

fhossfel
  • 2,041
  • 16
  • 24
  • Thanks, i'll try this. – Sean Archer Oct 03 '17 at 11:51
  • Unfortunately I cannot use `@PrePersist` and `@PreUpdate` since we are using Hibernate Session. This only works when using `EntityManager` https://stackoverflow.com/a/4133629/8712057 – Sean Archer Oct 05 '17 at 04:51
  • With pure Hibernate you can register an Interceptor which does essentially the same thing. See the [Hibernate documentation](https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#events) for details. – fhossfel Oct 05 '17 at 09:14
0

You can write your custom validation annotation and a class that implements ConstraintValidator. By overriding its isValid method you can add your custom logic to your custom annotation. Check out this example.

veljkost
  • 1,748
  • 21
  • 25