#PrimaryKeyJoinColumn not picking the shared key - java

I got the #PrimaryKeyJoinColumn working before , now I'm trying with spring boot and I can't figure what I'm missing , it's very weird as it seems I have done everything right :
Person class :
#Table(name = "PERSON")
#Entity
#Getter
#Setter
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.PERSIST)
#PrimaryKeyJoinColumn
private Department department;
}
Department class :
#Table(name = "DEPARTMENT")
#Entity
#Getter
#Setter
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#OneToOne(mappedBy = "department")
private Person person;
}
The service class :
#Transactional
public void addPerson() {
Person person = new Person();
person.setName("person 1");
Department department = new Department();
department.setName("test");
department.setPerson(person);
person.setDepartment(department);
personRepository.save(person);
}
That's what I get in the DB :
Person table :
ID Name
13 person 1
Department table :
ID Name
18 test
Desired output : both ID should be identical ( Person ID should be 18 not 13)
any idea ?
Update:
hibernate always tries to insert Person without an ID , so if I remove auto increment from Person ID and tries to insert the person with existing department I get :
Hibernate: insert into person (name) values (?)
Field 'id' doesn't have a default value
Update 2:
it seems the #PrimaryKeyJoinColumn won't handle the ID generation to be as the department class, so I need to use generator. I wonder because the same annotation works in Inheritance joined without doing anything on the ID of subclass. So I'm expecting an answer explaining why ID generation works in Inheritance joined , while OneToOne needs a generator

Although it is at least bad naming to make whole Department for just ONE Person (Department usually groups many people), I assume you really are looking for OneToOne relation with shared PRIMARY KEY.
The best solution (optimal memory, speed and maintenance) is to use #MapsId:
#Getter
#Setter
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
private Department department;
}
#Getter
#Setter
#Entity
public class Department {
#Id // Note no generation
private Long id;
private String name;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
#JoinColumn(name = "id", foreignKey = #ForeignKey(name = "department_belongs_to_person"))
private Person person;
}
Here Department is owning relation (owning usually means that it would have column with FK in database, but here they just share primary key) and owns FK that binds it's PK to one generated by Person.
Note that relation is OneToOne, bidirectional, with shared PK as FK. This is considered best practice, but has one small drawback of having to write one getter. :)
Sources: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/
Also - I highly recommend spending few days reading posts on this site and even implement few of them before designing anything bigger than few tables. :)
EDIT
I might be wrong here (I am sure now - look comments), but to my knowledge keeping IDs equal is not something JPA specifies. What it can specify is basically:
"Hey, you guys (Person, Department) are brothers (OneToOne) and both know it (bidirectional with mappedBy="person" in Person entity). You will be older bro that will generate IDs (Person has #GeneratedValue), and you will be youger that should have same. You will use those fields (IDs, one generated, second not) to connect (#PrimaryKeyJoinColumn)."
What I am trying to say is that just because you say "this connects you", it doesn't mean those are synchronized - YOU have to ensure it.
Now as to how to ensure it - #MapsId is known to be best.
If you are looking for different approaches there is also manually setting ID to be same as other with #setDepartment(Department) where you would set Department's ID to be same as calling Person (but this only works if said Person already has an ID generated, which basically breaks the idea).
The other known to me is using foreign generator strategy.
Person:
#Id
#GeneratedValue
private long id;
#OneToOne(mappedBy="person")
private Department department;
Department:
#Id
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="person"))
private long id;
#OneToOne
#PrimaryKeyJoinColumn
private Person person;
Now - this uses hibernate-specific annotations and is not pure JPA.

Related

Hibernate bidirectional #OneToOne always EAGER on child side

I have two entities: Person and Passport. I map it as bidirectional.
Passport (parent):
#Entity
#Table(name = "passports")
public class Passport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer serial;
private Integer number;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", referencedColumnName = "id")
private Person person;
Person (child):
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
private Passport passport;
Both relations fetch types are LAZY.
Now I'm trying to find only child:
Person p = entityManager.find(Person.class, 1);
System.out.println(p.getName());
Hibernate generates two SELECT statements instead of one (must select only person).
Hibernate: select p1_0.id,p1_0.name from people p1_0 where p1_0.id=?
Hibernate: select p1_0.id,p1_0.number,p1_0.person_id,p1_0.serial from passports p1_0 where p1_0.person_id=?
I expect only one select, but get two.
Why doesnt LAZY work?
What am I doing wrong?
For all other relation types, FetchType.LAZY will work as expected, but for #OneToOne it is different.
It would work if you change your Person entity such that you don't have relation to Passport, and in Passport have:
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Person person;
Then if you are using Spring JPA and you want to find a Passport for a Person, you can do passportRepository.findByPerson(person).
Or if you are using entityManager, you can find it as:
entityManager.find(Passport.class, person.getId());
For more explanation, please read https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/.

