Spring JPA 存储库查询过滤器按关系 table
Spring JPA Repository query filter by a relationship table
如果我在 JPA 实体之间有如下所示的多对多关系,我如何检索属于特定公司员工的 Person
(我对人员属性感兴趣)的列表?
Person
和Company
之间的关系是多对多的。关系 table Employee
具有指向 Person
和 Company
的 FK,以及 start_date 和 end_date 以指示雇佣开始和结束的时间。
@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;
}
我在 CompanyEmployeeJPARepository
上使用 @Query
吗?我该如何应对?
public interface CompanyEmployeeRepository extends JpaRepository<CompanyEmployee,Long> {
//
}
我以前有过 Hibernate JPA 的经验,但没有 spring JPA。根据这些知识,以下查询可能会有用:
select cp.person from CompanyEmployee cp where cp.company.id = ?
巴勃罗,
我们公司正在将我们现有的 Spring / MyBatis 代码转换为 Spring 数据JPA,所以这几周一直在学习Spring Data JPA。我显然不是专家,但我想出了一个类似于你的例子,可能会对你有所帮助。
我有 Person
和 Company
class 与您的相似,但是(如 Jens 所述),您需要带有 OneToMany
注释的列表。我使用了一个单独的联接 table(名为 company_person),它只有 companyId、personId 列来维护 多对多关系。请参阅下面的代码。
我没有找到将 start/end 日期放入 company_person 连接 table 的方法,因此我为此单独制作了一个(第 4 个 table)。我用 Java class 实体 EmploymentRecord
称它为 employment_record。它具有组合主键 (companyId, personId) 和 start/end 日期。
您需要 Person、Company 和 EmploymentRecord 的存储库。我扩展了 CrudRepository 而不是 JpaRepository。但是,您不需要用于连接的实体或存储库 table (company_record).
我制作了一个 Spring 启动应用程序 class 来测试它。我在 Person
的 OneToMany
上使用了 CascadeType.ALL
。在我的应用程序测试中,我测试了我可以更改分配给一个人的公司,并且 Spring 数据将所有需要的更改传播到 Company
个实体并加入 table。
但是,我不得不通过其存储库手动更新 EmploymentRecord
实体。比如我每次加一个公司给一个人都要加一个start_date。然后,当我从那个人那里删除那个公司时,添加一个 end_date。可能有某种方法可以自动执行此操作。 Spring / JPA 审计功能是可能的,所以检查一下。
您问题的答案:
how can I retrieve a list of Person (I am interested in the person
attributes) that are employees of a specific company?
您只需使用 companyRepository 的 findOne(Long id) 方法,然后使用 getPersonList() 方法。
来自 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);
}
以下是我认为有用的一些参考资料:
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 脚本,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;
您不需要为关系 table 创建一个单独的实体。
可以在两个实体内维护关系,
所以如果A和B是多对多关系,
@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;
...
}
您现在可以在任一实体存储库上使用存储库查询,或者如果您在两个实体存储库上都有带参数的查询,则可以在其中一个存储库中创建自定义查询。
如果我在 JPA 实体之间有如下所示的多对多关系,我如何检索属于特定公司员工的 Person
(我对人员属性感兴趣)的列表?
Person
和Company
之间的关系是多对多的。关系 table Employee
具有指向 Person
和 Company
的 FK,以及 start_date 和 end_date 以指示雇佣开始和结束的时间。
@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;
}
我在 CompanyEmployeeJPARepository
上使用 @Query
吗?我该如何应对?
public interface CompanyEmployeeRepository extends JpaRepository<CompanyEmployee,Long> {
//
}
我以前有过 Hibernate JPA 的经验,但没有 spring JPA。根据这些知识,以下查询可能会有用:
select cp.person from CompanyEmployee cp where cp.company.id = ?
巴勃罗,
我们公司正在将我们现有的 Spring / MyBatis 代码转换为 Spring 数据JPA,所以这几周一直在学习Spring Data JPA。我显然不是专家,但我想出了一个类似于你的例子,可能会对你有所帮助。
我有 Person
和 Company
class 与您的相似,但是(如 Jens 所述),您需要带有 OneToMany
注释的列表。我使用了一个单独的联接 table(名为 company_person),它只有 companyId、personId 列来维护 多对多关系。请参阅下面的代码。
我没有找到将 start/end 日期放入 company_person 连接 table 的方法,因此我为此单独制作了一个(第 4 个 table)。我用 Java class 实体 EmploymentRecord
称它为 employment_record。它具有组合主键 (companyId, personId) 和 start/end 日期。
您需要 Person、Company 和 EmploymentRecord 的存储库。我扩展了 CrudRepository 而不是 JpaRepository。但是,您不需要用于连接的实体或存储库 table (company_record).
我制作了一个 Spring 启动应用程序 class 来测试它。我在 Person
的 OneToMany
上使用了 CascadeType.ALL
。在我的应用程序测试中,我测试了我可以更改分配给一个人的公司,并且 Spring 数据将所有需要的更改传播到 Company
个实体并加入 table。
但是,我不得不通过其存储库手动更新 EmploymentRecord
实体。比如我每次加一个公司给一个人都要加一个start_date。然后,当我从那个人那里删除那个公司时,添加一个 end_date。可能有某种方法可以自动执行此操作。 Spring / JPA 审计功能是可能的,所以检查一下。
您问题的答案:
how can I retrieve a list of Person (I am interested in the person attributes) that are employees of a specific company?
您只需使用 companyRepository 的 findOne(Long id) 方法,然后使用 getPersonList() 方法。
来自 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);
}
以下是我认为有用的一些参考资料:
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 脚本,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;
您不需要为关系 table 创建一个单独的实体。
可以在两个实体内维护关系,
所以如果A和B是多对多关系,
@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;
...
}
您现在可以在任一实体存储库上使用存储库查询,或者如果您在两个实体存储库上都有带参数的查询,则可以在其中一个存储库中创建自定义查询。