Spring Data JPA: 实现自定义Repository

时间:2023-12-30 15:46:38

一、前言

  由于项目中的 实体(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;
} }

  自定义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> {
}

  然后,使所有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);
} }

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

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

  至此,完成了适配。

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