Spring Data JPA: 实现自定义Repository

一、前言

  由于项目中的 实体(entity)默认都是继承一个父类(包含一些公共的属性,比如创建时间,修改时间,是否删除,主键id)。为了实现逻辑删除,一般会自己实现RepositoryFactoryBean 和 Repository。但是由于多个团队开发的结果,表的结构没有同一,也就是会出现有的表没有基础父类对应的字段,这样就会导致自定义的jpa repository操作这些表就会出错。

二、最开始实现

  默认父类

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

import org.hibernate.annotations.GenericGenerator;

@MappedSuperclass
public abstract class AbsIdEntity implements Serializable {

    private static final long serialVersionUID = 7988377299341530426L;

    @Id
    @GenericGenerator(name="uuid", strategy="uuid")
    @GeneratedValue(generator="uuid")
    @Column(name="id")
    protected String id;

    @Column(name = "creationtime")
    protected Timestamp creationTimestamp = new Timestamp(System.currentTimeMillis());
    @Column(name = "lastmodifiedtime")
    protected Timestamp modificationTimestamp = new Timestamp(System.currentTimeMillis());
    
    @Column(name = "dr")
    protected int dr;// 是否删除。0:未删除;1:已删除

    /**
     * 主键,对应id字段
     */
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    /**
     * 创建日期,对应ts_insert字段
     */
    public Timestamp getCreationTimestamp() { return creationTimestamp; }
    public void setCreationTimestamp(Timestamp creationTimestamp) { this.creationTimestamp = creationTimestamp; }

    /**
     * 修改日期,对应ts_update字段
     */
    public Timestamp getModificationTimestamp() { return modificationTimestamp; }
    public void setModificationTimestamp(Timestamp modificationTimestamp) { this.modificationTimestamp = modificationTimestamp; }

    /**
     * 是否删除,对应dr字段
     * @return
     */
    public int getDr() {
        return dr;
    }
    public void setDr(int dr) {
        this.dr = dr;
    }

}
View Code

  自定义Repository接口

  • 添加BaseDao接口
  • BaseDao继承了JpaSpecificationExecutor、CrudRepository,这样可以保证所有Repository都有基本的增删改查以及分页等方法。
  • 在BaseDao上添加@NoRepositoryBean标注,这样Spring Data Jpa在启动时就不会去实例化BaseDao这个接口
import java.io.Serializable;

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

/**
 * Data Access Object基类,已经包含了常用的增删改查操作。<br>
 * 使用时只需要继承接口,不需要实现类,spring自动通过cglib生成实现类
 * 
 * @param <T>
 *            实体类型
 */
@NoRepositoryBean
public interface BaseDao<T extends AbsIdEntity> extends
        CrudRepository<T, Serializable>/* JpaRepository<T, Serializable> */, JpaSpecificationExecutor<T> {
}
View Code

  然后,使所有Repository接口都继承BaseDao

  实现BaseRepository

  定义好自定义的方法后,我们现在通过一个基本的Repository类来实现该方法:

  首先添加BaseDaoImpl类,继承SimpleJpaRepository类,使其拥有Jpa Repository的基本方法。

  我们发现Repository有两个构造函数:

  • SimpleJpaRepository(JpaEntityInformation entityInformation, EntityManager entityManager)
  • SimpleJpaRepository(Class domainClass, EntityManager em)

  这里我们实现第二个构造函数,拿到domainClassEntityManager两个对象。因为我们要实现的是知道某个Repository是否支持某个领域对象的类型,因此在实现构造函数时我们将domainClass的信息保留下来。

import java.io.Serializable;
import java.sql.Timestamp;

import javax.persistence.EntityManager;

import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;

import com.yyjz.icop.base.dao.BaseDao;

@Transactional
public class BaseDaoImpl<T extends AbsIdEntity> extends SimpleJpaRepository<T, Serializable> implements BaseDao<T> {

