How to create composite key objects with existing object using hibernate - java

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 }
}

Related

Define composite primary key on JPA Entity

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;
}

Saving #OneToMany entity along with children (#EmbeddedId with composite foreign key)

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;

Map composite primary key to foreign key entities with Java Hibernate

I'm trying to retrieve a list of entities from a table with two primary keys which are ids to a foreign key each.
MySQL tables:
Paintings table:
id - int, PK, Auto increment
name - varchar(45)
Pictures table:
id - int, PK, Auto increment
name - varchar(45)
location - varchar(45)
painting_to_picture_link table:
painting_id - int, FK to id in painting
picture_id - int, FK to id in painting
I've set primary key (painting_id, picture_id)
and set them to their foreign keys also as written above.
In Java:
Painting.java
#Entity
#Table(name = "paintings")
public class Painting {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
...
}
Picture.java
#Entity
#Table(name = "pictures")
public class Pictures {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "location")
private String location;
...
}
PaintingPictureLink.class
public class PaintingPictureLink implements Serializable {
#<SOME ANNOTATION HERE>
private Painting painting;
#<SOME ANNOTATION HERE>
private Picture picture;
...
}
I've seen many examples, but didn't work for me.
I've tried putting #Id annotations, #EmbeddedId, etc... non worked.
The errors I get are that table isn't mapped, could not determine type for the models, missing #Id annotation... :|
Would appreciate help with querying this table and getting a list of PaintingPictureLink.
Some of the examples I've followed:
https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Web_Server/1.0/html/Hibernate_Annotations_Reference_Guide/ch03s02s06.html
Using an Entity (and their Primary Key) as another Entity's Id
Thanks,
Guy
Derp
Found the solution.
I created a primary key class:
#Embeddable
public class PicturePaintingPK implements Serializable {
#ManyToOne
private Painting painting;
#ManyToOne
private Picture picture;
public PicturePaintingPK() {}
// getters and setters //
}
In the PicturePaintingLink class:
#Entity
#Table(name = "painting_to_picture_link")
public class PaintingPictureLink implements Serializable {
#Id
private PicturePaintingPK primaryKey = new PicturePaintingPK()
...
//constructor//
...
public TTPK getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(TTPK primaryKey) {
this.primaryKey = primaryKey;
}
// ... all the other getters and setters needed .... //
}
The source for this solution was from:
Example from Hibernate forum
Guy

JPA ManyToMany Join Table has all attributes as PK

