Is it possible to define a composite primary key in my Entity, but 2 IDs are #OneToOne fields? I'm trying to do it with #PrimaryKeyJoinColumn annotation, unsuccessfully
public class EmployeeEntity {
// my composite ID must be person_id + department_id
#OneToOne
//#PrimaryKeyJoinColumn(name = "person_id")
private PersonEntity person;
#OneToOne
//#PrimaryKeyJoinColumn(name = "department_id")
private DepartmentEntity department;
// other fields
I definitely can do it in a classic way with two numeric fields, but I want to have a OneToOne relationship to be set.
Actually, DB schema should be like that:
create table EMPLOYEE
(
PERSON_ID INT not null,
DEPARTMENT_ID INT not null,
constraint EMPLOYEE_PK
primary key (PERSON_ID, DEPARTMENT_ID),
constraint EMPLOYEE_PERSON_ID_FK
foreign key (PERSON_ID) references PERSON (ID),
);
I believe an embeddable composite key is what you need:
#Entity
public class EmployeeEntity extends BaseEntity {
#Embeddable
static class Pk implements Serializable {
#OneToOne
#JoinColumn(name = "PERSON_ID")
private PersonEntity person;
#OneToOne
#JoinColumn(name = "DEPARTMENT_ID")
private DepartmentEntity department;
}
#Id
private final Pk id;
public EmployeeEntity(DepartmentEntity department, PersonEntity person) {
this.id = new Pk();
this.id.person = person;
this.id.department = department;
}
}
You should create a new class which contains both fields, that class will become your composite key for your EmployeeEntity.
#Embeddable
public class EmployeeId implements Serializable {
private PersonEntity person;
private DepartmentEntity department;
public EmployeeId() {}
public EmployeeId(PersonEntity person, DepartmentEntity department) {
this.person = person;
this.department = department;
}
// equals() and hashCode() methods should also be implemented here
}
public class EmployeeEntity implements Serializable {
#EmbeddedId
private EmployeeId id;
}
Related
My tables look like this:
School
-------
school_id (pk)
...
Student
school_id (pk) (fk)
student_id (pk)
...
So using JPA (Hibernate), I tried something like this.
#Entity
#Table("SCHOOL")
public School {
#Column(name = "SCHOOL_ID")
private String schoolId;
#OneToMany
private List<Student> students;
}
#Entity
#Table("STUDENT")
public Student {
#EmbeddedId
private StudentPK studentPK;
#ManyToOne
#JoinColumn(name = "SCHOOL_ID")
private School school;
}
#Embeddable
public StudentPK implements Serializable {
#Column(name = "SCHOOL_ID")
private String schoolId;
#Column(name = "STUDENT_ID"
private String studentId;
}
When I do this, I frequently get an error that says that the foreign key constraint is being violated on runtime. I suspect that JPA is trying to generate a new foreign key called "SCHOOL_ID" instead of using the existing column from my composite key, but I'm not sure how to force it to use the existing column.
These relationships should be mapped a bit differently:
#Entity
#Table("SCHOOL")
public School {
#Column(name = "SCHOOL_ID")
private String schoolId;
#OneToMany(mappedBy="school")
private List<Student> students;
}
#Entity
#Table("STUDENT")
public Student {
#EmbeddedId
private StudentPK studentPK;
#ManyToOne
#MapsId("schoolId")
private School school;
}
#Embeddable
public StudentPK implements Serializable {
#Column(name = "SCHOOL_ID")
private String schoolId;
#Column(name = "STUDENT_ID"
private String studentId;
}
Note the #OneToMany.mappedBy attribute on School.students and #MapsId annotation on Student.school.
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
I have these tables:
CREATE TABLE company (
id VARCHAR(256) NOT NULL,
tenantId VARCHAR(256) NOT NULL,
fieldName VARCHAR(256) NOT NULL,
PRIMARY KEY (id, tenantId, fieldName)
);
CREATE TABLE employee (
tenantId VARCHAR(256) NOT NULL,
companyFieldName VARCHAR(256) NOT NULL,
companyId VARCHAR(256) NOT NULL,
fieldName VARCHAR(256) NOT NULL,
PRIMARY KEY (tenantId, companyFieldName, companyId, fieldName),
CONSTRAINT fkCompany FOREIGN KEY (tenantId, companyFieldName, companyId) REFERENCES employee (tenantId, fieldName, id)
);
One company can have many employees
A company's primary key is a composite key consisting of 3 fields
An employees' primary key consists of the company's composite key (i.e the foreign key), plus another field specific to company.
Based on this related question:
JPA how to make composite Foreign Key part of composite Primary Key
I have created the following entities:
#Embeddable
public class CompanyIdentity implements Serializable
{
#NotBlank
private String tenantId;
#NotBlank
private String fieldName;
#NotBlank
private String id;
//getters, setters, equals and hashcode ommited
}
#Entity
public class Company implements Serializable
{
#EmbeddedId
private CompanyIdentity companyIdentity;
#OneToMany(mappedBy = "company")
private Set<Employee> employees;
//getters, setters, equals and hashcode ommited
}
#Embeddable
public class EmployeeIdentity implements Serializable
{
#NotNull
private CompanyIdentity companyIdentity;
// This *not* the fieldName in the CompanyIdentity, this is a property
// specific to an employee, it just happens to have the same name
#NotBlank
private String fieldName;
//getters, setters, equals and hashcode ommited
}
public class Employee implements Serializable
{
#EmbeddedId
private EmployeeIdentity employeeIdentity;
#MapsId("companyIdentity")
#JoinColumns({
#JoinColumn(name = "tenantId", referencedColumnName = "tenantId"),
#JoinColumn(name = "companyFieldName", referencedColumnName = "fieldName"),
#JoinColumn(name = "companyId", referencedColumnName = "id")
})
#ManyToOne
#Cascade(value={org.hibernate.annotations.CascadeType.ALL})
private Company company;
//getters, setters, equals and hashcode ommited
}
I want to save a company with a single employee, and have it write a
row to the Company table and Employee table, but whenever I run the
following, I only ever see a row getting written to the Company table
and never the Employee table?
I'm not sure if below is the right approach or not, or maybe the entities
above are not correct?
public interface CompanyRepository extends CrudRepository<Company, String> {}
final Company company = new Company();
final CompanyIdentity companyIdentity = new CompanyIdentity("company-tenant-id", "company-field-name", "company-id");
company.setCompanyIdentity(companyIdentity);
final Employee employee = new Employee();
final EmployeeIdentity employeeIdentity = new EmployeeIdentity();
employeeIdentity.setFieldName("employee-field-name");
employeeIdentity.setCompanyIdentity(companyIdentity);
employee.setEmployeeIdentity(employeeIdentity);
employee.setCompany(company);
final Set<Employee> employees = new HashSet<>();
employees.add(employee);
company.setEmployees(employees);
companyRepository.save(company); //only saves company, not employee?
Many thanks!
Your are saving the company with the company repository but the company doens´t have a cascade annotation.
#OneToMany(mappedBy = "company")
private Set<Employee> employees;
Should be:
#OneToMany(mappedBy = "company")
#Cascade(value={org.hibernate.annotations.CascadeType.ALL})
private Set<Employee> employees;
Lets say I have the following database schema
CREATE TABLE employee(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
hrid VARCHAR (50)
);
CREATE TABLE territory(
id BIGINT PRIMARY KEY,
name varchar (50)
);
CREATE TABLE transcode(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR (10) NOT NULL
);
create table employee_territory_function(
employee_id BIGINT NOT NULL,
territory_id BIGINT NOT NULL,
transcode_id BIGINT NOT NULL,
PRIMARY KEY (employee_id,territory_id),
CONSTRAINT employeeref FOREIGN KEY (employee_id) REFERENCES employee (id),
CONSTRAINT territoryref FOREIGN KEY (territory_id) REFERENCES territory (id) ,
CONSTRAINT transcoderef FOREIGN KEY (transcode_id) REFERENCES transcode (id)
);
Now I have the following JPA mapped entities
Employee entity
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String hrid;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "employee", cascade = CascadeType.ALL)
private Set<EmployeeTerritoryFunction> employeeTerritoryFunctionList = new HashSet<>();
//getters and setters
}
Territory entity:
#Entity
public class Territory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
// getters and setters for all field
}
Transcode Entity:
#Entity
public class Territory implements Serializable {
#Id
private long id;
private String name;
//getters and setters
}
EmployeeTerritoryFunction entity (composite key table)
#Entity
#IdClass(value = EmployeeTerritoryFunctionPK.class)
public class EmployeeTerritoryFunction implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
private Employee employee;
#Id
#ManyToOne
private Territory territory;
#ManyToOne
#JoinColumn(name = "transcode_id", referencedColumnName = "id")
private Transcode transcode;
//getters and setters
}
EmployeeTerritoryFunction pk
public class EmployeeTerritoryFunctionPK implements Serializable {
private static final long serialVersionUID = 1L;
private Long employee;
private Long territory;
//getters and setters, no args constructor, equals and hashcode
}
Below sample insertion
Employee employee = this.employeeRepository.findByHrid("111");
if (employee == null) {
employee = new Employee();
employee.setName("Marie");
employee.setHrid("333");
}
Territory territory = new Territory();
territory.setId(2L);
territory.setName("T2");
Territory territory2 = new Territory();
territory2.setId(3L);
territory2.setName("T3");
Transcode transcode = this.transcodeRepository.findByCode("ASC");
Transcode transcode2 = this.transcodeRepository.findByCode("CC");
EmployeeTerritoryFunction employeeTerritoryFunction1 = new EmployeeTerritoryFunction();
employeeTerritoryFunction1.setTranscode(transcode);
employeeTerritoryFunction1.setTerritory(territory);
employeeTerritoryFunction1.setEmployee(employee);
employee.getEmployeeTerritoryFunctionList().add(employeeTerritoryFunction1);
EmployeeTerritoryFunction employeeTerritoryFunction2 = new EmployeeTerritoryFunction();
employeeTerritoryFunction2.setTranscode(transcode2);
employeeTerritoryFunction2.setTerritory(territory2);
employeeTerritoryFunction2.setEmployee(employee);
employee.getEmployeeTerritoryFunctionList().add(employeeTerritoryFunction2);
employeeRepository.save(employee);
when I run above code with only new objects, I have no issue because hibernate automatically insert the employee, the territory and the list of employee_territory_function but when I first delete all existing territory, employee_territory_function and try to insert using an existing employee, hibernate is not able auto insert or update employee, auto insert in territory, employee_territory_function.
Below the error
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.demo.Employee.employeeTerritoryFunctionList, could not initialize proxy - no Session
When I replace oneToMany fetch type to EAGER, I got below error
Caused by: javax.persistence.EntityNotFoundException: Unable to find com.example.demo.Territory with id 3
It seams that hibernate try to query Territory table but I do not when him to do that because I remove all data on Territory and EmployeeTerritoryFunction table and only employee existing data is not removed.
How to fixe please ?
Fields in both classes EmployeeTerritoryFunction and EmployeeTerritoryFunctionPK should be named exactly same and have same types which you don't have. Try like this:
#Entity
#IdClass(EmployeeTerritoryFunctionPK.class)
public class EmployeeTerritoryFunction implements Serializable {
#Id
#ManyToOne
private Employee employee;
#Id
#ManyToOne
private Territory territory;
}
public class EmployeeTerritoryFunctionPK implements Serializable {
private Employee employee;
private Territory territory;
public int hashCode() { //TODO }
public boolean equals(Object obj) { //TODO }
}
I'd like to create a composite primary key with hibernate. Usually I'd go for #IdClass.
But this time I want to use a foreign key also inside the composite primary key.
Question: is that possible at all?
Example:
#Entity
class Person {
long id;
}
class CarPK implements Serializable {
private int code;
private String name;
public CarPK(int code, String name) {
this.code = code;
this.name = name;
}
}
#Entity
#IdClass(CarPK.class)
class Car {
#Id
private int code;
#Id
private String name;
//can I also mark "person.id" with #Id?
#ManyToOne
#JoinColumn(name = "fk_person_id", foreignKey = #ForeignKey(name = "fk_person"))
private Person person; //assume car is shared
}
The person reference will show in database as fk_person_id. Is it possible to also add this column to the primary key of the car table?
So I'd be getting similar to: CONSTRAINT car_pkey PRIMARY KEY (code, name, fk_person_id)?
Update:
#ManyToOne
#JoinColumn(name = "id")
private Person person;
Results in: Property of #IdClass not found in entity path.to$Car: id
Yes, you can add the #Id to the join column, but you must use the key type in your IdClass. I'm doing exactly the same thing in my current project.
#Entity
#IdClass(MyIdClass.class)
public class MyObject {
#Id
private String key;
#Column
#Lob
private String value;
#ManyToOne(cascade = CascadeType.PERSIST)
#Id
#JoinColumn(name = "id")
private MyOtherObject otherObject;
...
and
public class MyIdClass implements Serializable {
private long otherObject;
private String key;
...
MyOtherObject.id is a long in this scenario.
I have two tables called Stock and stock_daily_record. Please find the table structure below
Stock
Id (primary key) Int
Name varchar
stock_daily_record
Stockid(primary key & foreign key on id of Stock)
Stock_price (Primary key)
My Entity class
#Entity #Table(name = "stock")
class Stock
{
#id #column(name=”id”)
Private int id;
#column(name=”name”)
Private String name;
#oneToMany(fetch = FetchType.LAZY)
Private Set<DailyStockRecord> dailyRecords;
//Getters and setters,equals
}
My next class DailyStockRecord contains composite key alone. How to define mapping between this two classes. Plus how to define DailyStockRecord entity?
Note:
Please don't consider my Database design cos I tried to project my actual problem through this dummy design
#Entity #Table(name = "stock_daily_record")
class StockDailyRecord
{
#id #column(name=”Stockid”)
Private int stockId;
#column(name=”Stock_price”)
Private String stockPrice;
#ManyToOne(fetch = FetchType.LAZY)
#joinColumn(name="id")
Private Stock stock;
//Getters and setters,equals
}
Try this configuration
#Entity
#Table("stock_daily_record")
public class DailyStockRecord
#EmbeddedId
private DailyStockId stockId;
#MapsId("stockId")
#ManyToOne
private Stock stock;
And the embeddable key as follows:
#Embeddedable
public class DailyStockId
private int dailyStockId;
private int stockId;
And update your code to
#Entity
#Table(name = "stock")
public class Stock
#id #column(name=”id”)
Private int id;
#column(name=”name”)
Private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy="stock" )
Private Set<DailyStockRecord> dailyRecords;
//Getters and setters,equals
}