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

2022-09-01 01:15:59

我有许多简单的对象类型需要持久保存到数据库中。我正在使用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);
    }
}

我突然想到,可以用三个基于泛型的类替换每个对象类型的多个类,从而节省大量的样板编码。我不完全确定如何去做,事实上,如果这是一个好主意?


答案 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) { … }
}

它将按预期工作。但是,有几件事需要注意:

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

一般来说,我们建议为每个聚合根创建存储库接口。这意味着您没有针对每个域类本身的存储库。更重要的是,在存储库上对服务进行1:1的抽象也完全没有抓住重点。如果你构建服务,你不会为每个存储库构建一个(一只猴子可以做到这一点,我们不是猴子,对吧?;)。服务公开了更高级别的 API,是更多的用例驱动器,并且通常协调对多个存储库的调用。

此外,如果您在存储库之上构建服务,则通常希望强制客户端使用服务而不是存储库(这里的一个经典示例是,用于用户管理的服务也会触发密码生成和加密,因此让开发人员直接使用存储库绝不是一个好主意,因为他们可以有效地解决加密问题)。因此,您通常希望有选择地选择谁可以保留哪些域对象,而不是在任何地方创建依赖项。

总结

是的,您可以构建通用存储库并将其用于多种域类型,但有非常严格的技术限制。不过,从架构的角度来看,你上面描述的场景甚至不应该弹出,因为这意味着你无论如何都面临着设计气味。


答案 2

这是很有可能的!我可能很晚才参加派对。但这肯定会在未来帮助某人。这是一个完整的解决方案,就像一个魅力!

为实体创建类,如下所示:BaseEntity

@MappedSuperclass
public class AbstractBaseEntity implements Serializable{

    @Id @GeneratedValue
    private Long id;
    @Version
    private int version;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    public AbstractBaseEntity() {
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    // getters and setters      
}

为 DAO 持久性创建一个通用的 JPA 存储库接口,如下所示:注意。请记住放置,以便 JPA 不会尝试查找存储库的实现!@NoRepositoryBean

@NoRepositoryBean
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable>
extends JpaRepository<T, ID>{
    
}

创建一个使用上述基本 JPA 存储库的基本服务类。这是您域中的其他服务接口将简单地扩展的接口,如下所示:

public interface AbstractBaseService<T extends AbstractBaseEntity, ID extends Serializable>{
    public abstract T save(T entity);
    public abstract List<T> findAll(); // you might want a generic Collection if u prefer

    public abstract Optional<T> findById(ID entityId);
    public abstract T update(T entity);
    public abstract T updateById(T entity, ID entityId);   
    public abstract void delete(T entity);
    public abstract void deleteById(ID entityId);
    // other methods u might need to be generic
    
}

然后为基本JPA存储库创建一个抽象实现,基本CRUD方法也将提供其实现,如下所示:

@Service
@Transactional
public abstract class AbstractBaseRepositoryImpl<T extends AbstractBaseEntity, ID extends Serializable>
        implements AbstractBaseService<T, ID>{
    
    private AbstractBaseRepository<T, ID> abstractBaseRepository;
    
    @Autowired
    public AbstractBaseRepositoryImpl(AbstractBaseRepository<T, ID> abstractBaseRepository) {
        this.abstractBaseRepository = abstractBaseRepository;
    }
    
    @Override
    public T save(T entity) {
        return (T) abstractBaseRepository.save(entity);
    }

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

    @Override
    public Optional<T> findById(ID entityId) {
        return abstractBaseRepository.findById(entityId);
    }

    @Override
    public T update(T entity) {
        return (T) abstractBaseRepository.save(entity);
    }

    @Override
    public T updateById(T entity, ID entityId) {
        Optional<T> optional = abstractBaseRepository.findById(entityId);
        if(optional.isPresent()){
            return (T) abstractBaseRepository.save(entity);
        }else{
            return null;
        }
    }

    @Override
    public void delete(T entity) {
        abstractBaseRepository.delete(entity);
    }

    @Override
    public void deleteById(ID entityId) {
        abstractBaseRepository.deleteById(entityId);
    }

}

如何使用上面的摘要 、 、 和 :entityservicerepositoryimplementation

此处的示例将是一个实体。创建一个域实体,该实体扩展了以下内容:NB。、 、 、 等 将自动包含在实体中MyDomainAbstractBaseEntityIDcreatedAtupdatedAtversionMyDomainAbstractBaseEntity

@Entity
public class MyDomain extends AbstractBaseEntity{

    private String attribute1;
    private String attribute2;
    // getters and setters
}

然后为实体创建一个扩展,如下所示:repositoryMyDomainAbstractBaseRepository

@Repository
public interface MyDomainRepository extends AbstractBaseRepository<MyDomain, Long>{

}

此外,为实体创建一个接口,如下所示:serviceMyDomain

public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{

}

然后为扩展实现的实体提供实现,如下所示:MyDomainAbstractBaseRepositoryImpl

@Service
@Transactional
public class MyDomainServiceImpl extends AbstractBaseRepositoryImpl<MyDomain, Long> 
        implements MyDomainService{
    private MyDomainRepository myDomainRepository;

    public MyDomainServiceImpl(MyDomainRepository myDomainRepository) {
        super(myDomainRepository);
    }
    // other specialized methods from the MyDomainService interface

}
Now use your `MyDomainService` service in your controller as follows: 

@RestController // or @Controller
@CrossOrigin
@RequestMapping(value = "/")
public class MyDomainController {
    
    private final MyDomainService myDomainService;

    @Autowired
    public MyDomainController(MyDomainService myDomainService) {
        this.myDomainService = myDomainService;
    }
   
    @GetMapping
    public List<MyDomain> getMyDomains(){
        return myDomainService.findAll();
    }   
    // other controller methods

}

铌。确保 用 进行注释,这样就不会尝试查找 Bean 的实现。此外,必须标记为抽象,否则JPA将尝试自动连接类构造函数中的所有子 daos,从而导致 a,因为在创建 bean 时将注入超过 1 个 daos(存储库)!现在,您的 、 和 更易于重用。我们都讨厌样板!AbstractBaseRepository@NoRepositoryBeanJPAAbstractBaseServiceImplAbstractBaseRepositoryNoUniqueBeanDefinitionExceptionservicerepositoryimplementations

希望这有助于某人。


推荐