java Spring Data - 覆盖某些存储库的默认方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27950246/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:41:46  来源:igfitidea点击:

Spring Data - Overriding default methods for some repositories

javaspringspring-dataspring-data-jpaspring-data-rest

提问by Fotis Paraskevopoulos

I am just staring with spring-dataand spring-data-restand I really want to take advantage of what these tools have to offer. For the most case the base functionality is perfect for my use case however there are some cases where I need to customize the underlying functionality quite a bit, and selectively assign some repositories to inherit the customized functionality I am after.

我只是用凝视spring-dataspring-data-rest我真的想盘点一下这些工具所提供的优势。在大多数情况下,基本功能非常适合我的用例,但是在某些情况下,我需要对底层功能进行大量自定义,并有选择地分配一些存储库来继承我所追求的自定义功能。

To explain the problem a bit better, in spring-datathere are 2 possible interfaces which you can inherit functionality from, CrudRepositoryor PagingAndSortingRepository. I want to add a third called lets say PesimisticRepository

为了更好地解释这个问题,spring-data有 2 个可能的接口,您可以从中继承功能,CrudRepositoryPagingAndSortingRepository。我想添加第三个叫做让我们说PesimisticRepository

All the PesimisticRepositorydoes is to handle the notion of a deleted @Entity differently. A deletedentity is one where its deletedproperty is NOT NULL. This means that an @Entity which can be handled by a PesimisticRepositoryhas to have a deletedproperty.

所有PesimisticRepository要做的就是以不同的方式处理已删除的@Entity 的概念。一个被删除的实体是一个地方它删除属性NOT NULL。这意味着可以由 a 处理的@EntityPesimisticRepository必须具有已删除的属性。

All this is possible, I have actually implemented this a couple of years ago. (You can check it out herein case you are interested)

所有这一切都是可能的,我实际上在几年前就已经实现了。(有兴趣的可以在这里查看)

My current attempt using spring-data is the following:

我目前使用 spring-data 的尝试如下:

An extension of the PagingAndSortingRepository

的扩展 PagingAndSortingRepository

package com.existanze.xxx.datastore.repositories;

import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

import java.io.Serializable;


@NoRepositoryBean
public interface PesimisticRepository<T,ID extends Serializable> extends PagingAndSortingRepository<T,ID> {
}

For which I provide a default implementation extending JPARepository

为此,我提供了一个默认实现扩展 JPARepository

package com.existanze.xxx.datastore.repositories;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.Date;


public class JpaPesimisticRepository<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements PesimisticRepository<T,ID> {


    private final EntityManager entityManager;

    public JpaPesimisticRepository(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.entityManager = em;
    }

    @Override
    @Transactional
    public Page<T> findAll(Specification<T> spec, Pageable pageable) {

        CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = cb.createQuery(getDomainClass());
        Root<T> from = criteriaQuery.from(this.getDomainClass());
        Predicate deleted = cb.equal(from.get("deleted"), cb.nullLiteral(Date.class));
        criteriaQuery.select(from).where(deleted);
        TypedQuery<T> query = this.entityManager.createQuery(criteriaQuery);
        return pageable == null ? new PageImpl<T>(query.getResultList()) : readPage(query, pageable, spec);

    }

}

And then for any bean for which I wish to handle the deletion using the pessimistic method I define it as such

然后对于我希望使用悲观方法处理删除的任何 bean,我将其定义为

package com.existanze.xxx.datastore.repositories;

import com.existanze.xxx.domain.Phone;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;


@RepositoryRestResource
public interface PhoneRepository extends PesimisticRepository<Phone,Integer> {



}

It is important to explain why I wish to override these methods instead of providing custom ones, like findAllButDeleted. The reason is because I also want the pessimistic delete to trickle down to spring-data-rest. So that the HTTP endpoints generated will not need any form of customization.

重要的是要解释为什么我希望覆盖这些方法而不是提供自定义的方法,例如findAllButDeleted. 原因是因为我也希望悲观的删除能够渗透到spring-data-rest. 这样生成的 HTTP 端点就不需要任何形式的定制。

This seems to work only for the findAllmethod. However for the rest of the methods the current exception is thrown.

这似乎只适用于该findAll方法。但是,对于其余方法,抛出当前异常。

$ curl http://localhost:8881/phones/23

$ curl http://localhost:8881/phones/23

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/>
<title>Error 500 </title>
</head>
<body>
<h2>HTTP ERROR: 500</h2>
<p>Problem accessing /phones/23. Reason:
<pre>    org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: object is not an instance of declaring class; nested exception is java.lang.IllegalArgumentException: object is not an instance of declaring class</pre></p>
<hr /><i><small>Powered by Jetty://</small></i>
</body>
</html>

Furthermore, I have read the documentation which allows you to change the default JpaRepository for all Repositories, but again I need to do this on a per repository basis.

此外,我已经阅读了允许您更改所有存储库的默认 JpaRepository 的文档,但我再次需要在每个存储库的基础上执行此操作。

I hope I have been descriptive enough. Please let me know in the comments section if there is something that needs better explanation.

我希望我的描述已经足够了。如果有什么需要更好的解释,请在评论部分告诉我。

回答by Bruno César

You can create a custom repository, something like this:

您可以创建一个自定义存储库,如下所示:

package com.brunocesar.custom.repository.support;

import java.io.Serializable;

import javax.persistence.EntityManager;

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

import com.brunocesar.custom.entity.CustomAbstractEntity;

