I've got two objects: UserEntity and ApartmentEntity that are relationed OneToMany (one user can have many apartments).
I had problem with serializing it into JSON with infinite recursion ( I solved it using #JsonManagedReference and #JsonBackReference: Infinite Recursion with Jackson JSON and Hibernate JPA issue ), but now i can't read user_id from Apartments table (i need to know which user own current apartment).
ApartmentEntity:
#Entity
#Table(name = "users")
#Scope("session")
public class UserEntity {
#Id
#Column(name = "user_id")
#GeneratedValue
public int user_id;
//other fields ...
#JsonManagedReference
#OneToMany(mappedBy="user", cascade = { CascadeType.MERGE }, fetch=FetchType.EAGER)
private List<ApartmentEntity> apartments;
//getters setters ...
}
ApartmentEntity
#Entity
#Table(name = "apartments")
public class ApartmentEntity {
#Id
#Column(name = "id")
#GeneratedValue
private int id;
// ...
#JsonBackReference
#ManyToOne
#JoinColumn(name="user_id")
private UserEntity user;
//getters, setters ...
}
And now returned JSON don't have user_id inside Apartment attributes.
How to solve this problem? How to read some attribute anyway, using that #Json annotations. I'm stuck here.
JsonManagedReference, JsonBackReference work one way. So UserEntity JSON contains ApartmentEntities, but ApartmentEntity won't contain UserEntity.
Jackson provides a better mechanism to avoid circular references:
http://wiki.fasterxml.com/JacksonFeatureObjectIdentity
Since you already have an id field using JPA, I would recommend using PropertyGenerator on the entity classes:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "user_id", scope = UserEntity.class)
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = ApartmentEntity.class)
Related
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.
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 table question_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
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.
I have two tables user and student. Student and user has one to one relationship.
I am mapping same column with both userId and UserEntity inside StudentEntity
#Entity
#Table(name = "user")
public class User {
#Id
private UUID id;
#Column(name = "name")
private String name;
...
// getter setter ignored for brevity
}
#Entity
#Table(name = "student")
public class Student {
#Id
private UUID id;
#Column(name = "user_id")
private UUID userId;
#OneToOne
#JoinColumn(name = "user_id", referencedColumnName = "id",
insertable = false, updatable = false)
private UserEntity userEntity;
...
// getter setter ignored for brevity
}
#Repository
public interface StudentRepository extend PagingAndSortingRepository<StudentEntity, UUID> {
Optional<StudentEntity> findByUserId(UUID userId);
}
Now when I call any of below method UserEntity is not populated
StudentEntity student = studentRepository.findByUserId(userId).get()
student.getUserId() --- this is present
student.getUserEntity() -- NULL
Not sure why UserEntity is not getting populated as a part of this call.
The issue is most likely that your persistence context still contains the entity that you previously saved (which has no user set). You would first have to clear the persistence context and the load it again to see a non-null user.
Anyway, like suggested in the comments, I would advise you to just map the user. You don't need the userId field. The way you mapped it now, you should be able to use the following:
Optional<StudentEntity> findByUserEntityId(UUID userId);
Also read the official documentation for more information on the matter: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-property-expressions
You can try reverse mapping mapping like below by using mappedby in the owner of the relationship
jpa hibernate #OneToOne #JoinColumn referencedColumnName ignored
As in the title, when performing the update operation, the previous child loses the reference to the parent.
Parent side
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "individual_id")
private List<ContactMedium> contactMedium;
Children side
#Entity
#Table(name = "contactMedium")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ContactMedium
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id ;
#ManyToOne
private Individual individual;
Patch operation
public Individual patch(Individual individual, Long id) {
Individual objectToSave = individual;
objectToSave.setId(id);
return individualRepository.save(objectToSave);
}
When updating, the previous property loses references to the child. How can I prevent this?
Your mappings seems wrong. Ideally they should be as below:
#Entity
#Table(name = "contactMedium")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ContactMedium
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id ;
#ManyToOne
#JoinColumn
private Individual individual;
and
#OneToMany(mappedBy = "individual", cascade = CascadeType.ALL)
private List<ContactMedium> contactMedium;
You need to save the ContactMedium and Individual will automatically be saved. Here ContactMedium has the foreign key reference to Individual (and that is what is depicted in your database table screenshot).
Often one use mappedBy as parameter to #OneToMany instead of #JoinColumn to make the relationship two-ways.
Can you please try to change
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "individual_id")
private List<ContactMedium> contactMedium;
to
#OneToMany(mappedBy = "individual", cascade =CascadeType.ALL)
private List<ContactMedium> contactMedium;
and see if that worked better?
I think you must add the #OneToMany(mappedBy="individual" , cascade =CascadeType.PERSIST) and the #JoinColumn in the #ManyToOne as below:
#OneToMany(mappedBy = "individual", cascade =CascadeType.PERSIST)
private List<ContactMedium> contactMedium;
#ManyToOne
#JoinColumn(name = "individual_id")
private Individual individual;
You should retrieve the entity from the database using the ID first and then update the specific fields and persist the updated entity back.
I have three Entities i'm modeling and am having issues with the associated annotations. I basically have a class that I intend on returning to the caller, a nested listed of Project's and the Project can contain a nested list of Endpoint's. It's a top-level has-a one-to-many, then the nested one-to-many has two one-to-many's.
I've played with #JoinColumn annotations, i've attempted to put a #ManyToOne on the other side of the OneToMany's (but it doesn't like that it's a Long..). I'm just fairly new and unsure on how to do this. I think the mappedById is the solution, but i'm uncertain.
Main Issue: This code allows me to "save" to the database, but upon retrieval, the list of Project's inside the DownDetectorPackage is empty.
A CascadeType.ALL throws referential integrity errors that I don't completely understand.
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Builder
public class DownDetectorPackage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy="id",fetch = FetchType.EAGER)
private List<Project> projects;
#Temporal(TemporalType.DATE)
private Date dateJobsLastRan;
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Project{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String projectName;
#OneToMany(mappedBy="id")
private List<Service> externalDependencies;
#OneToMany(mappedBy="id")
private List<Service> endpoints;
}
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Service {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String url;
private Boolean endpointIsUp;
private String serviceName;
}
You should be using #JoinColumn instead of mappedBy. MappedBy can be used when you have used #ManyToOne in the other class too, which you haven't.
So your final class should look something like this (this is applicable for the other classes too which you have mentioned) :
public class DownDetectorPackage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#JoinColumn(name = "downDetectorPackageId")
#OneToMany(fetch = FetchType.EAGER)
private List<Project> projects;
#Temporal(TemporalType.DATE)
private Date dateJobsLastRan;
Also, remember to state the parent object name in #JoinColumn annotation, since it would create a column for that foreign key.
You should mark every join column as JoinColumn denotating the referenced column from the other entity. Then, you are supposed to say which relation type are using this column.
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String projectName;
#JoinColumn(referencedColumnName = "id")
#OneToMany(fetch = FetchType.LAZY)
private ExternalDependencyEntity externalDependencies;
#JoinColumn(referencedColumnName = "id")
#OneToMany(fetch = FetchType.LAZY)
private EndpointEntity endpoints;
}
Finally, note that in a relational database, every fk column can takes only 1 value (pk of referenced entity id), so, on your entity, you should mark the data type as the entity you are refering to and no as a collection.
I think this sould solve your problem.
I'm writing an API using Spring Boot and Hibernate where my persisted entity objects are also used as DTOs sent to and from the client. This is a simplified version of a typical entity I use:
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#ElementCollection
#CollectionTable(name = "GROUP_STUDENT",
joinColumns = #JoinColumn(name = "GROUP_ID"))
#Column(name="STUDENT_ID")
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}
and this is the associated class:
#Entity
#Table(name = "GROUP")
public class Group {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
private Set<Student> students = new HashSet<>();
// getters and setters
}
As you can see, there is a #ManyToMany association between Student and Group.
Since I send objects like these to the client, I choose to send only the id's of the associations and not the associations themselves. I've solved this using this answer and it works as expected.
The problem is this. When hibernate tries to persist a Student object, it inserts the groups as expected, but it also tries to insert the groupIds into the mapping table GROUP_STUDENT. This will of course fail because of the unique constraint of the mapping table composite id. And it isn't possible to mark the groupIds as insertable = false since it is an #ElementCollection. And I don't think I can use #Formula since I require a Set and not a reduced value.
This can of course be solved by always emptying either the groups of the groupIds before saving or persisting such an entity, but this is extremely risky and easy to forget.
So what I want is basically a read only groupIds in the Student class that loads the data from the GROUP_STUDENT mapping table. Is this possible? I'm grateful for any suggestions and glad to ellaborate on the question if it seems unclear.
I've managed to solve this by making the id-collection #Transient and populating it using #PostLoad:
#Entity
#Table(name = "STUDENT")
public class Student {
#PostLoad
private void postLoad() {
groupIds = groups.stream().map(Group::getId).collect(Collectors.toSet());
}
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Transient
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}