Mapping a weak entity with JPA - java

My database contains a companies and employees. I have modeled Employee as a weak entity of Company.
My JPA annotations look like:
#Entity
public class Company extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<Employee> employees;
}
Employee.java:
#Entity
public class Employee extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#ManyToOne(optional = false)
#JoinColumn(name="company_id", insertable=false, updatable=false)
private Company company;
}
The following SQL code is created:
create table employee (
id bigint auto_increment not null,
company_id bigint not null,
constraint pk_employee primary key (id)
);
alter table employee add constraint fk_employee_company_id foreign key (company_id) references company (id) on delete restrict on update restrict;
What I want is (constraint pk_employee primary key (id, company_id):
create table employee (
id bigint auto_increment not null,
company_id bigint not null,
constraint pk_employee primary key (id, company_id)
);
alter table employee add constraint fk_employee_company_id foreign key (company_id) references company (id) on delete restrict on update restrict;
Is there a way to create such SQL script?
EDIT:
Letting Employee implement Serializable does not do the trick.
Caused by: javax.persistence.PersistenceException: Could not find BeanDescriptor for class models.Company. Perhaps the EmbeddedId class is not registered?
at io.ebeaninternal.server.deploy.BeanEmbeddedMetaFactory.create(BeanEmbeddedMetaFactory.java:26)
at io.ebeaninternal.server.deploy.BeanPropertyAssocOne.<init>(BeanPropertyAssocOne.java:79)
at io.ebeaninternal.server.deploy.BeanPropertyAssocOne.<init>(BeanPropertyAssocOne.java:62)
at io.ebeaninternal.server.deploy.meta.DeployBeanTable.createProperty(DeployBeanTable.java:68)
at io.ebeaninternal.server.deploy.meta.DeployBeanTable.createIdProperties(DeployBeanTable.java:59)
at io.ebeaninternal.server.deploy.BeanTable.<init>(BeanTable.java:42)

Thanks to #Alan Hay for giving us the link
https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Composite_Primary_Keys
Here is how you create a weak entity Address that takes it's Id (user_id) from the table pmo_user
pmo_user{id, ....}
test_address{user_id, address}
============================
#Entity
#Table(name="test_address")
public class Address
{
#Id
#Column(name="user_id")
protected int userId;
#OneToOne
#PrimaryKeyJoinColumn(name="user_id", referencedColumnName="id")
protected PmoUser owner;
#Column(name="address")
protected String address;
public void setOwner(PmoUser owner)
{
this.owner = owner;
this.userId = owner.getId();
}
#Override
public String toString() {
return "Address [userId=" + userId + ", owner=" + owner + ", address=" + address + "]";
}
}

Just add #Id annotation under your #JoinColumn annotation. But Employee class must implements Serializable to use this solution.

Related

Hibernate ERROR: Cannot delete or update a parent row: a foreign key constraint fails

I have the following classes:
#Entity
#Table(name = "lecture")
public class Lecture {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String name;
#OneToOne
#JoinColumn(nullable = false, name = "professorId")
#JsonIgnore
private Professor professor;
}
And:
#Entity
#IdClass(ListenTo.class)
#Table(name = "listen_to")
public class ListenTo implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
#JoinColumn(nullable = false, name = "lectureId")
private Lecture lecture;
#Id
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(nullable = false, name = "studentId")
private Student student;
}
And I want to delete a lecture through this function:
public void delete(Lecture lecture) {
currentSession().delete(lecture);
}
I created the table like this:
create table lecture (
id bigint primary key not null auto_increment,
name varchar(500) not null,
professorId bigint not null,
foreign key (professorId) references professorId (id)
);
create table listen_to (
lectureId BIGINT not null references lecture(id),
studentId BIGINT not null references student(id),
primary key(lectureId,studentId)
);
However, I keep getting this error:
Causing: java.sql.SQLIntegrityConstraintViolationException: (conn=10) Cannot delete or update a parent row: a foreign key constraint fails (`myDBS`.`listen_to`, CONSTRAINT `listen_to_ibfk_1` FOREIGN KEY (`lectureId`) REFERENCES `lecture` (`id`))
I tried multiple things, including using this function to delete:
public boolean deleteById(Class<?> type, Serializable id) {
Object persistentInstance = currentSession().load(type, id);
if (persistentInstance != null) {
currentSession().delete(persistentInstance);
return true;
}
return false;
}
but it still doesn't work..
You first have to delete all ListenTo entries that refer to this lecture before you can delete the lecture. Use delete from ListenTo l where l.lecture.id = :id and bind the lecture id, before you delete the lecture itself.