@NoRepositoryBean
public interface CustomGenericRepository<E extends CustomAbstractEntity, PK extends Serializable> extends
        JpaRepository<E, PK>, JpaSpecificationExecutor<E> {

    EntityManager getEntityManager();

}

package com.brunocesar.custom.repository.support.impl;

import java.io.Serializable;
import java.util.Calendar;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.brunocesar.custom.entity.CustomAbstractEntity;
import com.brunocesar.custom.repository.support.CustomGenericRepository;

@Transactional(readOnly = true)
public class CustomGenericRepositoryImpl<E extends CustomAbstractEntity, PK extends Serializable> extends
        SimpleJpaRepository<E, PK> implements CustomGenericRepository<E, PK> {

    private final EntityManager entityManager;
    private final JpaEntityInformation<E, ?> entityInformation;

    public CustomGenericRepositoryImpl(final JpaEntityInformation<E, ?> entityInformation,
            final EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
        this.entityInformation = entityInformation;
    }

    @Override
    @Transactional
    public void delete(final E entity) {
        Assert.notNull(entity, "Entity object must not be null!");
        entity.setChangeDate(Calendar.getInstance().getTime());
        entity.setDeleted(true);
    }

    @Override
    public List<E> findAll() {
        return super.findAll(this.isRemoved());
    }

    @Override
    public E findOne(final PK pk) {
        return this.findOne(this.isRemovedByID(pk));
    }

    private Specification<E> isRemoved() {
        return new Specification<E>() {

            @Override
            public Predicate toPredicate(final Root<E> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
                return cb.isFalse(root.<Boolean> get("deleted"));
            }

        };
    }

    private Specification<E> isRemovedByID(final PK pk) {
        return new Specification<E>() {

            @Override
            public Predicate toPredicate(Root<E> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                final Predicate id = cb.equal(root.get("id"), pk);
                final Predicate hidden = cb.isFalse(root.<Boolean> get("deleted"));
                return cb.and(id, hidden);
            }

        };
    }

    @Override
    public EntityManager getEntityManager() {
        return this.entityManager;
    }

    protected JpaEntityInformation<E, ?> getEntityInformation() {
        return this.entityInformation;
    }

}

You will need too a custom factory bean to setup your custom repositories. Look like this:

您还需要一个自定义工厂 bean 来设置您的自定义存储库。看起来像这样:

package com.brunocesar.custom.repository.support.factory;

import java.io.Serializable;

import javax.persistence.EntityManager;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import com.brunocesar.custom.repository.support.impl.CustomGenericRepositoryImpl;

public class CustomGenericRepositoryFactoryBean<T extends JpaRepository<S, ID>, S, ID extends Serializable> extends
        JpaRepositoryFactoryBean<T, S, ID> {

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) {
        return new RepositoryFactory(entityManager);
    }

    private static class RepositoryFactory extends JpaRepositoryFactory {

        public RepositoryFactory(final EntityManager entityManager) {
            super(entityManager);
        }

        @Override
        @SuppressWarnings({"unchecked", "rawtypes"})
        protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(
                final RepositoryMetadata metadata, final EntityManager entityManager) {
            final JpaEntityInformation<?, Serializable> entityInformation = this.getEntityInformation(metadata
                    .getDomainType());
            return new CustomGenericRepositoryImpl(entityInformation, entityManager);
        }

        @Override
        protected Class<?> getRepositoryBaseClass(final RepositoryMetadata metadata) {
            return CustomGenericRepositoryImpl.class;
        }

    }

}

And finally, the application context configuration.

最后,应用程序上下文配置。

Common repositories configuration look like this:

常见的存储库配置如下所示:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        p:dataSource-ref="dataSource" p:jpaProperties-ref="jpaProperties" p:jpaVendorAdapter-ref="jpaVendorAdapter"/>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory" />

<jpa:repositories base-package="com.brunocesar.repository"
    transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory" />

And custom, like this (you can or not use separeted EMF and transaction manager):

和自定义,像这样(你可以或不使用独立的 EMF 和事务管理器):

<bean id="entityManagerFactoryCustom" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:dataSource-ref="dataSource" p:jpaProperties-ref="jpaProperties" p:jpaVendorAdapter-ref="jpaVendorAdapter"/>

<bean id="transactionManagerCustom" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactoryCustom" />

<jpa:repositories base-package="com.brunocesar.custom.repository,com.brunocesar.custom.repository.support"
    factory-class="com.brunocesar.custom.repository.support.factory.CustomGenericRepositoryFactoryBean"
    transaction-manager-ref="transactionManagerCustom" entity-manager-factory-ref="entityManagerFactoryCustom" />

Example 1, using JpaRepository:

示例 1,使用 JpaRepository:

package com.brunocesar.repository;

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

import com.brunocesar.entity.CommonEntity;

@Repository
public interface CommonRepository extends JpaRepository<CommonEntity, Long> {

}

Example 2, using custom repository:

示例 2,使用自定义存储库:

package com.brunocesar.custom.repository;

import org.springframework.stereotype.Repository;

import com.brunocesar.custom.entity.CustomEntity;
import com.brunocesar.custom.repository.support.CustomGenericRepository;

@Repository
public interface CustomRepository extends CustomGenericRepository<CustomEntity, Long> {

}

This is part of what I usually do. If you need, I can create a basic application as an example.

这是我通常做的一部分。如果您需要,我可以创建一个基本应用程序作为示例。