Hibernate: Adding a new element to association list does not persist - hibernate-mapping

I have a ManyToMany association between two Entities: COURSE and STUDENT. They are associated through a STUDENT_COURSE_MAP association table.
Course.java:
#Entity
#Table(name="COURSE")
public class Course implements java.io.Serializable {
#ManyToMany(
mappedBy="courses",
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
private List<Student> students = Collections.emptyList();
// Rest of the class, getters, setters etc
}
Student.java:
#Entity
#Table(name="STUDENT")
public class Student implements java.io.Serializable {
#ManyToMany(
targetEntity=Course.class,
cascade = { CascadeType.PERSIST, CascadeType.MERGE }
)
#JoinTable(
name="STUDENT_COURSE_MAP",
joinColumns=#JoinColumn(name="STUDENT_REF"),
inverseJoinColumns=#JoinColumn(name="COURSE_REF")
)
private Set<Course> courses = Collections.emptySet();
// Rest of the class, getters, setters etc
}
I want to add a student (id=11) list for one particular course (id=77) as follows (transaction is already begun):
// Create new list and add a student to it
Student student_11 = (Student) session.get(Student.class.getName(), 11);
List<Student> studentsList = new ArrayList<Student>(1);
studentsList.add(student_11);
// Now, set the list of students in the course
Course course_77 = (Course) session.get(Course.class.getName(), 77);
course_77.setStudents(studentsList);
I save it using the following code:
session.flush();
session.getTransaction().commit();
However, the association - course_77 and student_11 - does not get persisted to the database. There are no insert statements generated in the hibernate log.
I even tried calling session.saveOrUpdate(course_77). Only the following is logged when saveOrUpdate is called:
allowing proxied method [saveOrUpdate] to proceed to real session
persistent instance of: org.whatra.tradecog.db.entity.Artist
ignoring persistent instance
object already associated with session: [Course#77]
Hints/tips/solutions are much appreciated.
Note 1:
The whole persistence thing works just fine for me when I create new Course and Student objects and save. What does not work is when I retrieve existing Course and existing Student objects and add a new association between them.

Here's what was wrong. It turns out I was saving only one side of the association. This is what I should have done:
// Create new list and add a student to it
Student student_11 = (Student)session.get(Student.class.getName(), 11);
Course course_77 = (Course) session.get(Course.class.getName(), 77);
List studentsList = new ArrayList(1);
studentsList.add(student_11);
Set coursesList = new HashSet(1);
coursesList.add(course_77);
course_77.setStudents(studentsList);
// THIS IS WHAT'S WRONG - I MISSED THIS NEXT LINE
student_11.setCourses(coursesList);
I found the answer at http://en.wikibooks.org/wiki/Java_Persistence/Relationships (look under 'Common Problems').

Related

Optaplanner: problems with InverseRelationShadowVariable

have a many-1 relationship pupil-formGroup: pupils are assigned to a formGroup and a formGroup can contain many pupils. I have attempted to implement an InverseRelationShadowVariable having watched your video/tutorial https://www.youtube.com/watch?v=ENKHGBMDaCM (which does not quite correspond with the latest optaplanner documentation I realise)
FormGroup extracts
#Entity
#PlanningEntity
public class FormGroup {
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
Pupil extracts
#Entity
#PlanningEntity
public class Pupil
#PlanningVariable(valueRangeProviderRefs = "formGroupRange")
#ManyToOne
private FormGroup formGroup;
Config extracts
<solutionClass>org.acme.optaplanner.domain.Plan</solutionClass>
<entityClass>org.acme.optaplanner.domain.Pupil</entityClass>
<entityClass>org.acme.optaplanner.domain.FormGroup</entityClass>
I believe I've followed the steps in the videoexactly (don't we all say that) but at solve time I get hundreds of errors... Repetitions of the following
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
Any hint gratefully received...
The InverseRelationShadowVariable creates a bi-directional relationship between the genuine planning entity (Pupil) and the planning value (FormGroup). This may become problematic if you re-use your planning domain classes for other purposes, such as ORM persistence or serialization.
In this case, Jackson is unable to serialize Pupil, because it references a FormGroup, which has a List containing a reference back to that Pupil. See? An endless loop.
Solve this issue by adding the #JsonIgnore annotation on top of your inverse relation property and breaking that loop for Jackson:
#Entity
#PlanningEntity
public class FormGroup {
#JsonIgnore // THIS IS THE FIX
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
...

Fill JPA fill child array (one to many) with only certain children

I need to fill a Entity with the children entities and it works well when I need all children but now I only want to have a few ones.
For example, I have Owner 1-----n Pet
If I ownerRepositoty.findAll(); I get all owners with each one having an array of all he's pets. But let's say I want to get all the owners with the pet array having only the ones which name starts with N.
Example of the parent entity:
public class Owner {
#OneToMany(mappedBy="Owner")
private Set<Pet> pets; //This gets filled with all pets
//getters & setters
}
And the child entity:
public class Pet {
#ManyToOne
#JoinColumn(name="owner_id", nullable=false)
private Owner owner;
//getters & setters
}
I tried with a JOIN FETCH as explained here in the repository query but It just made a normal sql JOIN which is not what I'm looking for.
Any ideas?
I found the way to do this:
You can use the #Where annotation to filter the childs of a one to many or a many to many relationship.
public class Owner {
#OneToMany(mappedBy="Owner")
#Where(clause = "name like 'N%'")
private Set<Pet> pets; //This gets filled with pets with a name starting with N
//getters & setters
}

NHibernate 4 child collection saved, but not re-loaded

I've got an NHibernate 4 project with several collection relationships. I'm unit-testing the object model, exercising all the collections. Most work fine, but in one case, the child collection is cascade-saved properly, but on loading the parent entity and examining the collection property, the child collection is empty.
Here are the abbreviated classes. GatewayUser is the parent object, and it has a collection of Student. The collection has a private backing property, and AddStudent/RemoveStudent methods.
Further complications: I'm using the NHibernate.AspNet.Identity library for OAuth2 user management, and GatewayUser inherits from IdentityUser. That in turn inherits from the the library's internal base entity class, which is different from my project's own base class.
public class GatewayUser : IdentityUser
{
public GatewayUser()
{
}
public virtual string FirstName { get; set; }
// ...More value properties and OAuth stuff omitted
// students associated with this user
private IList<Student> _students = new List<Student>();
public virtual IList<Student> Students
{
get { return new ReadOnlyCollection<Student>(_students); }
}
public virtual GatewayUser AddStudent(Student s)
{
if (_students.Contains(s))
return this;
s.GatewayUser = this;
_students.Add(s);
return this;
}
public virtual GatewayUser RemoveStudent(Student s)
{
if (_students.Contains(s))
{
_students.Remove(s);
}
return this;
}
Student is more ordinary; it inherits from my own BaseEntity class, has many value properties, and its own child collection of ProgramApplication items. Interestingly, this collection saves and loads fine; it's got the same structure (private backer, etc.) as the failing collection in GatewayUser.
The mapping is complicated, because the library internally maps its classes with NHiberante.Mapping.ByCode.Conformist classes (which I have no prior experience with).
I'm mapping my own classes with NHibernate automapping, because I have so many classes and properties to map. To get it all working, I copied the library's mapping helper class, and modified it a bit to add my base entity classes to it's list called baseEntityToIgnore. I also had to create a conformist mapping for GatewayUser, since it has a different base entity type, and my automapping wouldn't pick it up.
The unit test looks like this:
[Test]
public void GatewayUserCascadesStudents()
{
var u = new GatewayUser() { FirstName = "Mama", LastName = "Bear", UserName = "somebody#example.com" };
var s1 = new Student() { FirstName = "First", LastName = "Student" };
var s2 = new Student() { FirstName = "Second", LastName = "Student" };
u.AddStudent(s1).AddStudent(s2);
using (var s = NewSession())
using (var tx = s.BeginTransaction())
{
s.Save(u);
tx.Commit();
}
GatewayUser fetched = null;
int count = 0;
using (var s = NewSession())
{
fetched = s.Get<GatewayUser>(u.Id);
count = fetched.Students.Count;
}
Assert.AreEqual(2, count);
}
The generated SQL inserts into both AspNetUsers and GatewayUser (reflecting the inheritance relationship), and inserts two records into Student. All good. On fetching, the SELECT joins the two user tables, and I get a GatewayUser object, but accessing the Students collection does not trigger a SELECT on the Student table. But if I change the mapping to Lazy(CollectionLazy.NoLazy), the SQL to select eagerly load Students appears in the log, but the collection is not populated. If I switch the database from SQLite to Sql Server, I see the student records in the table. The generated SQL (when NoLazy is applied) will fetch them. So on the database end, things look fine.
I have to think my Frankenstein mapping situation is to blame. I'm mixing the library's conformist mapping with Fluent mapping, and there are two different base entity classes. However, the generated schema looks correct, and the save cascades correctly, so I don't know if that's the issue.
Found my own answer. My mapping of the parent class's list was like this:
public class GatewayUserMap : JoinedSubclassMapping
{
public GatewayUserMap()
{
Key(g => g.Column("Id"));
Property(c => c.FirstName, m => m.Length(50));
// ... more properties
List(gu => gu.Students, map =>
{
map.Key(c => c.Column("GatewayUser_Id"));
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
map.Index(li => li.Column("ListIndex"));
map.Access(Accessor.Field | Accessor.NoSetter);
}
);
}
}
I have a private backing field for the collection. Removing Accessor.NoSetter from the collection mapping fixed it. In fact, it still worked without Accessor.Field -- I guess the mapper does a good job of looking around for one, and using it if found. Changing the name of the private backer from "_students" to "funnyName" prevented the mapper from finding it.

How to prevent hibernate to save same object

I have tables named Country, City and People. What can I prevent hibernate to save same people object? Here is my classes and the problem is when I try to save a country object, Hibernate tries to save or update same people objects as expected.(City's people object and Country's people object has same PK)
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session
Classes;
class Country{
.....
#JoinColumn(.....)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private City city;
#JoinColumn(.....)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private People people;
}
class City{
....
#JoinColumn(.....)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private People people;
}
Here is my save method;
public void save(){
....
getCurrentSession().saveOrUpdate(customer); //Hibernate session
}
2 solutions here :
Use merge instead of save
or remove save cascade annotation.
Using both save and save cascade basically tells Hibernate "please persist this new object along with all his relationships which are also new". You don't want that.

do i need to set the value of the ONE side when adding an entity to a one-to-many collection?

This seems like a super obvious question, but I haven't been able to find a clear answer.
I'm using FluentNHibernate automapping with the DefaultCascade.All() convention.
Entities are saving, but in one-to-many relationships I'm having to provide the one side on my many side even though i'm saving by adding to a collection.
An example will probably explain this better:
Lets say I've got these two classes:
public class Owner
{
public virtual IList<PetDog> Dogs { get; set; }
}
public class PetDog
{
public virtual Owner Owner { get; set; }
}
In order to add a new PetDog to the Dogs collection on an owner, I feel like I should be able to call
Owner.Dogs.Add(new PetDog());
and dispose my ISession. However, I'm just getting the Owner saving and thats it.
If I explicitly set
Owner.Dogs.Add(new PetDog { Owner = Owner })
It works.
Is there a way to avoid explicitly providing that value?
This can be done by marking the Owner class as the owner of the relationship by setting inverse = false in the Owner mapping, i.e.
HasMany(x => x.Dogs)
.Not.Inverse()
.Cascade.AllDeleteOrphan();
Then the owner_id foreign key in the PetDog table will be populated on commit, i.e.
using (var transaction = session.BeginTransaction())
{
var order = new Owner() {Dogs = new List<PetDog>()};
order.Dogs.Add(new PetDog() );
order.Dogs.Add(new PetDog() );
session.Save(order);
transaction.Commit();
}
Alternatively, instead of using transaction you can call session.Flush() instead which will cause the new data to be inserted into the DB, i.e.
var order = new Owner() {Dogs = new List<PetDog>()};
order.Dogs.Add(new PetDog() );
order.Dogs.Add(new PetDog() );
session.Save(order);
session.Flush(); // data persisted to DBMS here.
Please note that the use of session.Flush() is not recommended best practice. It is recommended that explicit transactions are used. Please see this blog post by Ayende Rahien for further details.