How to create composite key objects with existing object using hibernate

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

Can someone help me understand the following logic related to inheritance strategy in hibernate?

This text is taken from a book named Spring persistence with hibernate and author is describing the shortcomings of one table for each concrete class inheritance strategy in hibernate. In the given example, Student and Teacher are child classes of Person class and Person is associated with Address class. I'm not able to understand this particular argument which is given below:-
Imperfect support for polymorphic associations: The problem occurs when
a parent class is associated with another persistent class. In our example,
suppose Person is associated with an Address class, so both Student and
Teacher are associated with Address, as well. To map this class hierarchy
according to this approach, we need these four tables in our database
schema: PERSON, STUDENT, TEACHER, and ADDRESS. If Address has a
many-to-one relationship with Person (more than one person may have
the same address), then the ADDRESS table should provide a foreign key
reference to all PERSON, STUDENT, and TEACHER tables, in order to establish
database-level relationship, but this is not possible.
Why is it not possible for Address table to provide foreign key reference to PERSON, STUDENT, and TEACHER tables?
Assuming you have three different database tables PERSON, STUDENT and TEACHER how would you define the foreign key constraint on the table ADDRESS?
CONSTRAINT FOREIGN KEY fk_address_owner
ADDRESS.owner_id
REFERENCES (which table?) (id)
As you can see, you would have to point to three database tables. That's not possible.
In InheritanceType.TABLE_PER_CLASS it is not possible to implement Address has many-to-one relation to Person. Hibernate doesn't create the association.
But It is possible to create unidirectional one-to-many between Student and Address. Here is Address entity :
#Entity
public class Address {
#Id
#GeneratedValue
private Long id;
private String address;
public Address() {
}
Person entity is:
#Entity
#Table(name = "persons")
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
public Person() {
}
Student entity subclasses Person entity:
#Entity
#Table(name = "students")
public class Student extends Person {
#OneToMany()
#JoinColumn(name = "student_id", referencedColumnName = "id")
private List<Address> addresses;
public Student() {
}
Because of #JoinColumn annotation hibernate creates student_id column in address table and adds foreign key constraint. HIbernate logs:
Hibernate:
create table Address (
id bigint not null,
address varchar(255),
student_id bigint,
primary key (id)
)
Hibernate:
create table persons (
id bigint not null,
name varchar(255),
primary key (id)
)
Hibernate:
create table students (
id bigint not null,
name varchar(255),
primary key (id)
)
Hibernate:
alter table Address
add constraint FKpe2jekm3pchvipc2cct5u5win
foreign key (student_id)
references students (id)
Bidirectional association between Student and Address can be implemented:
#Entity
public class Address {
#Id
#GeneratedValue
private Long id;
private String address;
#ManyToOne
private Student student;
public Address() {
}
And Student entity is:
#Entity
#Table(name = "students")
public class Student extends Person {
#OneToMany(mappedBy="student")
private List<Address> addresses;
public Student() {
}
Hibernate creates the same db tables as in unidirectional one-to-many above.
But with InheritanceType.JOINED it is possible to create bidirectional many-to-one between Address and Person,Student. Here is Address entity:
#Entity
public class Address {
#Id
#GeneratedValue
private Long id;
private String address;
#ManyToOne
private Person person;
public Address() {
}
Person entity :
#Entity
#Table(name = "persons")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy="person")
private List<Address> addresses;
public Person() {
}
Student entity is :
#Entity
#Table(name = "students")
public class Student extends Person {
private String email;
public Student() {
}
Hibernate creates tables:
Hibernate:
create table Address (
id bigint not null,
address varchar(255),
person_id bigint,
primary key (id)
)
Hibernate:
create table persons (
id bigint not null,
name varchar(255),
primary key (id)
)
Hibernate:
create table students (
email varchar(255),
id bigint not null,
primary key (id)
)
Hibernate:
alter table Address
add constraint FKtaq05im3hxcvufy75xk044251
foreign key (person_id)
references persons (id)
Hibernate:
alter table students
add constraint FK9nqs0pkter5l6no6n9v93uyau
foreign key (id)
references persons (id)
students table references persons table via FK9nqs0pkter5l6no6n9v93uyau constraint. address table references persons table via FKtaq05im3hxcvufy75xk044251. This means that hibernate handles one-to-many between Student entity and Address entity in addresses collection of Person entity. Any entity that subclasses Person has one-to-many relation to Address entity.
With InheritanceType.SINGLE_TABLE it is possible to create bidirectional many-to-one between Address and Student. Here is Address entity:
#Entity
public class Address {
#Id
#GeneratedValue
private Long id;
private String address;
#ManyToOne
private Person person;
public Address() {
}
Person entity is:
#Entity
#Table(name = "persons")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy="person")
private List<Address> addresses;
public Person() {
}
Student entity is:
#Entity
#Table(name = "students")
public class Student extends Person {
private String studentEmail;
public Student() {
}
Hibernate creates single table for fields in Student and Person entities:
Hibernate:
create table Address (
id bigint not null,
address varchar(255),
person_id bigint,
primary key (id)
)
Hibernate:
create table persons (
DTYPE varchar(31) not null,
id bigint not null,
name varchar(255),
studentEmail varchar(255),
primary key (id)
)
Hibernate:
alter table Address
add constraint FKtaq05im3hxcvufy75xk044251
foreign key (person_id)
references persons (id)
Student entity has one-to-many relation to Address entity because of FKtaq05im3hxcvufy75xk044251 constraint.

Hibernate Join and Restrictions on multiple tables

I have three tables to query with Hibernate or JPA...
Tables structure
CREATE TABLE `product` (
`product_id` int(11) NOT NULL AUTO_INCREMENT,
`insert_date` datetime NOT NULL,
`last_update` datetime NOT NULL,
...
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8;
CREATE TABLE `language` (
`language_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
...
PRIMARY KEY (`language_id`),
KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
CREATE TABLE `product_description` (
`product_id` int(11) unsigned NOT NULL,
`language_id` int(11) unsigned NOT NULL,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`tag` text NOT NULL,
`meta_title` varchar(255) NOT NULL,
`meta_description` varchar(255) NOT NULL,
`meta_keyword` varchar(255) NOT NULL,
PRIMARY KEY (`product_id`,`language_id`),
KEY `name` (`name`),
KEY `product_language_idx` (`language_id`),
CONSTRAINT `product_description` FOREIGN KEY (`product_id`) REFERENCES `product` (`product_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `product_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Product <OneToMany> ProductDescription
Language <ManyToOne> ProductDescription
Assuming my entities are the following:
Product Entity:
#Entity
#Table(name="product")
#NamedQuery(name="Product.findAll", query="SELECT p FROM Product p")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="product_id", unique=true, nullable=false)
private int productId;
....
//bi-directional many-to-one association to ProductDescription
#OneToMany(fetch = FetchType.LAZY, mappedBy = "product")
private List<ProductDescription> productDescriptions;
Language Entity:
#Entity
#Table(name="language")
#NamedQuery(name="Language.findAll", query="SELECT l FROM Language l")
public class Language implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="language_id", unique=true, nullable=false)
private int languageId;
...
//bi-directional many-to-one association to ProductDescription
#ManyToOne(fetch=FetchType.LAZY)
//#JoinColumn(name="language_id", referencedColumnName="id",nullable=false, insertable=false, updatable=false)
private ProductDescription productDescription;
ProductDescription Entity (id is a composite key in class ProductDescriptionPK):
#Entity
#Table(name="product_description")
#NamedQuery(name="ProductDescription.findAll", query="SELECT p FROM ProductDescription p")
public class ProductDescription implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ProductDescriptionPK id;
//bi-directional many-to-one association to Language
#OneToMany(mappedBy="productDescription")
private List<Language> languages;
//bi-directional many-to-one association to Product
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="product_id", nullable=false, insertable=false, updatable=false)
private Product product;
So I have to execute a query as sample, using a join between product and product_description, where language is 1:
SELECT p.product_id, pd.description
FROM product p
INNER JOIN product_description pd
USING ( product_id )
INNER JOIN language
USING ( language_id )
WHERE language_id = 1
Firstly I have used Hibernate with Criteria Query:
Criteria criteria = getSession().createCriteria(Product.class,"p");
criteria.createAlias("p.productDescriptions", "pd");
criteria.createAlias("pd.languages", "l");
criteria.add(Restrictions.eq("l.languageId", new Integer(1)));
result = criteria.list();
But that code retrieves this error:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Unknown column 'l2_.productDescription_language_id' in 'field list'
What's my issue? How can I perform queries like this?
Thanks a lot!
I do not understand completely your data model.
I think that the relationship between a Language and a ProductDescription should be one-to-many from the Language point of view but putting that aside ...
UPDATED:
Effectively, Hibernate do not map correctly the relationships using the annotations that you have indicated above. It's trying to map the strange ManyToOne relation in the table language and it can not find those fields: productDescription_language_id and productDescription_product_id.
I think that the correct mapping for your tables is:
LANGUAGE ENTITY
#Entity
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="language_id", unique=true, nullable=false)
private Long languageId;
private String name;
#OneToMany(fetch=FetchType.LAZY, mappedBy="language")
private List<ProductDescription> productDescriptions =
new ArrayList<ProductDescription>();
// Other fields + getters and setters
}
PRODUCT DESCRIPTION ENTITY
#Entity
#Table(name="product_description")
public class ProductDescription {
#Embeddable
public static class ProductDescriptionPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "product_id")
protected Long productId;
#Column(name = "language_id")
protected Long languageId;
public ProductDescriptionPK() {
}
public ProductDescriptionPK(Long productId, Long languageId) {
super();
this.productId = productId;
this.languageId = languageId;
}
}
#EmbeddedId
private ProductDescriptionPK id;
private String description;
#ManyToOne
#JoinColumn(name="language_id", nullable=false, insertable=false, updatable=false)
private Language language;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="product_id", nullable=false, insertable=false, updatable=false)
private Product product;
// Other fields + getters and setters
}
Here is a working example of how you can chain joins with JPQL using the entities defined as you have declared them on your question.
EntityManager em = emf.createEntityManager();
// [UPDATED] QUERY
String jpql = "SELECT p.id, pd.description FROM Product p "
+ "JOIN p.productDescriptions pd "
+ "JOIN pd.language l WHERE l.language_id = :idLanguage)";
Query query = newEm.createQuery(jpql);
query.setParameter("idLanguage", new Long(1));
List<Object> resultList = query.getResultList();
System.out.println( resultList.size() + " product(s) found:" );
for (Object singleResult : resultList) {
Object[] singleRow = (Object[]) singleResult;
System.out.println(singleRow[0] + " " + singleRow[1]);
}
That code generates this SQL query [UPDATED]
select
product0_.product_id as col_0_0_,
productdes1_.description as col_1_0_
from
Product product0_
inner join
product_description productdes1_
on product0_.product_id=productdes1_.product_id
inner join
Language language2_
on productdes1_.language_id=language2_.language_id
where
language2_.language_id=?
I have been reading some articles and books on the subject and using a left join fetch with a where clause is invalid. Quoting "Java Persistence with Hibernate" by Gavin King et al:
"The query left join fetch i.bids b where b.amount ... is invalid. You can't say, "Load the Item instances and initializes their bids collections, but only with Bid instances that have a certain amount"
Hope this helps.

Spring JPA Repository query filter by a relationship table

If I have a many-to-many relationship between JPA entities as below, how can I retrieve a list of Person (I am interested in the person attributes) that are employees of a specific company?
The relationship between Person and Company is many-to-many. The relationship table Employee has the FK to Person and Company, and a start_date and end_date to indicate when the employment started and finished.
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
}
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
}
#Entity
public class CompanyEmployee {
//note this is to model a relationship table. Am I doing this wrong?
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "start_date", nullable = false)
private LocalDate startDate;
#Column(name = "end_date", nullable = false)
private LocalDate endDate;
#ManyToOne
private Company company;
#ManyToOne
private Person person;
}
Do I use a #Query on the CompanyEmployeeJPARepository? How should I tackle it?
public interface CompanyEmployeeRepository extends JpaRepository<CompanyEmployee,Long> {
//
}
Pablo,
Our company is in the process of converting our existing Spring / MyBatis code to Spring Data JPA, so I have been learning Spring Data JPA for a few weeks. I'm clearly not an expert, but I worked out an example similar to yours which may help you.
I have Person and Company classes that are similar to yours, but (as Jens mentioned), you need lists with OneToMany annotations. I used a separate join table (named company_person) which only has companyId, personId columns to maintain the many-to-many relationship. See the code below.
I did not see a way to put the start/end dates in the company_person join table, so I made a separate (4th table) for that. I called it employment_record with Java class entity EmploymentRecord. It has the combo primary key (companyId, personId) and the start/end dates.
You need repositories for Person, Company, and EmploymentRecord. I extended CrudRepository instead of JpaRepository. But, you don't need an entity or repository for the join table (company_record).
I made a Spring Boot Application class to test it out. I used CascadeType.ALL on Person's OneToMany. In my Application test, I tested that I can change the companies assigned to a person and Spring Data propagates all the changes needed to the Company entities and join table.
However, I had to manually update the EmploymentRecord entities, via its repository. For example, I had to add a start_date each time I added a company to a person. Then, add an end_date when I removed that company from that person. There is probably some way to automate this. The Spring / JPA audit feature is a possibility, so check that out.
The answer to your question:
how can I retrieve a list of Person (I am interested in the person
attributes) that are employees of a specific company?
You simply use companyRepository's findOne(Long id) method followed by getPersonList() method.
snippet from Application.java:
PersonRepository pRep = context.getBean(PersonRepository.class);
CompanyRepository cRep = context.getBean(CompanyRepository.class);
EmploymentRecordRepository emplRep = context.getBean(EmploymentRecordRepository.class);
...
// fetch a Company by Id and get its list of employees
Company comp = cRep.findOne(5L);
System.out.println("Found a company using findOne(5L), company= " + comp.getName());
System.out.println("People who work at " + comp.getName());
for (Person p : comp.getPersonList()) {
System.out.println(p);
}
Here are some references that I found to be useful:
Spring Data JPA tutorial
Join Table example
Person.java:
#Entity
public class Person {
// no-arg constructor
Person() { }
// normal use constructor
public Person(String name, String address) {
this.name = name;
this.address = address;
}
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Version
private int versionId;
#OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name="company_person",
joinColumns={#JoinColumn(name="person_id", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="company_id", referencedColumnName="id")})
private List<Company> companyList;
// Getters / setters
}
Company.java:
#Entity
public class Company {
// no-arg constructor
Company() { }
// normal use constructor
public Company(String name, String address) {
this.name = name;
this.address = address;
}
#Id
#GeneratedValue
private Long id;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Version
private int versionId;
//#OneToMany(cascade=CascadeType.ALL)
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name="company_person",
joinColumns={#JoinColumn(name="company_id", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="person_id", referencedColumnName="id")})
private List<Person> personList;
// Getters / Setters
}
EmploymentRecord.java:
#Entity
#IdClass(EmploymentRecordKey.class)
public class EmploymentRecord {
// no-arg constructor
EmploymentRecord() { }
// normal use constructor
public EmploymentRecord(Long personId, Long companyId, Date startDate, Date endDate) {
this.startDate = startDate;
this.endDate = endDate;
this.companyId = companyId;
this.personId = personId;
}
// composite key
#Id
#Column(name = "company_id", nullable = false)
private Long companyId;
#Id
#Column(name = "person_id", nullable = false)
private Long personId;
#Column(name = "start_date")
private Date startDate;
#Column(name = "end_date")
private Date endDate;
#Version
private int versionId;
#Override
public String toString() {
return
" companyId=" + companyId +
" personId=" + personId +
" startDate=" + startDate +
" endDate=" + endDate +
" versionId=" + versionId;
}
// Getters/Setters
}
// Class to wrap the composite key
class EmploymentRecordKey implements Serializable {
private long companyId;
private long personId;
// no arg constructor
EmploymentRecordKey() { }
#Override
public int hashCode() {
return (int) ((int) companyId + personId);
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (obj == this) return true;
if (!(obj instanceof EmploymentRecordKey)) return false;
EmploymentRecordKey pk = (EmploymentRecordKey) obj;
return pk.companyId == companyId && pk.personId == personId;
}
// Getters/Setters
}
MySql script, createTables.sql:
DROP TABLE IF EXISTS `test`.`company_person`;
DROP TABLE IF EXISTS `test`.`employment_record`;
DROP TABLE IF EXISTS `test`.`company`;
DROP TABLE IF EXISTS `test`.`person`;
CREATE TABLE `company` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL DEFAULT '',
`address` varchar(500) DEFAULT '',
`version_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `person` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(128) NOT NULL DEFAULT '',
`address` varchar(500) DEFAULT '',
`version_id` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* Join table */
CREATE TABLE `company_person` (
`company_id` int NOT NULL,
`person_id` int NOT NULL,
PRIMARY KEY (`person_id`,`company_id`),
KEY `company_idx` (`company_id`),
KEY `person_idx` (`person_id`),
CONSTRAINT `fk_person` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* Employment records */
CREATE TABLE `employment_record` (
`company_id` int NOT NULL,
`person_id` int NOT NULL,
`start_date` datetime,
`end_date` datetime,
`version_id` int NOT NULL,
PRIMARY KEY (`person_id`,`company_id`),
KEY `empl_company_idx` (`company_id`),
KEY `empl_person_idx` (`person_id`),
CONSTRAINT `fk_empl_person` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `fk_empl_company` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I have previous experience in hibernate JPA but not spring JPA. From that knowledge following query might be useful:
select cp.person from CompanyEmployee cp where cp.company.id = ?
You shouldn't need to make a separate entity for the relationship table.
The relationship can be maintained within the two entities,
so if A and B are in a many-to-many relationship,
#Entity
class A {
#Id
Long id;
...
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="a_b",
joinColumns={#JoinColumn(name="id_a", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="id_b", referencedColumnName="id")})
List<B> bList;
...
}
#Entity
class B {
#Id
Long id;
...
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name="a_b",
joinColumns={#JoinColumn(name="id_b", referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="id_a", referencedColumnName="id")})
List<A> aList;
...
}
You can now use the repository queries on either of the entity repositories or if you have a query with params on both, you can create a custom query in the repository of one.

Categories