I'm using Hibernate 3.3.1 and am following along in modelling this sample table structure, but I'm having trouble creating a join table with extra attributes.
It's the many-to-many relationship between the Order and Product table. The join table is the Order Detail table. I followed the approach mentioned here.
Now I have the entities
#Entity
#Table(name = "Orders")
public class Order {
#OneToMany(mappedBy="order")
private List<OrderDetail> orderItems;
}
and
#Entity
#Table(name="PRODUCTS")
public class Product {
#OneToMany(mappedBy="product")
private List<OrderDetail> orderItems;
}
and
#Entity
#IdClass(OrderDetail.class)
#Table(name = "ORDER_DETAIL")
public class OrderDetail implements Serializable {
#Id
#Column(name="ORDER_ID")
private Long orderId;
#Id
#Column(name="PRODUCT_ID")
private Long productId;
#Column(name = "PRICE")
private double price;
#Column(name = "LAST_UPDATED_TIME")
private Date lastUpdatedTime;
#ManyToOne
#JoinColumn(name = "ORDER_ID")
private Order order;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID")
private Product product;
}
and
public class OrderDetailId implements Serializable {
private Long orderId;
private Long productId;
}
I used Apache Derby to do the test, but I'm having trouble with the generated table structure.
CREATE TABLE ORDER_DETAIL (
PRODUCT_ID BIGINT NOT NULL,
ORDER_ID BIGINT NOT NULL,
LAST_UPDATED_TIME TIMESTAMP NOT NULL,
PRICE DOUBLE NOT NULL
);
CREATE INDEX SQL120323142938020 ON ORDER_DETAIL (PRODUCT_ID ASC);
CREATE UNIQUE INDEX SQL120323142937810 ON ORDER_DETAIL (PRODUCT_ID ASC, ORDER_ID ASC, LAST_UPDATED_TIME ASC, PRICE ASC);
ALTER TABLE ORDER_DETAIL ADD CONSTRAINT SQL120323142937810 PRIMARY KEY (PRODUCT_ID, ORDER_ID, LAST_UPDATED_TIME, PRICE);
ALTER TABLE ORDER_DETAIL ADD CONSTRAINT FK4A94AA82CC6D989A FOREIGN KEY (PRODUCT_ID)
REFERENCES PRODUCTS (PROD_ID);
It seems that it has created all of my columns as the primary key. Why is this so?
You use class of your entity as an argument to IdClass. That is not correct. Class of Id should be used. Additionally separate fields for id in join entity are not needed.
Go for something like code below. I cannot guarantee that it works in such a old version of Hibernate, but works for sure in never ones. Worth of trying anyway. It would not hurt to update to at least 3.5.X version (or rather even fresher one) if you want to use JPA 2.0 features. Constructors/equals etc. are stripped away to save space.
#Entity
#Table(name = "Orders")
public class Order {
#Id Long id;
#OneToMany(mappedBy="order")
private List<OrderDetail> orderItems;
}
#Entity
#Table(name="PRODUCTS")
public class Product {
#Id Long id;
#OneToMany(mappedBy="product")
private List<OrderDetail> orderItems;
}
#Entity
#IdClass(OrderDetailId.class)
#Table(name = "ORDER_DETAIL")
public class OrderDetail implements Serializable {
#Id #ManyToOne #JoinColumn(name = "ORDER_ID")
private Order order;
#Id #ManyToOne #JoinColumn(name = "PRODUCT_ID")
private Product product;
#Column(name = "PRICE") private double price;
//Maybe you also want to use #TemporalType here
#Column(name = "LAST_UPDATED_TIME") private Date lastUpdatedTime;
}
public class OrderDetailId implements Serializable {
private Long order;
private Long product;
}
UPDATE 15/08/2017
In JPA 2.1 and above you don't need to add a class for the composite Id and you can do it like this:
#Entity
#Table(name = "ORDER_DETAIL")
public class OrderDetail implements Serializable {
#Id #ManyToOne #JoinColumn(name = "ORDER_ID")
private Order order;
#Id #ManyToOne #JoinColumn(name = "PRODUCT_ID")
private Product product;
#Column(name = "PRICE") private double price;
//Maybe you also want to use #TemporalType here
#Column(name = "LAST_UPDATED_TIME") private Date lastUpdatedTime;
}
The code below seems to generate tables as desired, I have tested it on MySQL (just the table creation, not CRUD):
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "orderDetailId.order")
private List<OrderDetail> orderItems;
//get set …..
}
#Entity
#Table(name="products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "orderDetailId.product")
private List<OrderDetail> orderItems;
//get set ……
}
#Entity
#Table(name = "order_detail")
public class OrderDetail {
#Id
private OrderDetailId orderDetailId;
private double price;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdatedTime;
//get set ….
}
#Embeddable
public class OrderDetailId implements Serializable{
private Order order;
private Product product;
#ManyToOne(fetch=FetchType.LAZY)
#Access(AccessType.PROPERTY)
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
#ManyToOne(fetch=FetchType.LAZY)
#Access(AccessType.PROPERTY)
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
//hash code equals override
}
Hibernate DEBUG details as below
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - create table order_detail (lastUpdatedTime datetime, price double precision not null, product_id bigint, order_id bigint, primary key (order_id, product_id)) ENGINE=InnoDB
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - create table orders (id bigint not null auto_increment, primary key (id)) ENGINE=InnoDB
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - create table products (id bigint not null auto_increment, primary key (id)) ENGINE=InnoDB
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - alter table order_detail add index FK23AE5A622128CF91 (order_id), add constraint FK23AE5A622128CF91 foreign key (order_id) references orders (id)
DEBUG: org.hibernate.tool.hbm2ddl.SchemaUpdate - alter table order_detail add index FK23AE5A62EB201631 (product_id), add constraint FK23AE5A62EB201631 foreign key (product_id) references products (id)

