小编典典

在Spring Data JPA存储库中使用泛型

spring

我有许多需要保留到数据库的简单对象类型。我正在使用Spring JPA来管理这种持久性。对于每种对象类型,我需要构建以下内容:

import org.springframework.data.jpa.repository.JpaRepository;

public interface FacilityRepository extends JpaRepository<Facility, Long> {
}


public interface FacilityService {
    public Facility create(Facility facility);
}

@Service
public class FacilityServiceImpl implements FacilityService {

    @Resource
    private FacilityRepository countryRepository;

    @Transactional
    public Facility create(Facility facility) {
        Facility created = facility;
        return facilityRepository.save(created);
    }
}

在我看来,有可能用三个基于泛型的类替换每种对象类型的多个类,从而节省了大量的样板代码。我不确定该怎么做,实际上,如果这是个好主意?


阅读 1015

收藏
2020-04-11

共1个答案

小编典典

首先,我知道我们在这里提高了一些标准,但这已经比没有Spring Data JPA的帮助而编写的代码少得多。

其次,如果你要做的只是将调用转发到存储库,那么我首先就不需要服务类。如果你有需要在事务中编排不同存储库的业务逻辑或要封装的其他业务逻辑,我们建议在存储库前面使用服务。

一般来说,你当然可以执行以下操作:

interface ProductRepository<T extends Product> extends CrudRepository<T, Long> {

    @Query("select p from #{#entityName} p where ?1 member of p.categories")
    Iterable<T> findByCategory(String category);

    Iterable<T> findByName(String name);
}

这将允许你像这样使用客户端上的存储库:

class MyClient {

  @Autowired
  public MyClient(ProductRepository<Car> carRepository, 
                  ProductRepository<Wine> wineRepository) { … }
}

它将按预期工作。但是,有一些注意事项:

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

一般而言,我们建议为每个聚合根创建存储库接口。这意味着你本身没有每个域类的存储库。更重要的是,基于存储库的服务的1:1抽象也完全没有意义。如果你构建服务,则不会为每个存储库都构建一个服务(猴子可以做到,而我们不是猴子,对吗?;)。服务正在公开一个更高级别的API,更多地是用例驱动,并且通常协调对多个存储库的调用。

另外,如果你在存储库之上构建服务,通常需要强制客户端使用服务而不是存储库(此处的经典示例是,用于用户管理的服务还会触发密码生成和加密,因此决不会最好让开发人员直接使用存储库,因为他们可以有效地解决加密问题。因此,你通常希望对谁可以保留哪些域对象保持选择性,而不是在整个地方都没有创建依赖项。

摘要
是的,你可以构建通用存储库并将其与多种域类型一起使用,但是存在相当严格的技术限制。但是,从架构的角度来看,你上面描述的场景甚至都不会弹出,因为这意味着你无论如何都面临着设计的气味。

2020-04-11