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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With