使用Spring JPA Repository根据关联表进行查询过滤

6
如果我有以下JPA实体之间的多对多关系,我该如何检索属于特定公司的Person列表(我感兴趣的是个人属性)? PersonCompany之间的关系是多对多。关系表Employee具有指向PersonCompany的FK,并具有开始和结束日期以指示就业开始和结束时间。
@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的引用都添加进去。 - Jens
我考虑过这个,但不想将关系抽象泄漏到对象映射中。但看起来我没有简单的解决办法。 :( - Pablo
3个回答

4
Pablo,
我们公司正在将现有的Spring/MyBatis代码转换为Spring Data JPA,因此我已经学习了几周的Spring Data JPA。虽然我不是专家,但我已经解决了一个类似于你的示例,它可能会对你有所帮助。
我拥有与你相似的Person和Company类,但是(正如Jens所提到的),你需要使用带有OneToMany注释的列表。我使用了一个单独的连接表(名为company_person),它只有companyId、personId两列,以维护多对多关系。请查看下面的代码。
我没有找到一种在company_person连接表中放置开始/结束日期的方法,因此我为此创建了一个单独的(第四个)表。我称之为employment_record,Java类实体为EmploymentRecord。它具有组合主键(companyId、personId)和开始/结束日期。
您需要为Person、Company和EmploymentRecord编写存储库。我扩展了CrudRepository而不是JpaRepository。但是,您不需要为连接表(company_record)创建实体或存储库。
我制作了一个Spring Boot应用程序类来测试它。我在Person的OneToMany上使用了CascadeType.ALL。在我的应用程序测试中,我测试了我可以更改分配给人员的公司,并且Spring Data会传播到Company实体和连接表所需的所有更改。
但是,我必须通过其存储库手动更新EmploymentRecord实体。例如,每次将公司添加到人员时,我必须添加一个start_date。然后,在从该人员中删除该公司时添加end_date。可能有一种自动化此过程的方法。Spring/JPA审核功能是一种可能性,因此请查看它。
回答你的问题:
如何检索特定公司的雇员列表(我对人员属性感兴趣)?
您只需使用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教程
连接表示例

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;

您提供了一个很好的答案。虽然不完全符合我的要求(我想避免泄漏关系抽象),但确实是非常出色的努力。谢谢! - Pablo

3

我之前有Hibernate JPA的经验,但没有Spring JPA。从这个知识出发,以下查询可能会有用:

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

0

你不需要为关系表创建一个单独的实体。

关系可以在两个实体内维护,

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

...

}

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


B不应该定义@JoinTable - 这样你会定义两个多对多关系,而通常只需要一个。相反,应该为类B@ManyToMany注释定义mappedBy参数。它可以看起来像@ManyToMany(mappedBy = "bList") - dazewell
@dazewell 我得试试看,但我觉得你说的有道理,谢谢! - AA_PV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接