【问题标题】:Spring Data - Overriding default methods for some repositoriesSpring Data - 覆盖某些存储库的默认方法
【发布时间】:2015-03-13 01:37:42
【问题描述】:

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

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

所有PesimisticRepository 所做的只是以不同方式处理已删除@Entity 的概念。 deleted 实体的deleted 属性为NOT NULL。这意味着可以由PesimisticRepository 处理的@Entity 必须具有 deleted 属性。

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

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

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> {
}

为此我提供了一个扩展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);

    }

}

然后对于我希望使用悲观方法处理删除的任何 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> {



}

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

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

$ 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>

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

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

【问题讨论】:

    标签: java spring spring-data spring-data-jpa spring-data-rest


    【解决方案1】:

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

    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;
        }
    
    }
    

    您还需要一个自定义工厂 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;
            }
    
        }
    
    }
    

    最后是应用上下文配置。

    常见的仓库配置如下:

    <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" />
    

    和自定义,像这样(你可以或不使用分离的 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" />
    

    示例 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> {
    
    }
    

    示例 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> {
    
    }
    

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

    【讨论】:

    • 你好布鲁诺,在我的问题中我说。 “此外,我已经阅读了允许您更改所有存储库的默认 JpaRepository 的文档,但我需要在每个存储库的基础上执行此操作。”覆盖工厂会使所有存储库成为 CustomGenericRepository 的后代,这是我试图避免的。
    • 是的,我读到了你说的。这是一种满足您需要的方法,为自定义实体创建自定义存储库。顺便说一句,与其说是技术问题,不如说是概念问题。
    • 但是如何避免不扩展 CustomGenericRepository 的实体成为 CustomGenericRepositoryImpl?您的示例发生了什么?我需要区分存储库
    • 我想魔法将在 getRepositoryBaseClassgetTargetRepository 方法中 - 你可以检查传入的 RepositoryMetadata 并根据元数据返回不同的类型?
    猜你喜欢
    • 1970-01-01
    • 2016-08-14
    • 2012-10-13
    • 1970-01-01
    • 2019-01-16
    • 1970-01-01
    • 2018-06-25
    • 2023-03-17
    • 2013-11-14
    相关资源
    最近更新 更多