I've been searching for days but can't seem to find the answer.
Given this many to many (employee/meeting)
#Entity
#Table(name="EMPLOYEE")
public class Employee {
#Id
#Column(name="EMPLOYEE_ID")
#GeneratedValue
private Long employeeId;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="EMPLOYEE_MEETING",
joinColumns={#JoinColumn(name="EMPLOYEE_ID")},
inverseJoinColumns={#JoinColumn(name="MEETING_ID")})
private Set<Meeting> meetings = new HashSet<Meeting>();
}
#Entity
#Table(name="MEETING")
public class Meeting {
#Id
#Column(name="MEETING_ID")
#GeneratedValue
private Long meetingId;
#ManyToMany(mappedBy="meetings")
private Set<Employee> employees = new HashSet<Employee>();
}
I can add employees to a meeting and it shows up in the employee_meeting table.
When I do get a meeting object and delete it, it's also gone from the join table but remains in the employee set... Is this the expected behaviour?
Here's how I would remove a meeting object
session.delete(meeting);
transaction.commit();
At this point it's gone from the table.
Thanks!
Yes this is correct behaviour. If you have many-to-many relationship, then you need to delete it manually. Please refer this link for hibernate collection mapping strategy
Related
First of all im not too experienced with all that DB stuff so pls have mercy on me.
I have 2 entity classes. Survey and Question.
#Entity
#Table
public class Survey {
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String surveyName;
private int questionCount;
private String owner;
private int participant;
#OneToMany(mappedBy= "survey", cascade=CascadeType.ALL)
private List<Question> question = new ArrayList<Question>();
and
#Entity
#Table
public class Question {
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String question;
private int index;
#ManyToOne
#JoinTable
private Survey survey;
I have a hard time to write the right query to get all Questions which belong to one Survey with HQL. Maybe somebody can give me a hint in the right direction or give me an example how to do it.
There are 2 ways to get all Questions of a Survey:
1. Traverse a managed association
You modeled a bidirectional many-to-one association. So, if you already loaded the Survey entity, you can call the getSurvey() method on that entity. Hibernate will then generate and execute the query to fetch the Survey entities.
There are several ways to optimize that if you're experiencing n+1 select issues. But that's a more advanced topic.
2. Implement a query
If you don't need the Survey entity, it's better to use a JPQL query which only loads the Survey entities. JPQL's syntax is very similar to SQL, and I explained it in great details in my guide to JPQL.
Here is the query that returns all Survey entities associated with a Query.
TypedQuery<Question> q = em.createQuery("SELECT q FROM Survey s JOIN s.question q WHERE s.id = :id", Question.class);
q.setParameter("id", id);
q.getResultList();
First I am a newbie with DBs in general so if this turns out to be a dumb question, please be a bit tolerant and generous with details in ur answer and thanks alot for any effort put in to help !!
I am having trouble designing the class structure for my code and will welcome any suggestions concerning the matter. I have 3 data classes
1) School
2) Teacher
3) Workshop
A School Entity has a List<Teacher> and a List<Workshop> they hosted.
A Workshop Entity has a single Host School Entity and a List<Teacher> of participants.
A Teacher Entity had a List<Workshop> they attended and an employment history List<School> (not showing school List in the code below, as I am leaving it till later when I figure simpler things first, but its a target)
Every single school in a given city will be assigned one entry and no more, everything else from Teacher and Workshop has to reference that single Entry.
Every single teacher in a given city will be assigned on entry/ account and no more, so everything else has to reference it
I know I can work with IDs, but that will involve tons of duplication and I wont be able to get an object from within another object, the entry will be a Long and and even if I make methods to automate the whole thing, this will eat up my query quotas very fast( would really love to avoid that)
I would like to be able to query for a single entity(School or Teacher or Workshop) and be able to see all the other Entity Lists associated .
Plus,
Teachers move around, so I must be able to remove a Teacher (as a Child Entity) from one School and add it to another, while maintaining the record of which School hosted their previous workshops.
I have done this much on my own
#Entity
public class School
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToMany(cascade = CascadeType.ALL, mappedBy="school")
private List<Teacher> teachers;
#OneToMany(cascade = CascadeType.ALL,mappedBy="school")
private List<Workshop> workshops;
// Getters and Setters and some methods
}
#Entity
public class Teacher
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne
private School school;
private List<Workshop> Workshops;
// Getters and Setters and some methods}
#Entity
public class Workshop
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne
private School school;
private List<Teacher> Participants;
// Getters and Setters and some methods}
Currently I am able to assign as many teachers and workshops to a particular School entity, however, I cannot assign teacher entities (that are already assigned to a School - key point here) to a Workshop. This is the Error I always get
Detected attempt to establish Workshop(no-id-yet) as the parent of School(6148469022523392)/Teacher(5585519069102080) but the entity identified by School(6148469022523392)/Teacher(5585519069102080) is already a child of School(6148469022523392). A parent cannot be established or changed once an object has been persisted.
The order of which is which varies depending on which Entity got created and persisted first.
Thanks alot and awaiting any advice and consultation ... I am not looking for a complete solution, I just need someone to point out how this could be done ( I am sure I am not the first to get stuck here and I am sure that generous experts will help out)
Now look I have some points : Three Entites you have : School , Workshop , Teacher.
School have oneTomany with - workshop & teacher. So once we are persisting school we'll have entries in both the tables - workshop & teacher.And also you wanted to have A Workshop Entity has a single Host School so we achieved that also while persisting as per below code.
Your School Entity :
#Entity
public class School
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy="school")
private List<Teacher> teachers;
#OneToMany(cascade = CascadeType.PERSIST,mappedBy="school")
private List<Workshop> workshops;
// Getters and Setters and some methods
}
Your Teacher Entity:
#Entity
public class Teacher
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="key")
private School school;
// Getters and Setters and some methods}
Your WorkShop Entity:
#Entity
public class Workshop
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Key key;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="key")
private School school;
and then :
em.getTransaction().begin();
School schoolObj = new School();
schoolObj.setName("School 1");
List <Teacher> teachers = new ArrayList<Teacher>();
List <Workshop> workshopList = new ArrayList<Workshop>();
Teacher teacher = new Teacher();
teacher.setSchool(schoolObj);
teacher.setName("Teacher 1");
teachers.add(teacher);
Teacher teacher1 = new Teacher();
teacher1.setSchool(schoolObj);
teacher1.setName("Teacher 2");
teachers.add(teacher1);
teacher teacher2 = new Teacher();
teacher2.setSchool(schoolObj);
teacher2.setName("Teacher 3");
teachers.add(teacher2);
Workshop ws = new Workshop();
ws.setSchool(schoolObj); //By this you setted schoolObj in workshop entity
ws.set(some attribute);
workshopList.add(ws);
school.setTeachers(teachers); //By this you setted teachers through school ,i.e., entry came in teachers table too.
school.setWorkshops(workshopList); //By this you setted workshopList through school ,i.e., entry came in workshop table too.
em.persist(schoolObj);
em.getTransaction().commit();
em.close();
Now you mentioned that: A WorkShop Entity also has a List of participants .And A Teacher Entity had a List they attended and an employment history List. This shows you are having ManyToMany between Workshop & Teacher. As in your case Workshop has List of teachers and Teachers also have List of Workshop's.So here you will be requiring a joining table to lonk this ManyToMany relationship. Similarly, between teacher & school you have ManyToMany as both have List of one other.So here also you will be needing joining table.To learn more about this click here.
Hence to set this ManyToMany relationship you have to link through a joining table not by persisting here as it will clash then. And if you want to fetch data as per this ManyToMany relationship then you have make a separate query.
Hope this help !
I have two entities, let's say
Person.java:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#OneToMany(mappedBy = "personData", cascade = CascadeType.PERSIST)
private List<SkillsData> skillsData;
// ...
}
SkillsData.java
#Entity
#Table(name = "SkillsData")
public class SkillsData implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#JoinColumn(name = "PERSONID")
#ManyToOne(cascade = REMOVE)
private Person personData;
// ...
}
When I create a person, add a list of type SkillsData to it's skillsData field and persist it everything works with no exceptions thrown, but when I browse the database directly in the SkillsData table the field PERSONID is not populated and because of that the skills added can't be referenced to the right person.
I'm trying to fix this problem for quite some time and I'll be thankful for any help.
The problem might be in the fact that you're not setting SkillsData.personData before persisting leaving it null.
You must set it cause adding SkillsData to the Person.skillsData list is not enough since you declared this side of relationship as inverse(mappedBy attribute).
Therefore it is the SkillsData.personData non-inverse side who is responsible for establishing this relationship.
I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!
I am having troubles getting this working and I wonder if what I am doing simply does not make sense?
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
....
}
#MappedSuperclass
public abstract class Sample {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne (cascade=CascadeType.ALL)
protected Application application;
....
}
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="application", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
Application application;
}
The problem I am having is that I am able to add parts, the database looks correct, then when I attempt to fetch the the parts list I get an empty list.
I can get it to work if I compromise on the database structure by changing these classes:
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="testSample", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
TestSample testSample;
}
The tables are being auto generated by hibernate, so they are coming out like this:
application
id : number
....
test_sample
id : number
application_id : number
...
part
id : number
application_id : number
If I change it to the less desirable way that works, the last table is different:
part
id : number
test_sample_id : number
Because the id's in all cases are being auto generated, there are no shared primary keys. Essentially what I am trying to do is use mappedby where mappedby is referring to a field that is not the primary key of the table/class called "TestSample". This is what I am not sure if makes sense in JPA.
The OneToMany is bi-directional with the "Part" class. I think this is getting very difficult to explain (:
Your one-to-many association between TestSample and Part is not bidirectional, the mappedBy is not correct (the application table is not owning the relation, it is not even aware of test_sample), your mapping doesn't make sense. There is something to change.
I think that you should show what the expected tables are, not the generated one (since the mappings are incoherent, the generated result can't be satisfying). You are talking about compromise so I believe that you have an idea of what the expected result should be. Please show it.