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:
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. QuestionComplaint
→ Question
. 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:
- What are the complaints filed by a user? -
User.getComplaints()
will fetch a UNION ALL of the complaints from all known subtypes - What are the complaints attachede to a question (answer, etc)? -
question.getComplaints()
knows to get records only from the tablequestion_complaints
- 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.