【问题标题】:Spring JPA Repository query filter by a relationship tableSpring JPA Repository 查询过滤器按关系表
【发布时间】:2015-03-26 20:41:37
【问题描述】:

如果我在 JPA 实体之间有如下多对多关系,我如何检索作为特定公司员工的 Person(我对人员属性感兴趣)的列表?

PersonCompany 之间的关系是多对多的。关系表Employee 具有到PersonCompany 的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> {
//
}

【问题讨论】:

  • 您还必须添加从PersonCompanyEmployee 和从CompanyCompanyEmployee 的引用。
  • 我想过这一点,但不满意将关系抽象泄露给对象映射。但似乎我没有简单的方法来解决它。 :(

标签: java spring jpa spring-data spring-data-jpa


【解决方案1】:

巴勃罗,
我们公司正在将我们现有的Spring/MyBatis代码转换为Spring Data JPA,所以一直在学习Spring Data JPA 几个星期。我显然不是专家,但我提出了一个与您类似的示例,可能会对您有所帮助。

我有 PersonCompany 与您的类相似的类,但是(正如 Jens 所提到的),您需要带有 OneToMany 注释的列表。我使用了一个单独的连接表(名为 company_person),它只有 companyIdpersonId 列来维护 多对多 关系。请参阅下面的代码。

我没有看到将开始/结束日期放在 company_person 连接表中的方法,所以我为此制作了一个单独的(第 4 个表)。我用 Java 类实体 EmploymentRecord 将其称为就业记录。它具有组合主键(companyId、personId)和开始/结束日期。

您需要个人、公司和就业记录的存储库。我扩展了 CrudRepository 而不是 JpaRepository。但是,您不需要连接表 (company_record) 的实体或存储库。

我创建了一个 Spring Boot 应用程序类来测试它。我在PersonOneToMany 上使用了CascadeType.ALL。在我的应用程序测试中,我测试了我可以更改分配给一个人的公司,并且 Spring Data 将所需的所有更改传播到 Company 实体和连接表。

但是,我必须通过其存储库手动更新 EmploymentRecord 实体。例如,每次我向一个人添加公司时,我都必须添加一个 start_date。然后,在我从那个人那里删除该公司时添加一个 end_date。可能有某种方法可以自动执行此操作。 Spring / JPA 审计功能是可能的,因此请检查一下。

你的问题的答案:

如何检索人员列表(我对此人感兴趣 属性)是特定公司的员工?

您只需使用 companyRepository 的 findOne(Long id) 方法,然后使用 getPersonList() 方法。

来自 Application.java 的 sn-p:

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

}

公司.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;

【讨论】:

  • 您先生,提供了一个很好的答案。不完全是我想要的(我想避免泄露关系抽象),但这确实是一项巨大的努力。谢谢!
【解决方案2】:

我以前在休眠 JPA 方面有过经验,但在 Spring JPA 方面没有经验。根据这些知识,以下查询可能有用:

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

【讨论】:

    【解决方案3】:

    您不需要为关系表创建单独的实体。

    关系可以在两个实体内保持,

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

    您现在可以在任一实体存储库上使用存储库查询,或者如果您的查询都带有参数,您可以在其中一个的存储库中创建自定义查询。

    【讨论】:

    • Class B 不应该定义 @JoinTable - 这样您将定义两个多对多关系,而不是通常需要的一个。相反,应该为类B@ManyToMany 注释定义mappedBy 参数。它可能看起来像@ManyToMany(mappedBy = "bList")
    • @dazewell 我得试试,但我认为你说的很有道理,谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-06-27
    • 2018-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-12
    • 2020-01-14
    • 1970-01-01
    相关资源
    最近更新 更多