JPA存储库与单表继承(Hibernate)

huangapple go评论71阅读模式
英文:

JPA repository with single table inheritance (hibernate)

问题

我已经创建了两个实体(RegularEmployeeContactEntity),它们都继承自 Employee 实体。

@Entity
@Table(name="employees")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(value="employee")
public class Employee {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

...

我在这些实现中使用了 SINGLE_TABLE 继承策略,并创建了一个用于操作数据的通用 JpaRepository:

@Repository
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
}

我还创建了一个 Service 类,自动装配了这三个通用仓库的实例,并为每个类编写了特定的方法。

@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository<Employee> employeeRepo;

    @Autowired
    private EmployeeRepository<RegularEmployee> regularRepo;

    @Autowired
    private EmployeeRepository<ContractEmployee> contractRepo;

    public List<Employee> getAllEmployee() {
        return employeeRepo.findAll();
    }

    public List<RegularEmployee> getAllRegularEmployee(){
        return regularRepo.findAll();
    }

    public List<ContractEmployee> getAllContractEmployee() {
        return contractRepo.findAll();
    }
...

我的问题是,当我尝试查找所有常规员工或合同员工时,我总是会得到所有类型的员工(包括员工、常规员工和合同员工)。

我不知道为什么会出现这种情况,尽管方法的签名表明它应该返回适当的类型。

英文:

I have created two entites (RegularEmployee and ContactEntity) that extends the Employee entity.

@Entity
@Table(name=&quot;employees&quot;)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;type&quot;, discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(value=&quot;employee&quot;)
public class Employee {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

...

Im using SINGLE_TABLE inheritance for this implementations, and created a generic JpaRepository for manipulating data:

@Repository
public interface EmployeeRepository&lt;T extends Employee&gt; extends JpaRepository&lt;T, Long&gt; {
}

I've created also the Service class that autowire three instance of these generic repositories, and specific methods for each class.

@Service
public class EmployeeService {

    @Autowired
    private EmployeeRepository&lt;Employee&gt; employeeRepo;

    @Autowired
    private EmployeeRepository&lt;RegularEmployee&gt; regularRepo;

    @Autowired
    private EmployeeRepository&lt;ContractEmployee&gt; contractRepo;

    public List&lt;Employee&gt; getAllEmployee() {
        return employeeRepo.findAll();
    }

    public List&lt;RegularEmployee&gt; getAllRegularEmployee(){
        return regularRepo.findAll();
    }