JPA many to many with extra column

I have a following problem that I need to solve.
The core issues is that I want to add additional column into JoinTable for ManyToMany relation in JPA. In my case I have following entities.
The Topic is a simple entity which has many RemoteDocument's (one RemoteDocument may be refered by many Topic's, hence it should be ManyToMany relation). Also RemoteDocument entity is read only because it may be read only from Oracle Materialized View moreover any altering of this Materialized View is forbidden. So I want to store order of RemoteDocuments related to some Topic. In fact I can do something like that with additional entity:
#Entity
public class Topic {
#Id
private Long id;
#Basic
private String name;
#OneToMany
private Set<TopicToRemoteDocument> association;
}
#Entity
public class RemoteDocument {
#Id
private Long id;
#Basic
private String description;
}
#Entity
public class TopicToRemoteDocument {
#OneToOne
private Topic topic;
#OneToOne
private RemoteDocument remoteDocument;
#Basic
private Integer order;
}
In this case additional entity TopicToRemoteDocument helps me to replace ManyToMany association with OneToMany and add extra field order.
But I want to have ManyToMany relation but with configured additional column in join table
Use list instead of set, together with the #OrderColumn annotation and JPA will automatically take care of the order:
#MappedSuperclass
public class BaseEntity{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public Long getId(){
return id;
}
public void setId(final Long id){
this.id = id;
}
}
#Entity
public class Topic extends BaseEntity{
#ManyToMany(mappedBy = "topics")
#OrderColumn
private List<Document> documents = new ArrayList<Document>();
public List<Document> getDocuments(){
return documents;
}
public void setDocuments(final List<Document> documents){
this.documents = documents;
}
}
#Entity
public class Document extends BaseEntity{
#ManyToMany
#OrderColumn
private List<Topic> topics = new ArrayList<Topic>();
public List<Topic> getTopics(){
return topics;
}
public void setTopics(final List<Topic> topics){
this.topics = topics;
}
}
Generated DDL (using hibernate and HSQL):
create table Document (
id bigint generated by default as identity (start with 1),
primary key (id)
);
create table Document_Topic (
documents_id bigint not null,
topics_id bigint not null,
topics_ORDER integer not null,
documents_ORDER integer not null,
primary key (documents_id, topics_ORDER)
);
create table Topic (
id bigint generated by default as identity (start with 1),
primary key (id)
);
alter table Document_Topic
add constraint FK343B5D0B481100B2
foreign key (documents_id)
references Document;
alter table Document_Topic
add constraint FK343B5D0B558627D0
foreign key (topics_id)
references Topic;
I would try to avoid using a List unless you allow duplicates.
There is a #OrderColumn annotation that automatically does this. Have you tried it?
#Entity
public class Topic {
#Id
private Long id;
#Basic
private String name;
#OneToMany
#OrderColumn
private Set<TopicToRemoteDocument> association;
}
One technique that is useful when creating the many-to-many mapping class entity is to attribute the id's in the class along with #ManyToOne designation which makes this class act as the composite key class:
#Entity
#Table(name = "market_vendor")
public class MarketVendor implements Serializable
{
#Id
#ManyToOne
#JoinColumn(name = "market_id")
private Market market;
#Id
#ManyToOne
#JoinColumn(name = "vendor_id")
private Vendor vendor;
#Basic
#Column(name="active")
private boolean active;
public MarketVendor(Market market, Vendor vendor, boolean active)
{
this.market = market;
this.vendor = vendor;
this.active = active;
}
}
This allows you to have the composite primary key defined within the same class without having to have a separate primary key class. You also need to make the class serializable.

Categories