Hibernate: how to set relations (annotations) in a more complex model? - java

I have the following simplified model:
Business - (1:n) - Assignment - (n:1) - Process
The model classes have the following annotation:
Business
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(mappedBy = "business", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Assignment> assignments;
Assignment
normally one would avoid creating a separate model class here, because Business and Process have a n:m relation. But I need to add attributes to Assigment itself.
#ManyToOne
#JoinColumn(name = "business_id")
private Business business;
#ManyToOne
#JoinColumn(name = "process_id")
private Process process;
Process
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(mappedBy = "process", cascade = CascadeType.ALL)
private List<Assignment> assignments;
Requirements
When a Business or Process is deleted, I also want all his Assignments deleted (but not the partner of the relation on #OneToMany side)
When an Assignment is deleted, I do not want to remove both #OneToMany sides (either Business or Process)
Hints
I tried this with orphanRemoval = true and without, but got no complete sufficient solution
The model classes inherit from a MappedSuperClass which provides Identifier
The #LazyCollection(LazyCollectionOption.FALSE) was needed because I have several #OneToMany relations in a Business and Process but this annotation does not relate to this issue
I use H2 as database and only work with Spring's #Repository interfaces when interacting with persistance layer so not a single line of SQL is written
UPDATE
Unfortunately I thought that the endorsement of following annotation:
#OnDelete(action = OnDeleteAction.CASCADE)
and JUnit test approves correct working of desired behaviour hence answered my question.
#Test
public void test() {
// stores an Business object in db and returns the saved object
Business b = createBusiness();
// stores an Process object in db and returns the saved object
Process p = createProcess();
// stores Assignmnent object with both relations in db and returns the saved object
Assignment a = createAssignment(b, p);
assertThat(a).isNotNull();
// deletes Process object from db
processService.delete(p);
assertThat(processService.getById(p.getId())).isNull();
assertThat(assignmentService.getById(a.getId())).isNull();
assertThat(businessService.getById(b.getId())).isNotNull();
}
But this is not the case. In my JavaFX application the deletion is logged and it looks like its working, but when querying the database afterwards, the entity is still in the table although in the JUnit test it is not...
If anybody could bring some light in this issue I would be very thankful.
If any further information is needed, I will provide it, of course. Thank you very much in advance for helping me out.

EDIT
Finally I have solved the issue and got my desired behaviour with the following setup:
Business
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(mappedBy = "business", orphanRemoval = true)
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Assignment> assignments;
Assignment
#ManyToOne
#JoinColumn(name = "business_id")
private Business business;
#ManyToOne
#JoinColumn(name = "process_id")
private Process process;
Process
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(mappedBy = "process", orphanRemoval = true)
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Assignment> assignments;
Please take note of the added annotation #OnDelete(action = OnDeleteAction.CASCADE). This hint came from here. I omitted Hibernate docs here, because they (imho) do not provide additional useful informations about the feature than the linked SO post.
Update:
Also consider the removed cascade attribute, which was not necessary because I am using hibernates #OnDelete.

Related

How to audit class that has OneToMany unidirectional relationship?

I'm using Spring Data JPA for Auditing. There's a unidirectional relationship between classes Article and File. The Article class looks like this:
#Getter
#Entity
#SuperBuilder(toBuilder = true)
#Table(name = "article")
public class Article extends AuditEntity {
...
#Builder.Default
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true)
#JoinTable(name = "article_additional_file",
joinColumns = #JoinColumn(name = "article_id"),
inverseJoinColumns = #JoinColumn(name = "additional_file_id"))
private List<File> additionalFiles = new ArrayList<>();
...
}
The problem is, when changes occur in the file list (owned files get deleted or added), the modifiedDate field (which is in AuditEntity class and it's annotated with #LastModifiedDate annotation) is not updated (it works with all other fields). And I cannot make it a bidirectional relationship since other classes own the File class as well. So my question is, how to trigger the update of field modifiedDate when changes occur in the file list?
EDIT
I'd prefer not to use Enver, if that's possible. I need to use as little additional libraries as possible
Instead of using #JoinTable use #AuditJoinTable
Info from the hibernate documentation:
When a collection is mapped using these two annotations (#OneToMany + #JoinColumn), Hibernate doesn't generate a join table. Envers, however, has to do this, so that when you read the revisions in which the related entity has changed, you don't get false results.
To be able to name the additional join table, there is a special annotation: #AuditJoinTable, which has similar semantics to JPA's #JoinTable.

Java Hibernate many-to-many not fully updating

I am still fairly new to Hibernate and I am still on a steep learning curve.
I have an application that will track which people were on which event and visa-versa. I have an Event Class and a Person Class linked via a jointable.
I have forms and helper classes that allow me to enter the data on the separate Person and Event classes, persist it, search it, delete it, change it and list it. This is all tested and working.
When I add people to the event I can list all the events and see the list of people attached to the events but when I output the list of People they all have an Event list of size 0.
It is my understanding that if I attach a person to an event that the person should show up in Event.myPeople and that the event should show up in Person.eventList.
Clearly I am doing something wrong and I suspect that it is in my annotations and Declarations. I have listed both set for Event and Person classes below. Failing that I have a fundamental misunderstanding of Hibernate ,both are likely. On the bright side, the more mistakes I make the faster I learn.
Any idea where I am going wrong?
#Entity
#Table(name = "PERSON")
public class Person implements Serializable {
#Id
#GeneratedValue
#Column(name = "person_id")
private int ID;
private String foreName;
private String surName;
#Temporal(javax.persistence.TemporalType.DATE)
private Date dob; //used to differentiate people with same name
#Temporal(javax.persistence.TemporalType.DATE)
private Date joinDate; //used to filter events outside active dates
#Temporal(javax.persistence.TemporalType.DATE)
private Date endDate; //used to filter events outside active dates
private Boolean active;
#ManyToMany()//cascade=CascadeType.ALL)
#JoinTable(name = "PERSON_EVENT", joinColumns = #JoinColumn(name = "person_id"),
inverseJoinColumns = #JoinColumn(name = "event_id"))
private Set<Event> eventList;
#OneToOne
private Sections mySection;
#Entity
#Table(name = "EVENT")
public class Event implements Serializable {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
#Column(name="event_id")
private long id;
private String eventTitle;
private String eventDescription;
private String eventLocation;
#Temporal(javax.persistence.TemporalType.DATE)
private Date startDate;
#Temporal(javax.persistence.TemporalType.DATE)
private Date endDate;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Person> myPeople;
#OneToMany(mappedBy = "myEvent")
private Set<EventType> type;
There is a common misconception about bidirectional relations in Hibernate. Hibernate does not care about consistency in your object tree. If there is a bidirectional relation between events and persons, you have to add the person to the event and the event to the person yourself. Hibernate only persists what you created in memory. Do not expect Hibernate to add any object to any collections, this is the responsibility of the business logic, which shouldn't rely on Hibernate to work properly.
Now, bidirectional relations are special in that inconsistent states in memory cannot even be persisted. With consistent data, Hibernate only has to persist one site of the bidirectional relation, because the other is (or should be) redundant. This is done by marking one part as the "inverse" part. (I'm sorry that I don't know annotation mapping syntax well enough to point to a possible error in you mapping.) "inverse" means to Hibernate nothing more then "ignore when syncing to database", because it is expected to be redundant.
You still have to make sure that the information in both collections are redundant. It actually "works" when you only add the items to the non-inverse collections. But, however, this is not recommended to do because the objects will not be consistent until saved and loaded into a new session.
Also make sure that the bidirectional relation is mapped to the same table using the same foreign keys. I don't know if annotation mapping does detect this automatically.
Hope that helps.
The problem must be due to a missing mappedBy field in the many to many associations.
The field that owns the relationship is required unless the relationship is unidirectional.
I think adding (mappedBy = eventList) will suffice.
You can try to use: #ManyToMany(fetch = FetchType.LAZY)

#ManyToMany relation not save

I have some entities with#ManyToMany relation:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "buses_drivers",
joinColumns = #JoinColumn (name = "driver_id_inner", referencedColumnName = "driver_id"),
inverseJoinColumns = #JoinColumn (name = "bus_id_inner", referencedColumnName = "bus_id"))
private List<Bus> buses;
and
#ManyToMany(mappedBy = "buses", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Driver> drivers;
When execute saving Driver model with some Bus models, all ok. Tables buses_drivers store all keys those entities. But when saving Bus model with drivers, table doesn't change. I think problem with inverseJoinColmns mapping.
That is the expected behaviour. In a bidirectional many-to-many association one side has to be the inverse side. In your case it is the Bus side because it contains mappedBy:
The field that owns the relationship. Required unless the relationship
is unidirectional.
That means that Driver is the owner of the association and Hibernate will only check that side when maintaining the association.
You should definitely redesign your relations.
Without even getting into the problems with your current save scenario, with bidirectional #ManyToMany + CascadeType.ALL, you're destined to get even more troubles.
For example, deleting one bus will due to cascade, delete all its drivers, which due to cascade again, will delete all its buses. You'll basically end up deleting much more than you probably want. Also, check the SQL generated by these mappings, you'll most likely notice that its far from ideal.
For people doesn't understand from the accepted answer. This is more appropriate : Java: saving entities with ManyToMany association
I came across with this problem in test cases when filling test data.
When there is an owning side you just can save child just with owner.

hibernate many-to-many relationship updating unexpectingly

I have two classes:
class TrainingCourse {
Integer id;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name = "TrainingCourseClass", joinColumns = { #JoinColumn(name = "CourseID") }, inverseJoinColumns = { #JoinColumn(name = "ClassID") })
private Set<TrainingClass> trainingClasses;
}
class TrainingClass {
Integer id;
}
In the database they are mapped using a join table. So this is a unidirectional relationship.
From the UI, when a TrainingCourse is created, a list of previously created TrainingClasses are selected from the UI.
Now if I create the TrainingCourse, then it automatically updates the associated TrainingClasses also. But trainingClass is independent of TrainingCourse and can exist independently. So TrainingClasses are created and updated separately from the TrainingCourse. So saving the TrainingCourse should save data in the TrainingCourse table and it will also save the association in the join Table TrainingCourseClass. Nothing should happen in the table TrainingClass.
However if I add these to the columns:
nullable=false, updatable=false and CascadeType.REMOVE
#OneToMany(cascade = CascadeType.REMOVE, fetch=FetchType.EAGER)
#JoinTable(name = "TrainingCourseClass", joinColumns = { #JoinColumn(name = "CourseID", nullable=false, updatable=false) }, inverseJoinColumns = { #JoinColumn(name = "ClassID", nullable=false, updatable=false) })
private Set<TrainingClass> trainingClasses;
Then the problem is fixed ie creating trainingCourse doesn't update the trainingClass table. Now I am not 100% sure whether it is the right solution or how it is working to solve the problem. There is also another thing called MappedBy. I am not sure whether this is relevant here.
I just used it as a guess and it is working. Moreover, this seems to be really a many-to-many relationship ie The same class can belong to many courses and one course can include many classes. But one-to-many relationship is also working. This is not very convincing. The trainingclass is really unaware of what training courses include it. It looks like the difference between one-to-many and many-to-many is like whether or not to have bidirectional pointers to each other.
Hence please suggest whether the above approach is correct to prevent updating the trainingclass while creating the trainingcourse.
Thanks
Your first mapping uses cascade = ALL. That means that every operation you make on a TrainingCourse (persist, merge, remove, etc.) will also be applied on the associated TrainingClass. That's precisely what you don't want, if I understand correctly. So just don't set any cascade to this association.
Regarding OneToMany vs. ManyToMany: if what you really want is a OneToMany (i.e. a TraningClass should not be associated with more than one TrainingCourse), then you should have a unique contraint on the TrainingCourseClass.ClassID column. That's what guarantees that the association is a OneToMany and not a ManyToMany.

How to define many-to-many to itself in JPA?

I'm trying to define this SQL schema in JPA:
TABLE event (id INT)
TABLE chain (predecessor INT, successor INT)
In other words, every event has a number of successors, which are events themselves. I'm trying to do it this way in JPA:
#Entity
public class Event {
#Id Integer id;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "chain",
joinColumns = #JoinColumn(name = "successor"),
inverseJoinColumns = #JoinColumn(name = "predecessor")
)
private Collection<Event> predecessors;
}
#Entity
public class Chain {
#Id Integer id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "predecessor")
private Event predecessor;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "successor")
private Event successor;
}
Is it correct?
Normally one would not both define a ManyToMany with a JoinTable and then also separately define the join table as its own Entity. Join tables aren't Entities normally, they're just join tables and the provider manages them under the hood. You're creating a lot of headaches for yourself as far as properly maintaining in memory state of the application when you change one or the other. (Which is necessary if, for example, you want to use L2 caching.)
So, either one works fine, combined, they are sort of oddsauce. Usually if you defined Chain as an entity, you would just have a list of Chain on the Event. Not also redefine it as a JoinTable on Event. Does that make sense?
(and as it is currently strictly defined, it will break if you try to make changes through the collection on Event unless that ID is a database generated sequence.)
Edit: something like this -
#Entity
public class Event {
#Id Integer id;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy="successor")
private Collection<Chain> predecessorChains;
}
What you wrote originally can be made to work as long as you realize that the Collection<Event> predecessors is inherently read only and will get fubared if you try to L2 cache it. The fact that you put a CascadeType on it makes one thing that you wanted to be able to add and remove Events to/from that, which will explode when hibernate tries to execute illegal SQL.
If you use #ManyToMany, you don't need Chain entity (otherwise, if you need Chain entity, for example, to store additional data associated with the relathionship, you need to declare two one-to-many relationships between Event and Chain).

Categories

Resources