I have the following mapping. It works fine. the problem is that Hibernate created 3 tables for that: request, research and request_researches.
request_researches is not needed.
I want the foreign key to be inside the research table. (research.request_id)
#Entity
public class Request{
#Id
private Long id;
#OneToMany(targetEntity = Research.class,cascade= CascadeType.ALL, fetch = FetchType.EAGER)
private List<ResearchEntity> researches = new ArrayList<>();
//... getters setters
}
Is this possible?
Thanks
Just add a #JoinColumn annotation where you specify the column from the related entity:
#OneToMany(targetEntity = Research.class,cascade= CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "request_id")
private List<ResearchEntity> researches = new ArrayList<>();
Related
I am a JPA newbie and trying to understand #JoinTable annotation for Bidirectional OneToMany relationship b/w Project and Task Entities where Project can have multiple tasks.
I can use #JoinTable with Entity having #ManyToOne annotation, but when I am placing #JoinColumn on the other Entity having #OneToMany, I am not getting an option to specify "mappedBy" attribute on #ManyToOne annotation.
I would like to know why ?
I have tried placing #JoinTable annotation on both the entities but then Hibernate is itrying to insert two records in Join table
Project Entity :-
#Entity
#Data
public class Project {
#Id
#Column(name = "project_pk")
#GeneratedValue
private Long id;
#Column(name = "project_name")
private String name;
#OneToMany(mappedBy = "project", cascade = CascadeType.ALL)
List<Task> tasks;
}
Tasks Entity :-
#Entity
#Data
public class Task {
#Id
#GeneratedValue
#Column(name = "task_pk")
private Long id;
public Task() {
}
private String name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinTable(name = "project_related_tasks",
inverseJoinColumns = #JoinColumn(name = "project_id", referencedColumnName = "project_pk"),
joinColumns = #JoinColumn(name = "task_id", referencedColumnName = "task_pk")
)
private Project project;
public Task(String name) {
this.name = name;
}
}
There are two ways to implement one-to-many relations:
Using a join table
Using a foreign key on the many-to-one side
mappedBy is used for the second way (using a foreign key). You don't have to specify mappedBy, if you want to use a join table.
Using a join table is not very good idea because you can't control that join table using Hibernate. For example you can't just add a record to a join table directly.
what is #JoinColumn and how it is used in Hibernate
I want to convert the following mapping on courseDetails to manyToMany.
This is because I get an exception Found shared references to a collection: com.xyz.courseDetails and I assume this happens because the relation is not actually one to many in the database, since there are some course_detail tuples that has multiple courses.
#Entity
#Table(name = "courses")
public class Course
{
#Column(name = "course_detail_id")
private Long extendedCourseDetailId;
...
#OneToMany(fetch = FetchType.LAZY, targetEntity = CourseDetail.class, cascade = CascadeType.ALL)
#JoinColumn(name="id", referencedColumnName="course_detail_id")
private List<CourseDetail> courseDetails = new ArrayList<>();
}
Simply changing the annotation to ManyToMany does not work, JPA somehow couldn't find the related columns. Why? How can I do this?
What do you think of this :
Let's assume the entity CourseDetail has as ID :
public class CourseDetail
{
#Id
#Column(name = "cd_id")
private Long courseDetailId;
So this non tested code might help you.
where the table "course__course_detail" will be automatically created to hold the relationship with 2 columns : "course_id" and "coursedetail_id".
#Entity
#Table(name = "courses")
public class Course
{
#Id
#Column(name = "c_id")
private Long courseId;
// #Column(name = "course_detail_id") // I comment because I dont understand the purpose
// private Long extendedCourseDetailId;
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "course__course_detail",
joinColumns = #JoinColumn(name = "course_id", referencedColumnName="c_id"),
inverseJoinColumns = #JoinColumn(name = "coursedetail_id", referencedColumnName="cd_id"),
)
private List<CourseDetail> courseDetails = new ArrayList<>();
}
PS: NOT TESTED
Feel free to tell me more in comments.
I have a scenario like below.
Lets say EntityA has three nested entities EntityB, EntityC, EntityD. And all of EntityB, EntityC, EntityD has several nested entities inside them.
But while selecting for EntityA it selects the whole tree of nested entities. Whereas I want to fetch a specific branch. Lets say only EntityA, EntityB and all sub entities of EntityB are to be fetched leaving EntityC and EntityD back then I am not sure how to do that. As spring jpa brings all the nested objects back to me.
I am using below collection mapping.
#Entity
#Table(name = "customer_party_mapping")
#Data
public class CustomerPartyMappingEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column(name = "customer_id")
private Integer custmerId;
#Column(name = "orgtype_id")
private Integer orgTypeId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL )
#JoinColumn(name = "customer_party_mapping_id")
#Fetch(value = FetchMode.SUBSELECT)
private List<CustomerPartyBookingLocationEntity> customerPartyBookingLocation=new ArrayList<CustomerPartyBookingLocationEntity>();
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL )
#JoinColumn(name = "customer_party_mapping_id")
#Fetch(value = FetchMode.SUBSELECT)
private List<CustomerPartyFieldMappingEntity> customerPartyFieldMappingEntity=new ArrayList<CustomerPartyFieldMappingEntity>();
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL )
#JoinColumn(name = "customer_party_mapping_id",referencedColumnName="id")
#Fetch(value = FetchMode.SUBSELECT)
private List<CustomerPartyOtherDocumentEntity> otherDocumentsList=new
ArrayList<>();
#OneToOne( cascade={ CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "customer_name_screening_id", referencedColumnName="id")
private CustomerNameScreeningEntity customerNameScreeningEntity;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL )
#JoinColumn(name = "customer_party_mapping_id")
#Fetch(value = FetchMode.SUBSELECT)
private List<CustomerDocInfoTrackingEntity> customerDocInfoTrackingList=new
ArrayList<CustomerDocInfoTrackingEntity>();
}
And I am calling
List<CustomerPartyMappingEntity> customerPartyMappingEntityList = customerPartyMappingRepository.findByCustmerId(customerid);
It gets all the nested mapped list of entities wheras I need only CustomerPartyMappingEntity and its list of customerPartyFieldMappingEntity nested object.
Any help will be appreciated.
First use FetchType.LAZY for nested entity.
Then you can use #EntityGraph to fetch nested entity by name and their nested entity using their name with . in the repository. You use to just specify the nested property in attributePaths like
#EntityGraph(attributePaths = {"customerPartyBookingLocation"})
And the nested property of customerPartyBookingLocation like
#EntityGraph(attributePaths = {"customerPartyFieldMappingEntity.subField"})
Example:
#EntityGraph(attributePaths = {"customerPartyBookingLocation", "customerPartyFieldMappingEntity.subField"})
List<CustomerPartyMappingEntity> findByCustmerId(Integer customerid);
Note: You can't use #EntityGraph with #Query annotation
If your entities are really setup correctly, see for instance the subselect example here and remove your EAGER (you are currently instructing hibernate to fetch all these fields upon entity initialization). It should work.
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
}
I have some problems with inheritance mapping. Here my database structure:
And associated entities:
AbstractEntity:
#MappedSuperclass
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id #GeneratedValue(strategy = IDENTITY)
#Column(unique = true, updatable = false, nullable = false)
private ID id;
public ID getId() {
return id;
}
#SuppressWarnings("unused")
public void setId(ID id) {
this.id = id;
}
UserAcitvity entity:
#Entity #Table(name = "user_activity")
#Inheritance(strategy = JOINED)
#AttributeOverride(name = "id", column = #Column(name = "ua_id"))
public abstract class UserActivity extends AbstractEntity<Long> {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "ua_user_id")
private User user;
...
}
Comment entity:
#Entity #Table(name = "comment")
#PrimaryKeyJoinColumn(name = "cm_id")
public class Comment extends UserActivity {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "cm_question_id")
private Question question;
...
}
Question entity:
#Entity #Table(name = "question")
#PrimaryKeyJoinColumn(name = "qs_id")
public class Question extends UserActivity {
...
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "question")
private List<Answer> answers = new ArrayList<>();
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "question")
private List<Comment> comments = new ArrayList<>();
...
}
Answer entity:
#Entity #Table(name = "answer")
#PrimaryKeyJoinColumn(name = "asw_id")
public class Answer extends UserActivity {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "asw_question_id")
private Question question;
...
}
and User entity:
#Entity #Table(name = "user")
#AttributeOverride(name = "id", column = #Column(name = "user_id"))
public class User extends AbstractEntity<Long> {
...
#OneToMany(cascade = REMOVE)
private List<Question> questions = new ArrayList<>();
#OneToMany(cascade = REMOVE)
private List<Answer> answers = new ArrayList<>();
#OneToMany(cascade = REMOVE)
private List<Comment> comments = new ArrayList<>();
...
}
Problem:
When I try to save or delete a User I get an exceptions:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into user_question (user_user_id, questions_qs_id) values (?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
and:
org.hibernate.engine.jdbc.spi.SqlExceptionHelper : 147 = user lacks privilege or object not found: USER_ANSWER
Hibernate is trying to create a table: user_question and user_answer which me do not need.
What I should doing for fixes ?
I don't think you can achieve this by mapping the ManyToOne association to User generically in the UserActivity entity. That's probably too confusing for the JPA provider (Hibernate).
Instead, I think you need to map the association to User in each of the Question, Answer and Comment entities. Yes, I know that would be duplicated code, but it looks like the only way you will then be able to qualify the OneToMany mappings in User using the mappedBy reference.
For instance, your Question entity would have an association defined as:
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "ua_user_id")
private User questionUser;
Depending on how clever (or not) Hibernate is about the above association, you may need to specify the table="USER_ACTIVITY" in the JoinColumn annotation.
Then the User would have the OneToMany as:
#OneToMany(mappedBy="questionUser", cascade = REMOVE)
private List<Question> questions = new ArrayList<>();
Similarly for each of Answer and Comment.
Of course, I haven't tried this, so I could be wrong.
It's probably happening because when you set the #OneToMany mapping then the hibernate will create an auxiliary table that will store the id from the entities on the relationship.
In this case you should try the following:
#OneToMany(cascade = REMOVE)
#JoinColumn(name = "answer_id")
private List<Answer> answers = new ArrayList<>();
The #JoinColumn annotation will map the relationship without the creation of the auxiliary table, so it's pretty likely this solution will help you in this situation.
Try this mapping, this should work as you expect according to section 2.2.5.3.1.1 of the documentation:
#Entity
public class User {
#OneToMany(cascade = REMOVE)
#JoinColumn(name="user_fk") //we need to duplicate the physical information
private List<Question> questions = new ArrayList<>();
...
}
#Entity
public class Question {
#ManyToOne
#JoinColumn(name="user_fk", insertable=false, updatable=false)
private User user;
...
}
The reason why the auxiliary association is created, is that there is no way for Hibernate to know that the Many side of the relation (for example Question) has a foreign key back to User that corresponds to the exact same relation as User.questions.
The association Question.user could be a completely different association, for example User.questionCreator or User.previousSuccessfulAnswerer.
Just by looking at Question.user, there is no way for Hibernate to know that it's the same association as User.questions.
So without the mappedBy indicating that the relation is the same, or #JoinColumn to indicate that there is no join table (but only a join column), Hibernate will trigger the generic one-to-many association mapping solution that consists in creating an auxiliary mapping table.
The schema misses such association tables, which causes the error that can be solved with the mapping above.
If you want unidirectional one-to-many usage in your entity relationship.
Try with..JoinTable
#OneToMany(cascade = REMOVE)
#JoinTable(name = "user_question", joinColumns = {
#JoinColumn(name = "user_id")}, inverseJoinColumns = {
#JoinColumn(name = "qs_id")})
private List<Question> questions = new ArrayList<>();