简体   繁体   English

通用Spring数据JPA存储库findAll

[英]Generic Spring Data JPA Repository findAll

Is there a way to make a generic Spring Data JPA repository handle correctly methods like findAll() ? 有没有一种方法可以使通用Spring Data JPA存储库正确处理诸如findAll()类的方法? eg AnimalRepository<Dog>.findAll return only Dogs, instead of all animals? 例如AnimalRepository<Dog>.findAll只返回狗,而不是所有动物? Or at least, how would be the best workaround at that? 或者至少,最好的解决方法是什么?

Say I have this: 说我有这个:

@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();
    }

    ...

}

It works almost perfectly, saves each animal on its own table and so. 它几乎完美地工作,将每只动物保存在自己的桌子上,等等。 The only problem is: the findAll() returns BOTH Capybaras and Dogs. 唯一的问题是: findAll() 同时返回水豚和狗。 This answer explains: 答案说明:

This only works if the domain classes use single table inheritance. 仅当域类使用单表继承时,此方法才有效。 The only information about the domain class we can get at bootstrap time is that it will be Product objects. 我们在引导时可以获得的有关域类的唯一信息是它将是Product对象。 So for methods like findAll() and even findByName(…) the relevant queries will start with select p from Product p where…. 因此,对于诸如findAll()甚至findByName(...)之类的方法,相关查询将从Product p其中...的select p开始。 This is due to the fact that the reflection lookup will never ever be able to produce Wine or Car unless you create a dedicated repository interface for it to capture the concrete type information. 这是由于这样的事实,除非您创建专用的存储库接口以捕获具体的类型信息,否则反射查找将永远无法产生Wine或Car。

Ok, sadly the code gets a bit less clean having multiple repositories instead of only one. 好的,可悲的是,使用多个存储库而不是只有一个存储库,代码变得不太干净。 But I would still like to keep a single AnimalService class. 但是我仍然想保留一个AnimalService类。 This is the way I did: 这是我的方法:

@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();
    }

    ...

}

If the Repository really can't handle the findAll() according to the type <T> , what would be the cleanest way to have a single AnimalService? 如果存储库确实不能根据类型<T>处理findAll() ,那么拥有单个AnimalService的最干净方法是什么? Surely there must be a better way than what I did, since it gets real ugly, really fast that way if you have more complexity in the service and more than a couple animals. 当然,必须有一种比我做的更好的方法,因为如果您在服务中具有更多的复杂性,并且有很多动物,那么它将变得非常丑陋,非常快。


EDIT: Given that Spring's DI considers AnimalRepository<Dog> and AnimalRepository<Capybara> as being the same thing (injecting the same Repository onto a Service that uses the Capybara one and another Service who uses the Dog one), I had to create a different Repository and Service for each of them (Option B of @ESala answer): 编辑:考虑到Spring的DI认为AnimalRepository<Dog>AnimalRepository<Capybara>是同一件事 (将相同的存储库注入使用AnimalRepository<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);
    }
}

There's probably a better way, so I'll stay open to suggestions. 可能有更好的方法,所以我将保持开放的态度。

On the repository: since you don't use single table inheritance, you are going to need a repository instance for each T , no way around that. 在存储库上:由于您不使用单表继承,因此您将需要为每个T都需要一个存储库实例,没有办法解决。 This means you will have an instance of AnimalRepository<Dog> and an instance of AnimalRepository<Capybara> . 这意味着您将拥有AnimalRepository<Dog>的实例和AnimalRepository<Capybara> AnimalRepository<Dog>的实例。

On the service: somewhere in your application you will need a switch/case that directs you to the correct repository depending on the type. 在服务上:应用程序中的某个位置,您将需要一个开关/外壳,根据类型将您引导至正确的存储库。

You have 2 options, a) having a single service instance that handles all types and internally directs the query to the corresponding repository, or b) having a service instance for each T and selecting the instance elsewhere. 您有2个选择,a)具有处理所有类型的单个服务实例,并在内部将查询定向到相应的存储库,或者b)具有每个T的服务实例并在其他位置选择该实例。

I prefer option a). 我更喜欢选项a)。 You can adapt your current implementation to return the correct type with a single findAll() by getting a reference to the T type and using a switch/case for selecting the appropriate repository. 您可以通过获取对T类型的引用并使用开关/案例来选择适当的存储库,使当前的实现通过单个findAll()返回正确的类型。

Getting a reference to <T> at runtime is messy because of type erasure, but it can be done. 由于类型擦除,在运行时获取对<T>的引用很麻烦,但是可以做到。

How about adding a category property/enum to 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();
}

NOTE: It's also possible to get the Class type of the generic but this is a little more tricky 注意:也可以获取泛型的Class类型,但这有点棘手

How to get a class instance of generics type T 如何获得泛型类型T的类实例

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

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