【问题标题】:Generic Spring Data JPA Repository findAll通用 Spring Data JPA 存储库 findAll
【发布时间】:2018-06-15 23:22:17
【问题描述】:

有没有办法让通用 Spring Data JPA 存储库正确处理 findAll() 之类的方法?例如AnimalRepository<Dog>.findAll 只返回狗,而不是所有动物?或者至少,最好的解决方法是什么?

说我有这个:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Animal extends BaseEntity {
    ...
}

@Entity
public class Dog extends Animal {
    ...
}

@Entity
public class Capybara extends Animal {
    ...
}

public interface AnimalRepository<T extends Animal> extends JpaRepository<T, Long> {

}

@Service("animalService")
public class AnimalServiceImpl<T extends Animal> implements AnimalService<T> {

    private final AnimalRepository<T> animalRepository;

    @Autowired
    public AnimalServiceImpl(AnimalRepository<T> aR) {
        this.animalRepository = aR;
    }

    @Override
    public void save(T animal) {
        animalRepository.save(animal);
    }

    @Override
    public List<T> findAll() {
        return animalRepository.findAll();
    }

    ...

}

它几乎完美地工作,将每只动物保存在自己的桌子上等等。唯一的问题是:findAll() 返回BOTH Capybaras 和 Dogs。这个answer解释道:

这仅在域类使用单表继承时才有效。我们可以在引导时获得的关于域类的唯一信息是它将是 Product 对象。因此,对于 findAll() 甚至 findByName(...) 等方法,相关查询将以 select p from Product p where... 开始。这是因为反射查找永远无法生成 Wine 或 Car,除非您为其创建专用的存储库接口以捕获具体类型信息。

好吧,遗憾的是,拥有多个存储库而不是只有一个存储库,代码变得不那么干净了。但我仍然想保留一个 AnimalService 类。我就是这样做的:

@Service("animalService")
public class AnimalServiceImpl<T extends Animal> implements AnimalService<T> {

    private final DogRepository dogRepository;
    private final CapybaraRepository capybaraRepository;

    @Autowired
    public AnimalServiceImpl(AnimalRepository<T> aR) {
        this.animalRepository = aR;
    }

    @Override
    public void save(T animal) {
        animalRepository.save(animal);
    }

    @Override
    public List<T> findAllDogs() {
        return dogRepository.findAll();
    }

    @Override
    public List<T> findAllCapybaras() {
        return capybaraRepository.findAll();
    }

    ...

}

如果存储库真的无法根据&lt;T&gt; 类型处理findAll(),那么拥有单个 AnimalService 的最干净的方法是什么?肯定有比我做的更好的方法,因为如果你的服务更复杂并且不止一对动物,它会变得非常丑陋,非常快。


编辑:鉴于 Spring 的 DI 考虑了AnimalRepository&lt;Dog&gt; and AnimalRepository&lt;Capybara&gt; as being the same thing(将相同的存储库注入到使用 Capybara 的服务和使用 Dog 的服务中),我必须为每个服务创建不同的存储库和服务( @ESala 答案的选项 B):

@NoRepositoryBean
public interface AnimalRepository<T extends Animal> extends JpaRepository<T, Long> {
}

public interface DogRepository extends AnimalRepository<Dog> {   
}

public interface CapybaraRepository extends AnimalRepository<Capybara> {   
}

public abstract class AnimalService<T extends Animal> {
    private final AnimalRepository<T> animalRepository;

    AnimalService(AnimalRepository<T> repo) {
        this.animalRepository = repo;
    }

    public void salvar(T palavra) {
        animalRepository.save(palavra);
    }

    public List<T> findAll() {
        return animalRepository.findAll();
    }

}

@Service
public class DogService extends AnimalService<Dog> {

    @Autowired
    public DogService(DogRepository repo) {
        super(repo);
    }
}

@Service
public class CapybaraService extends AnimalService<Capybara> {

    @Autowired
    public CapybaraService(CapybaraRepository repo) {
        super(repo);
    }
}

可能有更好的方法,所以我会继续接受建议。

【问题讨论】:

    标签: java spring architecture spring-data-jpa


    【解决方案1】:

    如何将类别属性/枚举添加到 Animal

    @Override
    public List<T> findAll(Animal animal) {
        if(animal.getCategory() == "Dog") 
           return findAllDogs();
        if(animal.getCategory() == "Capybara") 
           return findAllCapybaras();
    }
    
    @Override
    private List<T> findAllDogs() {
        return dogRepository.findAll();
    }
    
    @Override
    private List<T> findAllCapybaras() {
        return capybaraRepository.findAll();
    }
    

    注意:也可以获取泛型的 Class 类型,但这有点棘手

    How to get a class instance of generics type T

    【讨论】:

    • 您确定这是 Java 中的有效语法吗?我认为 findAll 和 T.getCategory 无效。
    • 抱歉,赶时间:)
    【解决方案2】:

    在存储库上:由于您不使用单表继承,因此您将需要为每个 T 提供一个存储库实例,这是没有办法的。这意味着您将拥有一个 AnimalRepository&lt;Dog&gt; 实例和一个 AnimalRepository&lt;Capybara&gt; 实例。

    在服务上:在您的应用程序的某个地方,您需要一个开关/案例,根据类型将您引导到正确的存储库。

    您有 2 个选项,a) 拥有一个服务实例来处理所有类型并在内部将查询定向到相应的存储库,或者 b) 为每个 T 拥有一个服务实例并在其他地方选择该实例。

    我更喜欢选项 a)。您可以通过获取对 T 类型的引用并使用 switch/case 来选择适当的存储库来调整当前实现以使用单个 findAll() 返回正确的类型。

    由于类型擦除,在运行时获取对&lt;T&gt; 的引用很麻烦,但可以做到。

    【讨论】:

    • 我试过这样做,但事实证明,Spring 单例(@Service, @Repository, @Components 等)不考虑类型 &lt;T&gt;。即:AnimalRepository&lt;Dog&gt;AnimalRepository&lt;Capybara&gt; 都是相同的。 More info.
    • 选项 B 是使用 Spring 的 DI 效果最好的选项,因为我在上面的评论中说过。我将编辑问题以列出我必须做的事情。谢谢。
    猜你喜欢
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 2018-08-06
    • 2016-12-26
    • 1970-01-01
    • 2016-05-21
    • 2020-10-08
    • 2013-08-23
    相关资源
    最近更新 更多