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)
Related
I'm trying to model a business entity, where said business can have several parent businesses and several child businesses. I'm not sure which relationships are suited, or even what mapping is appropriate. I'm familiar with SQL but new to ORM in Java.
My thinking is that a business can have many or none children, and a business can have many or none parents. Therefore I've tried setting both as OneToMany but also as OneToMany, both resulting in this error: Illegal use of mappedBy on both sides of the relationship.
The implementation:
#Entity
public class Business{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "parentOrgs")
private Collection<Business> chlidOrgs;
#OneToMany(mappedBy = "chlidOrgs")
private Collection<Business> parentOrgs;
// --- Getters and setters below ---
What am I not understanding here? Any and all help much appreciated.
Your current mapping is syntactically incorrect, because only one side of the relationship can be owning side. Owning side is the field defined by value of mappedBy attribute. More detailed explanation can be found from here.
Also removing mappedBy from the one side does not solve the problem, because counterpart of OneToMany must be ManyToOne. Removing it from the both sides leaves us with two unirectional associations, which is also not what is needed.
Because each Business can have multiple parents and it seems to be preferred to be able to navigate directly to the childrens as well, solution is to use bidirectional ManyToMany:
#Entity
public class Business {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(mappedBy = "parents")
private Collection<Business> childrens;
#ManyToMany
private Collection<Business> parents;
}
From database point of view this means following tables:
Business(id)
Business_Business(childrens_id, parents_id)
When necessary, name of the join table and columns can be controlled via JoinTable.
I have Spring Boot project with DB. I've already get table, let's call it "People". I need to add a second table "People_Strings" with two columns: People_id and String. I need to include many strings for every row from People.
How can I map it in my People entity in project?
Edit: I need to do this without creating separete class for String or for People_Strings
If you only need that, you can add the following property to the People entity class:
#ElementCollection
public List<String> strings;
What you need is #OneToMany relation between people and strings. Something like the following will work for you.
#Entity
#Table(name = "People")
public class People{
#Id
#GeneratedValue
private Long id;
#OneToMany(fetch= FetchType.EAGER, cascade=CascadeType.ALL, mappedBy = "peopleId")
private List<PeopleStrings> PeopleStrings;
#Entity
#Table(name = "People_Strings")
public class PeopleStrings{
#Id
#GeneratedValue
private Long peopleId;
#ManyToOne
#JoinColumn(name="peopleId")
private String string;
You'll want a #OneToMany relationship on the Person object (please don't use plurals, eg People). And possibly a #ManyToOne relationship on the PersonString object (again no plurals)
https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/OneToMany.html
https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/ManyToOne.html
This is kinda basic question and I suggest you to read Hibernate "Get Started" first. (one-to-many / many-to-one relations especially)
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.
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).
It's quite some time that I'm trying to figure out this problem and from googling around many people have similar problems.
I'm trying to model a User in a Social Network, using Hibernate, and what is more basic to a social network than to map a friendship relation?
Every user in the system should have a list of it's friends and I thought that this might be an incredibly easy task (just use a ManyToMany relation, right?). So I went on to try the following:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
protected List<User> friends = null
}
The problem now is that it tells me I use ManyToMany wrongly by having no clear distinction between friend and befriended. So far so good, I get the error, but how can I do what I want?
Any idea? I've reached the end of my wisdom.
The fact about Many to Many is that it needs a little more configuration, because its imperative that Hibernate generates a relation table.
Try this:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
#JoinTable(name = "user_friends",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "friend_id"))
protected List friends = null;
#ManyToMany(mappedBy = "friends")
protected List befriended = null;
}
Hope it works =)
EDIT: Also, be very careful with fetch types... you can enter an user fetching out-of-control loop and get all the DB.
The ManyToMany annotation has a mappedBy parameter. I guess something like
#ManyToMany(mappedBy = "friends")
might work. In any case, see the docs.
Edit:
It seems as if a many to many relationship needs to be accessible from both ends. Currently yours is only accessible from one end. Maybe you should add the other relation as well:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
protected List<User> friends = null;
#ManyToMany(mappedBy = "friends")
protected List<User> befriended = null;
}
If that doesn't work you can always introduce a separate class representing the relation between two users, and let every user have a collection of instances of this class.
Good point, and in fact I tried that. Problem is that I then get a complaint about the mappedBy attribute being set on both sides of the relationship, which is true, but invalid.
I was wondering wether a simple #ManyToMany with some clever custom query to fetch the friends might be a solution:
The ManyToMany would generate a join table with user_id and friend_id, but the query would match either of the fields, returning all users where that match either the friend_id or the user_id that is.
Any ideas?
That of course would be a good fix, yet I'm not yet completely sold. Basically to get the friends I'd have to merge the two collections, which is quite unsatisfactory.
Is there absolutely no way to create an idempotent, reflexive relation in hibernate?
I had the same problem today as well.
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
protected List<User> friends = null
}
Should be ok, I used a similar structure. However I got lazy loading exceptions and when setting the fetchType to EAGER, I got complaints about recursive initialisation of bags.
How I fixed my problem: In the query you use to fetch the 'user', do something like:
from User as u left join fetch u.friends
This will initialise the list of friends for that entity. Be warned though that it doesn't initialise any friends from those friends. That is a good thing actually.