Lombok - Hibernate #OneToOne in same class - StackOverFlowError - java

I'm working with 2 tables: Person and City. I have a #ManyToOne relationship which worked fine. (many persons can belong to one city).
Then I needed to create a parent-child relationship. (one person can be parent of another person). The code:
#Entity
#Data
#Table(name="PERSON")
public class Person {
#Id
#Column(name="person_id")
private int id;
#OneToOne
#JoinColumn(name = "parent_id")
private Person parentPerson;
#OneToOne(mappedBy = "parentPerson")
private Person childPerson;
public Person() {
}
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "city_id", nullable = false)
private City city;
}
#Entity
#Data
#Table(name = "city")
public class City {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "city_id")
private Integer cityId;
[...]
}
This code compiles, I let hibernate to create the table and I can see the parent_id column.
However, after I inserted a few rows and ran myRepository.findAll(), I'm getting the following:
java.lang.StackOverflowError
at java.base/java.lang.Integer.toString(Integer.java:438)
at java.base/java.lang.Integer.toString(Integer.java:1165)
at java.base/java.lang.String.valueOf(String.java:2951)
at package.City.toString(City.java:15)
at java.base/java.lang.String.valueOf(String.java:2951)
at package.Person.toString(Person.java:16)
at java.base/java.lang.String.valueOf(String.java:2951)
at package.Person.toString(Person.java:16)
at java.base/java.lang.String.valueOf(String.java:2951)
[...]
at java.base/java.lang.String.valueOf(String.java:2951)
at package.Person.toString(Person.java:16)
at java.base/java.lang.String.valueOf(String.java:2951)
Even inspecting the result in debug, it was returning the StackOverFlow error, but the child-parent mappings were done correctly. Even though from parent I could inspect/expand the child, then expand the parent and so on...
The example with #OneToOne in the same class is taken from here.
Any ideas on how I can solve the issue?

Thanks #Daniel Wosch and #dan1st for suggestions.
Indeed, the generated toString from Lombok's #Data was the problem.
Solution was to use #Getter, #Setter, #EqualsAndHashCode from Lombok and my own toString which didnt reference both parent and child. Just one of them and it's ok.

Another option would be adding the following annotation right after the Data annotation: #EqualsAndHashCode(callSuper = false,exclude="your relational properties")
Just replace the value of exclude with your field.

Related

OneToOne relationship, keep only foreign key

