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

时间:2013-10-17 03:20:23

标签: 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);
    }
}

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

3 个答案:

答案 0 :(得分:62)

首先,我知道我们在这里提高了相当多的标准,但这已经比你在没有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…开头。这是因为反射查找永远不会产生WineCar ,除非为它创建专用存储库接口以捕获具体类型信息

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

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

摘要

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

答案 1 :(得分:9)

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

为您的实体创建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存储库接口,如下所示: 注意请记住放置@NoRepositoryBean,以使JPA不会尝试为存储库找到实现!

@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

此处的示例为一个MyDomain实体

  1. 创建一个扩展AbstractBaseEntity的域实体,如下所示: 注意IDcreatedAtupdatedAtversion等将自动从MyDomain

    包含在AbstractBaseEntity实体中

    @实体 公共类MyDomain扩展了AbstractBaseEntity {

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

    }

然后为repository实体创建一个MyDomain,以扩展AbstractBaseRepository如下:

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

}

还要为service实体创建一个MyDomain接口,如下所示:

public interface MyDomainService extends AbstractBaseService<MyDomain, Long>{

}

然后提供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

}

NB。确保AbstractBaseRepository带有@NoRepositoryBean注释,以便JPA不会尝试为该bean查找实现。 同样必须将AbstractBaseServiceImpl标记为抽象,否则JPA将尝试自动将AbstractBaseRepository的类的所有子daos连接到导致NoUniqueBeanDefinitionException的类的构造函数中,因为超过1个daos(存储库) )将在创建bean时注入! 现在,您的servicerepositoryimplementations更具可重用性。我们都讨厌样板!

希望这对某人有帮助。

答案 2 :(得分:1)

我正在一个项目中,使用春季数据为cassandra创建通用存储库。

首先用代码创建一个存储库接口。

StringBuilder sourceCode = new StringBuilder();
sourceCode.append("import org.springframework.boot.autoconfigure.security.SecurityProperties.User;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.AllowFiltering;\n");
sourceCode.append("import org.springframework.data.cassandra.repository.Query;\n");
sourceCode.append("import org.springframework.data.repository.CrudRepository;\n");
sourceCode.append("\n");
sourceCode.append("public interface TestRepository extends CrudRepository<Entity, Long> {\n");
sourceCode.append("}");

编译代码并获取类,我使用org.mdkt.compiler.InMemoryJavaCompiler

ClassLoader classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
compiler = InMemoryJavaCompiler.newInstance();
compiler.useParentClassLoader(classLoader);
Class<?> testRepository = compiler.compile("TestRepository", sourceCode.toString());

并在spring数据运行时初始化存储库。这有点棘手,因为我调试SpringData代码以查找如何在Spring中初始化存储库接口。

CassandraSessionFactoryBean bean = context.getBean(CassandraSessionFactoryBean.class);
RepositoryFragments repositoryFragmentsToUse = (RepositoryFragments) Optional.empty().orElseGet(RepositoryFragments::empty); 
CassandraRepositoryFactory factory = new CassandraRepositoryFactory(
    new CassandraAdminTemplate(bean.getObject(), bean.getConverter()));
factory.setBeanClassLoader(compiler.getClassloader());
Object repository = factory.getRepository(testRepository, repositoryFragmentsToUse);

现在,您可以尝试存储库的save方法,还可以尝试其他方法,例如findById。

Method method = repository.getClass().getMethod("save", paramTypes);
T obj = (T) method.invoke(repository, params.toArray());

我已在此回购中放入完整的示例代码和实现 https://github.com/maye-msft/generic-repository-springdata

您可以使用类似的逻辑将其扩展到JPA。