    @SuppressWarnings("unused")
    private final EntityManager entityManager;

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

    public BaseDaoImpl(JpaEntityInformation<T, Serializable> information, EntityManager entityManager) {
        super(information, entityManager);
        this.entityManager = entityManager;
    }

    @Override
    public <S extends T> S save(S entity) {
        entity.setModificationTimestamp(new Timestamp(System.currentTimeMillis()));
        return super.save(entity);
    }

    /**
     * 只做逻辑删除
     */
    @Override
    public void delete(T entity) {
        entity.setDr(1);
        save(entity);
    }

    @Override
    public void delete(Serializable id) {
        T entity = findOne(id);
        entity.setDr(1);
        this.save(entity);
    }

}
View Code

  RepositoryFactoryBean 实现

  接下来我们来创建一个自定义的BaseDaoFactoryBean来代替默认的RepositoryFactoryBeanRepositoryFactoryBean负责返回一个RepositoryFactory,Spring Data Jpa 将使用RepositoryFactory来创建Repository具体实现,这里我们用BaseDaoImpl代替SimpleJpaRepository作为Repository接口的实现。这样我们就能够达到为所有Repository添加或修改自定义方法的目的。

import xxx.AbsIdEntity;
import org.springframework.data.jpa.repository.JpaRepository;
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.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

public class BaseDaoFactoryBean<R extends JpaRepository<T, Serializable>, T extends AbsIdEntity> extends JpaRepositoryFactoryBean<R, T, Serializable> {

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) {
        return new JpaRepositoryFactory(entityManager) {

            protected SimpleJpaRepository<T, Serializable> getTargetRepository(
                    RepositoryInformation information,    EntityManager entityManager) {
                return new BaseDaoImpl((Class<T>) information.getDomainType(), entityManager);
            }

            @Override
            protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
                return BaseDaoImpl.class;
            }
        };
    }
}
View Code

  jpa 配置文件

<!-- Spring Data Jpa配置 -->
<jpa:repositories base-package="com.xxx"
    transaction-manager-ref="transactionManager"
    entity-manager-factory-ref="entityManagerFactory"
    factory-class="xxx.BaseDaoFactoryBean"><!-- 自定义RepositoryFactoryBean -->
</jpa:repositories>

三、改进之后

  由于有的表没有默认父类AbsIdEntity对应的字段,导致生成 Repository 在操作表的时候会报错。需要修改的就是RepositoryFactoryBean的实现逻辑。对于继承了AbsIdEntity的实体类,返回自定义的BaseRepository(也就是BaseDaoImpl),否则就返回SimpleJpaRepository。注意自定义RepositoryFactoryBean的泛型也做了修改。

import xxx.AbsIdEntity;
import org.springframework.data.jpa.repository.JpaRepository;
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.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import javax.persistence.EntityManager;
import java.io.Serializable;

public class BaseDaoFactoryBean<R extends JpaRepository<T, Serializable>, T> extends JpaRepositoryFactoryBean<R, T, Serializable> {

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(final EntityManager entityManager) {
        return new JpaRepositoryFactory(entityManager) {

            protected SimpleJpaRepository<T, Serializable> getTargetRepository(
                    RepositoryInformation information,    EntityManager entityManager) {
                Class<T> domainClass = (Class<T>) information.getDomainType();
                if(AbsIdEntity.class.isAssignableFrom(domainClass)) {
                    return new BaseDaoImpl(domainClass, entityManager);
                } else { 
                    return new SimpleJpaRepository(domainClass, entityManager);
                }
            }

            @Override
            protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
                return metadata.getDomainType().isAssignableFrom(AbsIdEntity.class) ? BaseDaoImpl.class : SimpleJpaRepository.class;
            }
        };
    }
}
View Code

  至此,完成了适配。

 

 生活不止眼前的bug,还有诗和远方。。。

posted @ 2017-03-03 10:21  hjzqyx  阅读(2933)  评论(0编辑  收藏  举报