Hibernate updating child entries in one to many Mapping

I have a
company table
department table
employee table.
I am using hibernate to persist data in the database.
1. One to Many Relationship between Company and Department .
A company can have multiple departments and a department can have multiple employees.
I have done corresponding one to many mapping of entities as mentioned in below code. Request to update these entities comes from UI in the JSON format.
I have provided company Id, department Id, and employee Id in the request.
Now Suppose If for a particular company , there is one department in the database with dept_id 3 . . In the Json request, I get a request to update that particular company with one more department. So after updation, previous entry should remain as it is, i.e department with ID 3 ,should remain untouched and new entry should be added with some department Id, say 4,.
Now that company would have two departments one with Id 3 and other with id 4.
How could this be achieved??..Also department entries , which are not there in the request, should be deleted from the database... Same goes for the relation between employee and department,.request may ask to add new employee for a particular department,keeping the existing one.
Please help me with this, what configuration/approach has to be done in my code, to achieve this.
Here is the code for these three tables:
#Entity
#Table(name = "COMPANY")
#Getter
#Setter
public class Company implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "company_id")
private long companyId;
#Column(name = "company_region")
private String companayRegion;
#Column(name = "company_code")
private String companyCode;
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<department> departments;
public Company() {
}
}
#Entity
#Table(name = "Department")
#Getter
#Setter
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "dept_id")
private long departmentID;
#Column(name = "dep_code")
private String departmentCode;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id")
private Company company;
#OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
private List<Employee> employees;
}
#Entity
#Table(name = "Employee")
#Getter
#Setter
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "employee_id")
private long employeeId;
#Column(name = "emp_code")
private String empCode;
#Column(name = "emp_name")
private String empname;
#Column(name = "employee_city")
private String employeeCity;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "dept_id")
private Department department;
}
the mappings seem correct, what you need is to use merge to obtain an Hibernate-managed entity and copy the fields from the detached instance (coming from the REST api):
Company c = (Company) session.merge(companyFromDto);
Merge should take care to create a new Company, or update an existing one (depending if it is already in the DB) as well as cascade the associations.
Make sure your JSON contains companyId / departmentID / employeeId
Load the company by companyId
INSERT all department entries where departmentID == 0, and fetch the generated ID (Hibernate will update the POJO, or return a new one with the ID set)
UPDATE all department entries where departmentID > 0, and remember the used departmentID
Iterate over company.getDepartments() and iterator.remove() entries with departmentID not contained in the collection built from (3) and (4)
By persisting the company Hibernate will detect which departments were removed, and delete those
Do basically the same for the department -> employee relationship

How do I map all these tables with Hibernate?

I have a table PATIENT which has some fields. There's also a CONTACT table that has a field called 'patientId' that needs to store PATIENT's ID (which is autogenerated), and a PATIENT_CONTACT table that only relates the two tables.
Now, here comes the tricky part. There are three other tables: CONTACT_ADDRESS, CONTACT_PHONE, CONTACT_EMAIL. A row in CONTACT will have the same ID as one (and only one) of CONTACT_ADDRESS, CONTACT_PHONE and CONTACT EMAIL. How do I get this all to work?
I have tried so many approaches, this is what I have right now:
#Entity
#Table(name = "patient", schema = "patient")
public class PatientEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
//... more fields
#OneToOne
private ContactEmailEntity contactEmailEntity;
#OneToOne
private ContactAddressEntity contactAddressEntity;
#OneToOne
private ContactPhoneEntity contactPhoneEntity;
}
The three CONTACT_* classes are similar and they look like this:
#Table(name = "contact_address", schema = "patient")
public class ContactAddressEntity {
#Id
#Column(name = "id")
private Long id;
// ... more fields
#OneToOne(cascade = {CascadeType.ALL})
#MapsId
private ContactEntity contact;
}
And my CONTACT class looks like this:
#Table(name = "contacto", schema = "paciente")
public class ContactEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
//... more fields
Can you see things that don't look right or could be done better? I get all sorts of errors with every approach. My latest one is:
ERROR: column patientent0_.contact_address_entity_contact_id does not exist
when trying to do a simple patient find. Please, any help is appreciated!

