I'm having a problem understanding/figuring out a problem I'm having with OpenJPA and was hoping someone here can help out.
Basically, I have the following two entities:
#Entity
#Table(name = "images")
public class Image {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "path")
private String path;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "image_id")
#ForeignKey(name = "fk_person-image")
private Image image;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
}
As you can see, a Person can have an Image. This is an optional column and supports a null value. When a call from the REST Web Service comes in to update a Person changing the Image field, if the Image is being set to null the database isn't being updated, however if the Image is being changed to a different existing Image, it is saving.
I suspect this has something to do with the fact that the Person is coming in from the outside (detached), since if I obtain the Person from the DAO Service, set the Image to null and call the update/merge method using that instance it is updating the DB.
I was able to get it to work by adding the following property:
<property name="openjpa.DetachState" value="fetch-groups(DetachedStateField=false)"/>
However this seems to have some drawbacks as other relationships aren't being populated anymore when fetching from the DB. (Rooms in a Building when retrieving the Building for example)
Has anyone encountered this before? Any ideas on how this should be handled? I'm tempted to test out Hibernate/Eclipselink to see if/how they handle this.
Any help would be appreciated, I've been racking my brain around this for the past 2 days.
Related
Project is based on JPA persistance with two Entities (Deaprtment and Employee)
Department(OneToMany) and Employee(ManyToOne)
Whenever I send a request via API there's a StackOverFlow error. So far I back Traced the main cause which is the stack is full is indefinite recursion. Could someone explain why this happened ususally it shouldn't have confused by bidirectioanl relationship of entities.
package com.springjpacrud01.model;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
List<Employee> employees;
public Department() { }
public Department(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
import com.fasterxml.jackson.annotation.JsonBackReference;
import javax.persistence.*;
#Entity
#Table(name = "employees")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "position")
private String position;
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
public Employee(Long id, String name, String position) {
this.id = id;
this.name = name;
this.position = position;
}
public Employee() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
After I just deleted the getters/setters from the Department entity it worked but
it shouldn't have work like that and I want why I cannot do relation pointing to each other entities? It couldn't form JSON response because of the infinite recursion of pointing to each other I guess. How can I solve it effectively in order to retrieve Employees by department ID, thank you)
If someone needs it I've solved this by understanding the deep root of the cause which was #JoinColumn created and addressed by Hibernate to that empty common column which I deleted manually. And when I was requesting the department_id of the employee via the employee repository Hibernate sort of got stuck in an infinite loop of going to the employee repository and from there to the department repository and in the department repository going to the employee repository. To stop that I've mapped the relation differently by making a configuration of the department
#OneToMany(mappedBy = "department", cascade= CascadeType.ALL, orphanRemoval=true) private Set<Employee> employeeHashSet = new HashSet<>();
And
#ManyToOne
#JoinColumn(name = "department_id")
private Department department;
i'm new to Springboot. I'm trying to implement a simple REST api using :
-Springboot, JPA & rest along with hibernate
I have a 2 tables database, Notebook that contains 1 to many notes
I already setup the 2 tables and relationships. I also created a NotebookRepository and NoteRepository to get basic CRUD operations via the springboot rest. The Database connection and relationships are functionning
but i don't know how to add a new note (it has a notebook_id foreign key which msut NOT be NULL) and everytime i tryto post something along these lines
{
"title:"abc",
"text":"whatever",
"notebook":{
"id":2
}
}
i get this error :
Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'notebook_id' cannot be null
#Entity
#Table(name="notebook")
public class NoteBook {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="name")
private String name;
#OneToMany(mappedBy="notebook", cascade=CascadeType.ALL)
List<Note> notes;
public NoteBook() {
}
public NoteBook(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Note> getNotes() {
return notes;
}
public void setNotes(List<Note> notes) {
this.notes = notes;
}
public void addNote(Note note) {
if(notes == null) {
notes = new ArrayList<>();
}
note.setNotebook(this);
notes.add(note);
}
#Entity
#Table(name="note")
public class Note {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="title")
private String title;
#Column(name="text")
private String text;
#ManyToOne(cascade={CascadeType.MERGE, CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name="notebook_id")
private NoteBook notebook;
public Note() {
}
public Note(String title, String text) {
this.title = title;
this.text = text;
}
#RepositoryRestResource(collectionResourceRel = "note", path = "notes")
public interface NoteRepository extends JpaRepository<Note, Integer>{
//No code...
}
#RepositoryRestResource(collectionResourceRel = "notebook", path = "notebooks")
public interface NotebookRepository extends JpaRepository<NoteBook, Integer>{
}
The problem is that the class Note doesn't have a constructor with NoteBook parameter to pass the created NoteBook object to, so the solution is to add this constructor:
public Note(String title, String text, NoteBook noteBook) {
this.title = title;
this.text = text;
this.noteBook = noteBook;
}
and it's enough to send the JSON object as you do, but just be aware of case-sensitivity:
{ "title:"abc", "text":"whatever", "noteBook":{ "id":2 } }
I think you need to add referencedColumnName = "id" for JoinColumn annotation for notebook field in Note class.
Maybe you have problem with IDENTITY generation type. See this problem with null pointer
Need make a relationship between same table. Example: the object "category" have subcategories and the subcategorie have other subcategorie.
In MySQL make a column and point to primary key of same table, but, howto make in JPA?
My code is:
#Entity
#Table(name = "objects")
public class JObject {
private long id;
private String name;
private JObject parentJObject;
private Set<JObject> jObjects;
public JObject(){
}
public JObject(long id){
this.id = id;
}
public JObject(String name){
this.name = name;
}
public JObject(String name, JObject parentJObject){
this.name = name;
this.parentJObject = parentJObject;
}
#Null
#JoinColumn(name="parent_object_id", referencedColumnName="id")
#ManyToOne(cascade=CascadeType.ALL)
public JObject getParentJObject() {
return parentJObject;
}
public void setParentJObject(JObject parentJObject) {
this.parentJObject = parentJObject;
}
#Null
#OneToMany(mappedBy = "parentJObject", cascade = CascadeType.ALL)
public Set<JObject> getJObjects() {
return jObjects;
}
public void setJObjects(Set<JObject> jObjects) {
this.jObjects = jObjects;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#NotNull
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And the making objects:
JObject jObjectcategories = new JObject("Demo 1");
Set categoriesJObjects = new HashSet<JObject>(){{
add(new JObject("Demo 1.1", jObjectcategories));
}};
jObjectcategories.setJObjects(categoriesJObjects);
jObjectDao.save(new HashSet<JObject>() {{
add(jObjectcategories);
}});
But does not works. The log says:
List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='It has to be null', propertyPath=JObjects, rootBeanClass=class a.b.c.models.JObject, messageTemplate='{javax.validation.constraints.Null.message}'} ]
You need to be consistent in where you place your JPA annotations: either all on fields, or all on getters. But not mixed as you're doing.
The OneToOne should be a ManyToOne according to your description, since several objects share the same parent.
And the cascade ALL doesn't make sense: you don't want to delete a parent when a child is deleted.
I am trying to establish unidirectional relationship between two entities called Person and Address,while saving Person(containing collection of Address) getting org.hibernate.TransientObjectException: object references an unsaved transient instance.
When I change cascadeType=all,child objects are getting propagated.But the problem here is with cascadeType=all Hibernate also tries to delete child entities on Deleting Owning entity.I don't want that to happen because in ManyToMany relationship child entity might be being referenced by some other entity.Ideally cascadeType=persist should do the job but unfortunately that give me mentioned exception.
Can somebody help me out how can I save the child objects (Address) with cascadeType=persist.I just wonder why cascadeType=persist is not doing the task of persisting.
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
#LazyCollection(LazyCollectionOption.FALSE)
#ManyToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE})
private Collection<Address> address=new HashSet<Address>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Collection<Address> getAddress() {
return address;
}
public void setAddress(Collection<Address> address) {
this.address = address;
}
}
#Entity(name="Address")
public class Address {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="country")
private String country;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
In case of many- many relationship you always have to make use of JOIN Table(which is a third table).In that case you dont have a problem of Child entities getting deleted on deleting the Owning entity even if you use CASCADE-ALL.
Please see following post below:
Modeling many-to-many Relationship in JPA/Hibernate
I want to copy the entity's UUID, generated at run time to another field.
The entity id is generated via the code described bellow:
package eclipselink.example;
public class UUIDSequence extends Sequence implements SessionCustomizer {
public UUIDSequence() {
super();
}
public UUIDSequence(String name) {
super(name);
}
#Override
public Object getGeneratedValue(Accessor accessor,
AbstractSession writeSession, String seqName) {
return UUID.randomUUID().toString().toUpperCase();
}
...
public void customize(Session session) throws Exception {
UUIDSequence sequence = new UUIDSequence("system-uuid");
session.getLogin().addSequence(sequence);
}
}
Persitence.xml:
property name="eclipselink.session.customizer" value="eclipselink.example.UUIDSequence"
The entity:
public abstract class MyEntity{
private String id;
private String idCopy;
#Id
#Basic(optional = false)
#GeneratedValue(generator="system-uuid")
#XmlElement(name = "ID")
public String getId() {
return id;
}
}
How can I instruct JPA (Eclipse-link) to copy the UUID generated at runtime to idCopy field as well?
I'm not 100% sure this will work (I don't know if EclipseLink calls the setter or assigns the field directly), but give this a try:
public abstract class MyEntity{
private String id;
private String idCopy;
#Id
#Basic(optional = false)
#GeneratedValue(generator="system-uuid")
#XmlElement(name = "ID")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
this.idCopy = id;
// or
// this.setIdCopy(id);
}
}