I am trying to establish a OneToOne relationship between two entities (PartnerDetails and JWTData. How ever, I only want to store the primary key of PartnerDetails entity in JWTData, not the whole object, like this.
#Entity
#Data
#Table(name = "partner_details")
public class PartnerDetails {
#Id
#Column(name = "partner_id")
private String partnerId;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "partnerId")
private JWTData jwtData;
}
#Entity
#Data
#Table(name = "jwt_data")
#NoArgsConstructor
public class JWTData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(targetEntity = PartnerDetails.class)
#JoinColumn(name = "partner_id", foreignKey = #ForeignKey(name = "fk_jwt_partnerdetails_partnerid"))
private String partnerId;
#NotBlank
private String secret;
}
But after fetching the JWTData using repository, Hibernate cannot convert the String to a PartnerDetails. Can this be done using any other way?
If you just add PartnerDetails to JWTData then JPA will know to use only the id. JPA is an Object Oriented framework so you should reference objects unless you specifically want a field. JPA handles the details for you. Note that in this configuration JWTData in the "owning" entity because of the mappedBy annotation, therefore only setting the partnerDetails field in a JWTData instance will persist the relationship to the database. The jwtData field in PartnerDetails is for query results only and makes for a Bidirectional instead of a Unidirectional mapping. Also, because of this, having a CascadeType setting generally only makes sense on the owning entity since it is the one handling the database updates and deletes.
When playing around with JPA be sure to turn on the SQL output so that you know what is actually happening.
#Entity
#Data
#Table(name = "partner_details")
public class PartnerDetails {
#Id
#Column(name = "partner_id")
private String partnerId;
#OneToOne(mappedBy = "partnerDetails")
private JWTData jwtData;
#Entity
#Data
#Table(name = "jwt_data")
#NoArgsConstructor
public class JWTData {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// even though it looks like the entire class it's only saving the id to the database.
#OneToOne
private PartnerDetails partnerDetails;

JPA OneToMany/ManyToOne relationship not working - What am I missing?

I know this has been asked a lot of times before, I know it because I've searched for every related question to my problem to try to find a solution, however, none of the proposed solutions are working for me and I'm pretty sure that I have to be missing something.
Person Class:
#Entity
#Table(name = "person", schema = "test")
public class PersonEntity {
#Id
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "age")
private int age;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "personid")
private List<ProjectEntity> projects;
}
Project Class:
#Entity
#Table(name = "project", schema = "test")
public class ProjectEntity {
#Id
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "budget")
private int budget;
#JoinColumn(name = "personid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.ALL)
private PersonEntity personid;
}
I have a bidirectional OneToMany/ManyToOne relationship, I have tried changing
the cascade type to PERSIST, adding 'optional=false' and way more things but nothing seems to work.
I read that I have to 'join' manually the entities before the persist, and that's what I did:
em = JPAUtility.getEntityManager();
em.getTransaction().begin();
PersonEntity personTest = new PersonEntity();
personTest.setName("Test");
personTest.setAge(23);
ProjectEntity projectTest = new ProjectEntity();
projectTest.setName("hello");
projectTest.setBudget(232);
projectTest.setPersonid(personTest);
List<ProjectEntity> projects = new ArrayList<ProjectEntity>();
projects.add(projectTest);
personTest.setProjects(projects);
em.persist(personTest);
em.getTransaction().commit();
em.close();
return personTest;
But I still get this:
Caused by:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
Cannot add or update a child row: a foreign key constraint fails
(`test`.`project`, CONSTRAINT `FK_Personid` FOREIGN KEY (`personid`) REFERENCES
`person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE)
I honestly don't know what I'm missing, if anyone has any suggestion I'll be more than happy to try it.
Thank you so much!
SOLUTION
I managed to solve the problem thanks to all the suggestions, basically, I was missing the #GeneratedValue(strategy=GenerationType.AUTO) annotation which I removed because I thought it didn't work but, it wasn't working because I was missing a property on the persistence.xml:
<property name="hibernate.id.new_generator_mappings" value="false" />
I found this info here
You also need a method to add the relationship in the objects:
public void addToProjects(ProjectEntity project){
project.setPersonid(this);
this.projects.add(project);
}
To make this work you need to initialize the List when you declare the variable:
private List<ProjectEntity> projects = new ArrayList<ProjectEntity>();
And that's it!
This is the final working code in case anyone can find it useful :):
Person Class:
#Entity
#Table(name = "person", schema = "test")
public class PersonEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "age")
private int age;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "personid")
private List<ProjectEntity> projects = new ArrayList<ProjectEntity>();
public void addToProjects(ProjectEntity project) {
project.setPersonid(this);
this.projects.add(project);
}
}
Project Class:
#Entity
#Table(name = "project", schema = "test")
public class ProjectEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private long id;
#Basic
#Column(name = "name")
private String name;
#Basic
#Column(name = "budget")
private int budget;
#JoinColumn(name = "personid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.PERSIST)
private PersonEntity personid;
public void setPersonid(PersonEntity personid) {
this.personid = personid;
}
}
Make sure you add the Children to their Parent and vice-versa (addToProjects())
em = JPAUtility.getEntityManager();
em.getTransaction().begin();
PersonEntity personTest = new PersonEntity();
personTest.setName("Butters");
personTest.setAge(10);
ProjectEntity projectTest = new ProjectEntity();
projectTest.setName("Hanks");
projectTest.setBudget(10000);
ProjectEntity projectTest2 = new ProjectEntity();
projectTest2.setName("X");
projectTest2.setBudget(100);
personTest.addToProjects(projectTest);
personTest.addToProjects(projectTest2);
em.persist(personTest);
em.getTransaction().commit();
em.close();
Hope it helps! Thank you so much.
The main thing that you will want to watch out for is to define the owning side of the relation correctly. As far as I remember, my takeaway from the (sometimes difficult to understand) official documentation was that the owning side is pretty much the one that will by default trigger cascades and transparent deletions.
For example, in the above, you have defined the owning side as ProjectEntity, so the most important step for cascaded persistence to work is to add the project to PersonEntity.projects.
You will then want to call persist on the owning side of the relation, i.e.
em.persist(projectTest);
If this doesn't help, I would suggest that you enable SQL logging in your JPA provider to find out what statements it is trying to execute, and especially in what order these entities are being inserted.
Also try, as per existing comments, to persist person first.
If you do this, I believe the correct way is to add the persisted entity to the relationship, i.e:
PersonEntity persistedPerson = em.persist(personTest);
projectTest.setPersonId(persistedPerson);
em.persist(projectTest);
A couple of leads I can think of, because I crossed more than once this kind of problems:
Unless you want a cascade operation from Project to update your Person, you should remove
#ManyToOne(cascade = CascadeType.ALL)
from your personid attribute
Try updating your projects collection instead of creating a new one, because if it's already managed by Hibernate (which doesn't need to be , the persist/merge operation will be executed on the old one and the new one.
Person Class:
private List<ProjectEntity> projects = new ArrayList<>();
your code :
personTest.getProjects().addAll(projects);
I usually prefer merge instead of persist, because I find it more 'natural', and sometimes, the output is clearly not the same.
I had the same problem. A #ManyToOne that was not working for no reason and 2 classes. I added #GeneratedValue(strategy=GenerationType.AUTO), but it didn't fix my problem.
I also tried to rename the classes, clean, close project, restart, etc., but none worked. At the end, I deleted the files (made a copy before) and recreated them from new and that fixed my problem. I was on Eclipse 4.8, Spring 2.5, Groovy 2.5, Java 1.8
UPDATE:
Not really sure what was the problem, anyway (for groovy) check your save method: myUserRepo(new MyUser("username")), check as well your xxxxRepo< MyUser, Integer> and also check that your file is .groovy (last one shouldn't be a problem)
Other UPDATE:
If you're creating a relational between 2 tables and use the save result, be sure to use #Transactional on a Service and link the relation field, for example:
#Transactional
UserAccount save(UserAccount userAccount) {
User user = userRepo.save(new User(userAccount))
UserAccount.setUser(user)
userAccountRepo.save(userAccount)
}

Spring JPA LAZY load OneToOne relation won't load

i have a LAZY OneToOne relationship,
and i want to load it per use (The same model have more OneToOne relations and i want to do less queries to the Database.)
Looking at the native db queries in the log file, i can see the when i am not trying to access to user.city the SELECT FROM City... statement is not printed.
but when accessing user.city i can see the SQL Statement running in the log, i am getting the class instance. but all filled inside the City entity are null, see below more info:
this code:
System.out.println(user.city);
System.out.println(user.city.location);
will print
Hibernate:
select
city0_.id as id1_3_0_,
city0_.accentName as accentNa2_3_0_,
city0_.location as location3_3_0_,
city0_.name as name4_3_0_,
city0_.state_id as state_id5_3_0_
from
City city0_
where
city0_.id=?
com.dateup.models.City#1ed01095
null
Thous are my models :
#Entity
#Table(
indexes={#Index(name = "name", columnList="name")}
)
public class City {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
public State state;
public String name;
public String accentName;
#Column(name = "location", columnDefinition = "POINT")
public Point location;
}
#Entity
#Table(
name="User"
)
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorFormula("'User'")
#JsonAutoDetect
public abstract class BaseUser {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long id;
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#OneToOne(fetch = FetchType.LAZY)
public City city;
...
}
Just to mention that when testing this with #OneToOne(fetch = FetchType.EAGER) all is working fine..
thank you very much for the help.
I wouldn't do one-to-one.
... in city class:
#ManyToOne state; (I usually do EAGER for one side, lazy for other; (FetchType.EAGER, CascadeType.ALL, mappedBy='city'))
... in state class:
#OneToMany city; (a state has many cities).
Are you sure you have an INSERT statement somewhere in your logs, which adds the correct city with the correct id ?
It's hard to know without seeing all the code.

Annotation to join columns with OR condition instead of AND condition

I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.

Is Hibernate #Cascade(CascadeType.DELETE_ORPHAN) correct when it's applied to #ManyToOne?

Let's imagine the scenario: Entity Company and Entity Address has one-to-many bidirectional relationship. So Entity Address will look like:
#Entity
#Table(name = "address")
public class AddressHbm{
#Id
#GeneratedValue(generator = "id-generator")
#Column(name="address_id")
private long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name="company_id")
private Company company = null;
#Column(name="address_name")
private String name;
// other properties and methods
}
I'm going to migrate these codes to Hibernate 4.3 where CascadeType.DELETE_ORPHAN is deprecated. When I am trying to replace CascadeType.DELETE_ORPHAN with orphanRemoval = true, it seems that orphanRemoval = true doesn't even exist in #ManyToOne.
So my question is:
Does AddressHbm use #Cascade(CascadeType.DELETE_ORPHAN) in #ManayToOne incorrectly?
If #Cascade(CascadeType.DELETE_ORPHAN) is misused here, is it valid to just remove it?

Categories