Strange behaviour of spring data jpa

I'm new to JPA and I have a case where in my opinion JoinColumn behaves different and I want to know why.
UserEntites should join authorites.
Organziations should join OrganizationSettings.
I have two different approaches and both work.
Case 1
UserEntity :
#Entity
#Table(name = "users")
#Inheritance(strategy = InheritanceType.JOINED)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private List<UserAuthority> authorities;
}
UserAuthoritiesEntity
#Entity(name = "authorities")
#Table(name = "authorities")
public class UserAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String authority;
}
Here in my opinion the JoinColumn name references to UserAuthority.userId - and it works as expected.
Case 2
See my two other classes:
OrganizationEntity:
#Entity
#Table(name="organization")
public class OrganizationEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#NotNull
private String url;
#NotNull
private String name;
#OneToOne (cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name="id",updatable = false)
private OrganizationSettingsEntity settings;
}
OrganizationSettings:
#Entity
#Table(name = "organization_settings")
public class OrganizationSettingsEntity {
#Id
private Long organizationId;
}
As you can see here -> Organization is Joining OrganizationSettings with the name id - which works. But in OrganizationSettings there is no id - just organizationId. This works - but makes me wonder.
Why does the second one also work? Shouldn't it be #JoinColumn(name="organizationId") ?
Spring is nothing to do with it. JPA is a standard API.
1-N case : you will create a FK column in the authorities table with name userId (linking back to the users table). You seem to also want to REUSE that same column for this userId field in the element ... this will cause you problems sooner or later since reusing columns without marking the userId field as insertable=false, updatable=false will mean that both may try to update it. Either get rid of the userId field in the element, or convert the field to be of type UserEntity (and have it as a bidirectional relation, using mappedBy on the 1-N owner field), or mark the userId field with those attributes mentioned earlier.
1-1 case : you will create a FK column in the organization table with name id (linking across to the organization_settings table). Sadly this is the same column as the PK of that table is going to use, so again you are reusing the column for 2 distinct purposes, and hell will result. Change the column of the relation FK to something distinct - the FK is in the organization table, not the other side.

How can I mark a foreign key constraint using Hibernate annotations?

I am trying to use Hibernate annotation for writing a model class for my database tables.
I have two tables, each having a primary key User and Question.
#Entity
#Table(name="USER")
public class User
{
#Id
#Column(name="user_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="username")
private String username;
// Getter and setter
}
Question Table.
#Entity
#Table(name="QUESTION")
public class Questions extends BaseEntity{
#Id
#Column(name="question_id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="question_text")
private String question_text;
// Getter and setter
}
And I have one more table, UserAnswer, which has userId and questionId as foreign keys from the above two tables.
But I am unable to find how I can reference these constraints in the UserAnswer table.
#Entity
#Table(name="UserAnswer ")
public class UserAnswer
{
#Column(name="user_id")
private User user;
//#ManyToMany
#Column(name="question_id")
private Questions questions ;
#Column(name="response")
private String response;
// Getter and setter
}
How can I achieve this?
#Column is not the appropriate annotation. You don't want to store a whole User or Question in a column. You want to create an association between the entities. Start by renaming Questions to Question, since an instance represents a single question, and not several ones. Then create the association:
#Entity
#Table(name = "UserAnswer")
public class UserAnswer {
// this entity needs an ID:
#Id
#Column(name="useranswer_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "question_id")
private Question question;
#Column(name = "response")
private String response;
//getter and setter
}
The Hibernate documentation explains that. Read it. And also read the javadoc of the annotations.
There are many answers and all are correct as well. But unfortunately none of them have a clear explanation.
The following works for a non-primary key mapping as well.
Let's say we have parent table A with column 1
and another table, B, with column 2 which references column 1:
#ManyToOne
#JoinColumn(name = "TableBColumn", referencedColumnName = "TableAColumn")
private TableA session_UserName;
#ManyToOne
#JoinColumn(name = "bok_aut_id", referencedColumnName = "aut_id")
private Author bok_aut_id;
#JoinColumn(name="reference_column_name") annotation can be used above that property or field of class that is being referenced from some other entity.

Categories

Resources