JPA2 One-To-Many Empty Join Table - java

I'm trying to create a one-to-many relationship between Publications and Authors, however for some reason when I persist the Publication, the Authors get persisted, but the Join table is empty.
Publication.java
#Id
#Column(name="PUBLICATIONID")
private String id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "authorspublication", fetch = FetchType.EAGER)
private Collection<Author> authors;
Author.java
#Id
#Column(name = "AUTHORID")
private String authorid;
#ManyToOne(optional = false, targetEntity = Publication.class)
#JoinColumn(name = "authorspublication", referencedColumnName = "publicationid")
private Publication authorspublication;
DataParser.java
//pub is created - non managed
//author is created - non managed
author.setPublication(pub);
pub.getAuthors().add(author);
em.merge(pub);
I dont know if owning sides are backwards, or if its something else.
Any insight would be appreciated.

You are using #JoinColumn which indicates you are using a FK column in the authors table to link publications to their authors.
If you want to use a join table you need to remove this and use the #JoinTable annotation. However it seems to me that you have your mappings the wrong way round. Surely the relationship is OneToMany from Authors to Publications? For that matter surely an author can have many publications and a publication can have more than one author?
For the scenario posted however it should like:
public class Publication{
#Id
#Column(name="PUBLICATIONID")
private String id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "publications", fetch = FetchType.EAGER)
private Collection<Author> authors;
}
public class Author{
#Id
#Column(name = "AUTHORID")
private String id;
#ManyToOne
#JoinTable(name = "author_publications", joinColumn = #JoinColumn(name = "AUTHORID"), inverseJoinColumn = #JoinColumn(name = "PUBLICATIONID"))
private Publication publications;
}
However you probably want to change the Publication field in Author to a be a Collection of Publications and replace the #OnetoMany with a #ManyToMany

You haven't defined it to use a #JoinTable; it is currently using a foreign key hence why no insert into the join table. You need to add
#JoinTable
to the one to many field (authors). Don't forget to remove the #JoinColumn since that is for FK relations

Related

Why there is no mappedBy attribute present in #ManyToOne

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

Get id's of a ManyToMany mapping table

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
}

Hibernate: Join table with extra columns, remove children from one side

The scenario is the following
I have 2 tables, Company and Activity. A company can have one or more activities. One of these activities is a "primary" activity, and all others become secondary.
To handle this, I created 2 entities (Activity, Company) and a third entity for the join table, which is CompanyActivity
I used this tutorial as a starting point
Below my code (getters and setters omitted)
Company.java
#Entity
#Table(name = "T_COMPANY")
public class Company {
#Id
#Column(name = "COM_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private List<CompanyActivity> activities = new ArrayList<>();
}
Activity.java
#Entity
#Table(name = "T_ACTIVITY")
public class Activity {
#Id
#Column(name = "ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String description;
private boolean availableOnline;
}
CompanyActivity.java
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Adding activities for a company works without a problem. The children collection contains the newly added activities, and there is always one marked as primary as expected.
The problem happens when updating a company.
When I add a new activity, all previous existing activities are persisted again.
When I remove an activity, it is not removed from the table.
I'm using this code to update a company' activities
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
In this code, newActivities have the new activities that should be considered (this collection does not have the previous ones, I just replace them all)
I tried adding orphanRemoval=true to the #OneToMany(cascade = CascadeType.ALL, mappedBy = "company") on Company, but this deletes the activity type when no other company is using it, which is wrong as they should be available always.
Can you please help me sync the activities collection on Company without removing elements from Activity table ?
Thanks a lot!
I solved it. Here are the steps I followed.
First, I changed my Join table entity cascade types as follows
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Then, I added again the "orphanRemoval" property to Company mapping, and changed my CascadeTypes too, as follows
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "empresa", orphanRemoval = true)
private List<CompanyActivity> activities = new ArrayList<>();
With these changes, my mapping works as expected with the same code I used to replace the relationships.
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
Thanks :)
The way you created your entities is not correct. You don't need to create an entity for your join table (CompanyActivity/T_COMPANY_ACTIVITY). Instead you should be using the #JoinTable on your activities entity. Something like below:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
#JoinTable(
name = "T_COMPANY_ACTIVITY",
joinColumns = #JoinColumn(name = "COM_ID"),
inverseJoinColumns = #JoinColumn(name = "ACT_ID")
)
private List<CompanyActivity> activities = new ArrayList<>();
for more detailed explanation on how One-to-Many/Many-to-One with Join tables work here: http://www.codejava.net/frameworks/hibernate/hibernate-one-to-many-association-on-join-table-annotations-example

JPA CascadeType.REMOVE not working

I have two entities Business which is composed of a list of Departments
#Entity
#Table(name = "Business")
public class Business implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Long id;
#OneToMany(mappedBy = "business",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Department> departments;
#OneToMany(mappedBy = "business", orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<Process> processs;
#ManyToMany
private List<Competence> competences;
}
#Entity
#Table(name = "Department")
public class Department implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "father",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Department> departments;
}
When I try to remove a business instance I get a Mysql Exception
Cannot delete or update a parent row: a foreign key constraint fails (evac_java.Department, CONSTRAINT FK_Department_Business FOREIGN KEY (Business) REFERENCES Business (Id)):HY000 - null
Which means I can't delete the business instance because it has departments associated with it, but a department cannot exists by itself so I want to delete all business's departments when it gets removed. I thought I would achieve this by adding cascade = CascadeType.REMOVE to the #OneToMany annotation in the business entity, but it does not work.
I did a search on the net and I found a lot of questions similar to this one on stackoverflow but they all suggest the same: add cascade = CascadeType.REMOVE or CascadeType.ALL
So I'm wondering if I'm missing somethig.
I'm using Glassfish 4.1 and EclipseLink
I tried with
#OneToMany(mappedBy = "business", orphanRemoval = true)
private List<Department> departments;
on the business entity but it does not work either
Here's the method I'm using to remove entities which is declared in an abstract class
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
JPA can only remove and cascade the remove over entities it knows about, and if you have not been maintaining both sides of this bidirectional relationship, issues like this will arise. If the collection of departments is empty, try an em.refresh() before the remove, forcing JPA to populate all relationships so that they can be correctly removed, though it is better to maintain both sides of the relationship as changes are made to avoid the database hit.

Troubles with cascade deleting objects with parent-child relations to the same table

First of all, sorry for my English.
So, I'm working with MS SQL Server with hibernate and i faced with a problem.
I have next mapping of one of the tables in my DB:
#Entity(name = " ... ")
public class Entity extends BaseEntity implements Comparable {
#Id
#Column(name = "...")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "parent_entity_id", insertable = false, updatable = false)
private Integer parentId;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
#JoinColumn(name = "parent_entity_id")
private Entity parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE}, orphanRemoval = true)
private Set<Entity> children;
//other fields, setters, getters
}
That means, that my Entity objects can have children, which are also an Entity objects.
So, my problems is that I can't correctly delete parent with all his children. When I try to remove parent, i get an SQL error:
The DELETE statement conflicted with the SAME TABLE REFERENCE
So, any ideas, how to solve this problem?
You have a foreign key defined between parent_entity_id and id. Set it to allow cascading deletes: deleting a parent will delete all it's children, and all their children et cetera.
Be sure you actually want this to happen!

Categories

Resources