Home > Software design >  How to connect one entity with more than one entity in Spring JPA?
How to connect one entity with more than one entity in Spring JPA?

Time:12-06

I have some problems in understanding how to connect one entity with others in one attribute. That is not typical OneToMany relationship, I'm talking about situation when I need to implement complains functionality in my application: User can complain about several different entities (Question, Answer, Comment or another User), so the Complain entity will have schema relations like: schema

where User connects as One to many to user_id and Many To One to entity_id (1 to * and * to 1 in image).

So, I tried to use parameterized class Complain to implement this (BaseComplain is empty class):

Complain.java

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "complains")
public class Complain<T extends BaseComplain> {

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


    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user_id;

    @ManyToOne
    @JoinColumn(name = "entity_id", nullable = false)
    private T entity_id;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date created_on;

}

User.java

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "users")
public class User extends BaseComplain {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @OneToMany(mappedBy = "user_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complain<BaseComplain>> author_complains;

    @OneToMany(mappedBy = "entity_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complain<User>> complains;

    <...other stuff...>
}

And Question.java (all entities have the same realisation of relationship):

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "questions")
public class Question extends BaseComplain {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "entity_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    @JsonManagedReference
    private Set<Complain<Question>> complains;
    
    <...other stuff...>
}

But it caused (formatted):

org.hibernate.AnnotationException: 
Property com.*.*.Entities.Complain.entity_id has an unbound type and no explicit target entity.
Resolve this Generic usage issue or set an explicit target attribute (eg @OneToMany(target=)
or use an explicit @Type...

I can add all stack trace, but there are only typical spring app exceptions (Bean creation error, embedded Tomcat exception).

So the question is - Is there any way to implement this logics using only, like, "basic" features of JPA?

Probably, I have some ideas of @MappedSuperclass usage, but still need your help.

CodePudding user response:

How about this - it looks like what you have tried to a certain extent; the idea is that the Complaint is an abstract base entity, so that it can have relations to and from it, but the concrete implementations are questions, answers etc. To have a base table and separate ones per complaint type, use @Inheritance(strategy = InheritanceType.JOINED). And use concrete complaint types that are different from the entities they link to, e.g. QuestionComplaintQuestion. So:

@Entity
@Table(name = "complaints")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Complaint {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date created_on;
}

The user in turn relates to a set of Complaint objects, but is not a Complaint itself (doesn't make sense, does it?)

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @OneToMany(mappedBy = "user_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complaint> complaints;
}

And the concrete Complaint instances:

@Entity
@Table(name = "question_complaints")
@PrimaryKeyJoinColumn(name = "id")
public class QuestionComplaint extends Complaint {
    @ManyToOne(fetch = FetchType.LAZY)
    private Question question
}

This entity represents the link from Complaint to Question. The Question entity, which represents a "Question", no matter if it has complaints attached, may optionally have a relation back to the QuestionComplaint:

@Entity
@Table(name = "questions")
public class Question {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
    private List<QuestionComplaint> complaints;
}

Now usages I expect would be:

  1. What are the complaints filed by a user? - User.getComplaints() will fetch a UNION ALL of the complaints from all known subtypes
  2. What are the complaints attachede to a question (answer, etc)? - question.getComplaints() knows to get records only from the table question_complaints
  3. What complaints has user x filed for question y? - SELECT c FROM QuestionComplaint c WHERE c.user.id = :x AND c.question.id = :y

CodePudding user response:

Fistly, in java Generic Types can't deteminable in runtime, so JPA can't determine to fetch from which table, so it will throw Exception which you send.

Secodly your database design is wrong, Complain table will not connect to Question and Answer, Question and Answer need to connect Complain. Like:

Quesiton -|
          v
          ---> Complain ---> User
          ^
Answer   -|

Or you need to add to two fields to Complain table like questionId and answerId.

Q u e s i t o n    A n s w e r 
       ^               ^
       |               |
  (questionID)     (answerId)    
       |               |
        -----------Complain ---> User

One field two foreign table not good design specially for JPA.

  • Related