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

sky*_*man 30 java spring jpa spring-data spring-data-jpa

我有许多简单的对象类型需要持久化到数据库.我正在使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Oli*_*ohm 71

首先,我知道我们在这里提高了相当多的标准,但这已经比没有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);
}
Run Code Online (Sandbox Code Playgroud)

这将允许您在客户端使用存储库,如下所示:

class MyClient {

  @Autowired
  public MyClient(ProductRepository<Car> carRepository, 
                  ProductRepository<Wine> wineRepository) { … }
}
Run Code Online (Sandbox Code Playgroud)

它将按预期工作.但是有几点需要注意:

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

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

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

摘要

是的,您可以构建通用存储库并将它们与多种域类型一起使用,但存在相当严格的技术限制.尽管如此,从架构的角度来看,您上面描述的场景甚至应该弹出,因为这意味着您无论如何都要面对设计气味.

  • 这是非常有用的信息.如果尚未在实际参考资料中包含这种更高级别的设计指南,那将会很棒.像"最佳实践"标注或其他东西. (5认同)
  • 嗯,这是软件架构和设计 1:1。如果我们在参考文档中包含这些内容,那么我们应该在哪里停止呢?;) (2认同)
  • 我正在设计一个应用程序时遇到这种情况......为每个模型提供一个'dto'和'repo'......它闻起来不对劲. (2认同)

Jos*_*nga 21

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

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      
}
Run Code Online (Sandbox Code Playgroud)

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

@NoRepositoryBean
public interface AbstractBaseRepository<T extends AbstractBaseEntity, ID extends Serializable>
extends JpaRepository<T, ID>{
    
}
Run Code Online (Sandbox Code Playgroud)

创建一个使用上述基本 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
    
}
Run Code Online (Sandbox Code Playgroud)

然后为基本 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

如何使用上述抽象entityservicerepository,和implementation

此处的示例将是一个MyDomain实体。创建一个扩展AbstractBaseEntity如下的域实体:NB。IDcreatedAtupdatedAtversion等将自动包含在MyDomain实体中AbstractBaseEntity

@Entity
public class MyDomain extends AbstractBaseEntity{

    private String attribute1;
    private String attribute2;
    // getters and setters
}
Run Code Online (Sandbox Code Playgroud)

然后repositoryMyDomain扩展AbstractBaseRepository如下的实体创建一个:

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

}
Run Code Online (Sandbox Code Playgroud)

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

public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{

}
Run Code Online (Sandbox Code Playgroud)

然后提供一个实现 MyDomain扩展AbstractBaseRepositoryImpl实现实体如下所示:

@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

}
Run Code Online (Sandbox Code Playgroud)

注意。确保AbstractBaseRepository使用注释,@NoRepositoryBean这样JPA就不会尝试查找 bean 的实现。也AbstractBaseServiceImpl必须标记为抽象,否则 JPA 将尝试自动装配AbstractBaseRepository类的构造函数中的所有子 daos,导致一个NoUniqueBeanDefinitionException创建 bean 时将注入 1 个以上的 daos(存储库)!现在,您的servicerepositoryimplementations更具可重用性。我们都讨厌样板!

希望这可以帮助某人。


Sco*_*ers 6

受到 @Jose Mhlanga 答案的启发,我找到了另一种方法来使用更少的类来做到这一点

创建一个BaseEntity

@Getter
@Setter
@MappedSuperclass
public class BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Run Code Online (Sandbox Code Playgroud)

创建一个BaseRepository

@NoRepositoryBean
public interface BaseRepository<T extends BaseEntity> extends JpaRepository<T, Long> { }
Run Code Online (Sandbox Code Playgroud)

最后创建一个BaseService完成设置

public interface BaseService<T extends BaseEntity> {

    BaseRepository<T> getRepository();

    default T create(T t) {
        return getRepository().save(t);
    }
    
    default Optional<T> update(T t) {
        if (getRepository().existsById(t.getId())) {
            return Optional.of(getRepository().save(t));
        }
        return Optional.empty();
    }
    
    default Optional<T> get(Long id) {
        return getRepository().findById(id);
    }
    
    default List<T> getAll() {
        return getRepository().findAll();
    }
    
    default void delete(Long id) {
        getRepository().deleteById(id);
    }
    
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以开始创建我们的实体。假设我们有一个名为 的实体Category。我们将创建模型、存储库、服务和控制器。

实体和存储库将类似于:

@Getter
@Setter
@Entity
public class Category extends BaseEntity { String name; }

public interface CategoryRepository extends BaseRepository<Category> { }
Run Code Online (Sandbox Code Playgroud)

对于服务,我们只需要重写一个方法:getRepository()

@ApplicationScoped
public class CategoryService implements BaseService<Category> {

    @Inject
    CategoryRepository categoryRepository;

    @Override
    public BaseRepository<Category> getRepository() {
        return categoryRepository;
    }

}
Run Code Online (Sandbox Code Playgroud)

最后,我们创建控制器。我还没有完全达到可以为控制器创建抽象的程度。如果我这样做,我会编辑这个。

@Path("categories")
@ApplicationScoped
public class CategoryController {

    // You can (and should!) use BaseService here.
    // Omitting that part as that would need qualifier.
    @Inject
    CategoryService categoryService;

    @POST
    public Response create(Category category) {
        return Response.status(Status.CREATED).entity(categoryService.create(category)).build();
    }

    @PUT
    @Path("{id}")
    public Response update(Category category, @PathParam("id") Long id) {
        if (Objects.isNull(category.getId()))
            category.setId(id);
        return categoryService.update(category).map(i -> Response.status(Status.ACCEPTED).entity(i).build())
                .orElseGet(() -> Response.status(Status.NOT_FOUND).build());
    }
    
    @DELETE
    @Path("{id}")
    public Response delete(@PathParam("id") Long id) {
        categoryService.delete(id);
        return Response.status(Status.ACCEPTED).build();
    }
    
    @GET
    @Path("{id}")
    public Response get(@PathParam("id") Long id) {
        return categoryService.get(id).map(i -> Response.status(Status.OK).entity(i).build())
                .orElseGet(() -> Response.status(Status.NO_CONTENT).build());
    }
    
    @GET
    public Response get() {
        return Response.status(Status.OK).entity(categoryService.getAll()).build();
    }
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助。干杯!