    public List&lt;ContractEmployee&gt; getAllContractEmployee() {
        return contractRepo.findAll();
    }
...

My problem is, that when I try to find all regular employees or contract employees, I always get all type of employees (employees, regular employees and contract employees all together).

I do not know why it behaves like this, even though the method's signature says it returns the appropriate type.

答案1

得分: 4

`EmployeeRepository`一种选择是使用`@Query`:

```java
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
    @Query("from RegularEmployee")
    List<RegularEmployee> findAllRegularEmployees();
}

第二种选择是为每个Employee子类创建一个额外的存储库。对于RegularEmployee,可以这样:

public interface RegularEmployeeRepository extends EmployeeRepository<RegularEmployee> {}

以下是如何在EmployeeService中使用这两种选项:

@Service
public class EmployeeService {
    @Autowired EmployeeRepository<Employee> employeeRepo;

    @Autowired EmployeeRepository<RegularEmployee> regularRepoT;

    @Autowired RegularEmployeeRepository regularRepo;

    @PostConstruct
    public void init(){
        employeeRepo.save(new ContractEmployee("Mark"));
        employeeRepo.save(new RegularEmployee("Luke"));
        employeeRepo.findAll().forEach(System.out::println); // 打印 Mark 和 Luke
        regularRepo.findAll().forEach(System.out::println); // 仅打印 Luke
        regularRepoT.findAllRegularEmployees().forEach(System.out::println); // 仅打印 Luke
    }
    //...
}

另外,你可以在EmployeeRepository的类顶部省略@Repository。Spring 已经知道它是一个存储库,因为它扩展了JpaRepository

附注:如果你不希望Spring创建EmployeeRepository,可以在其类顶部添加@NoRepositoryBean


注意:代码中的一些符号可能在显示上会有差异,但我已经尽力保留了原始代码的结构和内容。如果需要进一步的帮助,请随时提问。

<details>
<summary>英文:</summary>

One option is to use `@Query` in `EmployeeRepository`:

    public interface EmployeeRepository&lt;T extends Employee&gt; extends JpaRepository&lt;T, Long&gt; {
        @Query(&quot;from RegularEmployee&quot;)
        List&lt;RegularEmployee&gt; findAllRegularEmployees();
    }


A second option is to create an additional repository for each subclass of `Employee`. For `RegularEmployee` would be:

    public interface RegularEmployeeRepository extends EmployeeRepository&lt;RegularEmployee&gt;{}

This is how to use both options in `EmployeeService`:

    @Service
    public class EmployeeService {
        @Autowired EmployeeRepository&lt;Employee&gt; employeeRepo;

        @Autowired EmployeeRepository&lt;RegularEmployee&gt; regularRepoT;
    
        @Autowired RegularEmployeeRepository regularRepo;
    
        @PostConstruct
        public void init(){
            employeeRepo.save(new ContractEmployee(&quot;Mark&quot;));
            employeeRepo.save(new RegularEmployee(&quot;Luke&quot;));
            employeeRepo.findAll().forEach(System.out::println); // prints Mark and Luke
            regularRepo.findAll().forEach(System.out::println); // prints only Luke
            regularRepoT.findAllRegularEmployees().forEach(System.out::println); // prints only Luke
        }
    //...
    }

Also you can omit `@Repository` on top of `EmployeeRepository`. Spring already knows that is a Repository because it extends `JpaRepository`.

**Side note**: if you don&#39;t need `EmployeeRepository` to be created by Spring add `@NoRepositoryBean` on top of its class.


  [1]: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query.spel-expressions

</details>



# 答案2
**得分**: 1

我已经能够使用您的通用EmployeeRepository复制您遇到的问题。作为一种替代方法,我创建了两个单独的存储库:ContractualEmployeeRepository和RegularEmployeeRepository。

```java
public interface ContractualEmployeeRepository extends JpaRepository<ContractualEmployee, String> {
}

public interface RegularEmployeeRepository extends JpaRepository<RegularEmployee, String> {
}

然后,我创建了一个集成测试。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Main.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@TestPropertySource(locations="classpath:application-test.properties")
@DatabaseSetup("classpath:SingleTableDataSet.xml")
public class IntegrationTest {

    @Autowired
    private RegularEmployeeRepository regularEmployeeRepository;

    @Autowired
    private ContractualEmployeeRepository contractualEmployeeRepository;

    @Test
    public void test() {
        Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
        Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
    }

}

它有效运行。

至于在Spring Data JPA存储库中使用泛型的用途和限制,可以参考:https://stackoverflow.com/a/19443031/14180014 他在解释方面做得非常出色。

英文:

I've been able to replicate what you've encountered using your generic EmployeeRepository. As an alternative I created two separate repositories: ContractualEmployeeRepository and RegularEmployeeRepository.

public interface ContractualEmployeeRepository extends JpaRepository&lt;ContractualEmployee, String&gt; {
}

public interface RegularEmployeeRepository extends JpaRepository&lt;RegularEmployee, String&gt; {
}

Then, I created an integration test.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Main.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class})
@TestPropertySource(locations=&quot;classpath:application-test.properties&quot;)
@DatabaseSetup(&quot;classpath:SingleTableDataSet.xml&quot;)
public class IntegrationTest {

    @Autowired
    private RegularEmployeeRepository regularEmployeeRepository;

    @Autowired
    private ContractualEmployeeRepository contractualEmployeeRepository;

    @Test
    public void test() {
        Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
        Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
    }

}

and it works.

As for the usage and limitations of Generics in Spring Data JPA repositories: https://stackoverflow.com/a/19443031/14180014 He had done a great job explaining it.

huangapple
  • 本文由 发表于 2020年8月30日 18:31:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/63656497.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定