简体   繁体   English

Spring JPA Repository查询过滤关系表

[英]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? 如果我在JPA实体之间存在多对多关系,如何检索特定公司员工的Person列表(我对person属性感兴趣)?

The relationship between Person and Company is many-to-many. PersonCompany之间的关系是多对多的。 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. 关系表Employee具有FK到PersonCompany ,以及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;
}

Do I use a @Query on the CompanyEmployeeJPARepository ? 难道我用@QueryCompanyEmployeeJPARepository How should I tackle it? 我应该如何解决这个问题?

public interface CompanyEmployeeRepository extends JpaRepository<CompanyEmployee,Long> {
//
}

I have previous experience in hibernate JPA but not spring JPA. 我以前有过hibernate JPA的经验但不是spring JPA。 From that knowledge following query might be useful: 根据该知识,以下查询可能有用:

select cp.person from CompanyEmployee cp where cp.company.id = ?

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. 我们公司正在将现有的Spring / MyBatis代码转换为Spring Data JPA ,因此我已经学习了几周的Spring Data JPA 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. 我有与您类似的PersonCompany类,但(正如Jens所提到的),您需要使用OneToMany注释的列表。 I used a separate join table (named company_person) which only has companyId , personId columns to maintain the many-to-many relationship. 我使用了一个单独的连接表(名为company_person),它只有companyIdpersonId列来维护多对多关系。 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. 我没有看到将开始/结束日期放在company_person连接表中的方法,所以我为此做了一个单独的(第4个表)。 I called it employment_record with Java class entity EmploymentRecord . 我用Java类实体EmploymentRecord称它为employment_record。 It has the combo primary key (companyId, personId) and the start/end dates. 它具有组合主键(companyId,personId)和开始/结束日期。

You need repositories for Person, Company, and EmploymentRecord. 您需要Person,Company和EmploymentRecord的存储库。 I extended CrudRepository instead of JpaRepository. 我扩展了CrudRepository而不是JpaRepository。 But, you don't need an entity or repository for the join table (company_record). 但是,您不需要连接表(company_record)的实体或存储库。

I made a Spring Boot Application class to test it out. 我做了一个Spring Boot Application类来测试它。 I used CascadeType.ALL on Person 's OneToMany . 我在PersonOneToMany上使用了CascadeType.ALL 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. 在我的应用程序测试中,我测试过我可以更改分配给某人的公司,Spring Data会传播Company实体和联接表所需的所有更改。

However, I had to manually update the EmploymentRecord entities, via its repository. 但是,我不得不通过其存储库手动更新EmploymentRecord实体。 For example, I had to add a start_date each time I added a company to a person. 例如,每次我向一个人添加公司时,我都必须添加一个start_date。 Then, add an end_date when I removed that company from that person. 然后,当我从该人那里删除该公司时添加end_date。 There is probably some way to automate this. 可能有一些方法可以实现自动化。 The Spring / JPA audit feature is a possibility, so check that out. Spring / JPA审核功能是可能的,所以检查一下。

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. 您只需使用companyRepository的findOne(Long id)方法,然后使用getPersonList()方法。

snippet from Application.java: 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 Spring Data JPA教程
Join Table example 加入表示例

Person.java: 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: 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: 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: 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;

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, 所以如果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;

...

}

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. 您现在可以在任一实体存储库上使用存储库查询,或者如果您在两者上都有params查询,则可以在其中的存储库中创建自定义查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM