All Downloads are FREE. Search and download functionalities are using the official Maven repository.

top.openyuan.jpa.repository.SimpleJpaRepositoryExt Maven / Gradle / Ivy

package top.openyuan.jpa.repository;

import top.openyuan.jpa.common.constant.JpaConfigConstant;
import top.openyuan.jpa.common.util.StringUtils;
import org.hibernate.Session;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaEntityInformationSupport;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.SingularAttribute;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * 继承了 {@link SimpleJpaRepository} 类,实现了 {@link JpaRepositoryExt} 接口的 Fast JPA Repository 基础实现类.
 *
 * @author lzy on 2020-12-04.
 * @since v2.4.0
 */
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepositoryExt extends SimpleJpaRepository implements JpaRepositoryExt {

    private static final String ENTITIES_NULL_MSG = "Entities must not be null!";

    private final JpaEntityInformation entityInformation;

    private final EntityManager em;

    /**
     * 构造方法.
     *
     * @param entityInformation JPA 实体信息类,不能为 {@literal null}.
     * @param entityManager 实体管理器类,不能为 {@literal null}.
     */
    public SimpleJpaRepositoryExt(JpaEntityInformation entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityInformation = entityInformation;
        this.em = entityManager;
    }

    /**
     * 构造方法.
     *
     * @param domainClass JPA 实体类的 class,不能为 {@literal null}.
     * @param em 实体管理器类,不能为 {@literal null}.
     */
    public SimpleJpaRepositoryExt(Class domainClass, EntityManager em) {
        this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em);
    }

    /**
     * 批量新增实体类集合,该方法仅用于新增,不能用于有更新数据的场景,需要调用方事先做好处理.
     *
     * 

该方法会批量 {@code flush} 数据到数据库中,每次默认的批量大小为 {@link JpaConfigConstant#DEFAULT_BATCH_SIZE}. * 该方法相比 {@link #saveAll(Iterable)} 性能更高,但仅能用于新增插入数据的场景。

* * @param entities 实体类集合 * @param 泛型实体类 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void saveBatch(Iterable entities) { this.saveBatch(entities, JpaConfigConstant.DEFAULT_BATCH_SIZE); } /** * 批量新增实体类集合,该方法仅用于新增,不能用于有更新数据的场景,需要调用方事先做好处理. * *

该方法会批量 {@code flush} 数据到数据库中,每次批量大小为可通过参数设置. * 该方法相比 {@link #saveAll(Iterable)} 性能更高,但仅能用于新增插入数据的场景。

* * @param entities 实体类集合 * @param batchSize 批量大小 * @param 泛型实体类 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void saveBatch(Iterable entities, int batchSize) { Assert.notNull(entities, ENTITIES_NULL_MSG); int i = 0; for (S entity : entities) { this.em.persist(entity); if (++i % batchSize == 0) { this.em.flush(); this.em.clear(); } } } /** * 批量更新实体类集合,该方法仅用于更新,不能用于含有新增数据的场景,需要调用方事先做好处理. * *

该方法会批量 {@code flush} 数据到数据库中,每次默认的批量大小为 {@link JpaConfigConstant#DEFAULT_BATCH_SIZE}. * 该方法相比 {@link #saveAll(Iterable)} 性能更高,但仅能用于更新数据的场景。

* * @param entities 可迭代的实体类集合 * @param 泛型实体类 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void updateBatch(Iterable entities) { this.updateBatch(entities, JpaConfigConstant.DEFAULT_BATCH_SIZE); } /** * 批量更新实体类集合,该方法仅用于更新,不能用于含有新增数据的场景,需要调用方事先做好处理. * *

该方法会批量 {@code flush} 数据到数据库中,每次批量大小可通过参数设置. * 该方法相比 {@link #saveAll(Iterable)} 性能更高,但仅能用于更新数据的场景。

* * @param entities 可迭代的实体类集合 * @param batchSize 批量大小 * @param 泛型实体类 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void updateBatch(Iterable entities, int batchSize) { Assert.notNull(entities, ENTITIES_NULL_MSG); int i = 0; Session session = this.em.unwrap(Session.class); for (S entity : entities) { session.update(entity); if (++i % batchSize == 0) { this.em.flush(); this.em.clear(); } } } /** * 保存或更新实体类中非 null 属性的字段值. * *
    *
  • 如果实体的主键 ID 为空,说明是新增的情况,就插入一条新的数据;
  • *
  • 如果实体的主键 ID 不为空,会先判断是否存在该 ID 的数据,如果不存在也会新增插入一条数据; * 否则说明是更新的情况,会仅更新实体类属性中不为 null 值的属性字段到数据库中;
  • *
* * @param entity 实体类 * @param 泛型实体类 * @return 原实体类,注意:如果是更新的情况,返回的值不一定有数据库中之前的值 */ @Transactional(rollbackFor = RuntimeException.class) @Override public S saveOrUpdateByNotNullProperties(S entity) { Assert.notNull(entity, "Entity must not be null."); // 获取对象实体 ID,如果为空,就直接新增即可. ID id = (ID) this.entityInformation.getId(entity); if (StringUtils.isEmptyObject(id)) { this.em.persist(entity); return entity; } // 如果根据 ID 查询的实体不存在,也要新增插入一条新的记录. Optional entityOptional = super.findById(id); if (!entityOptional.isPresent()) { this.em.persist(entity); return entity; } // 此时说明,该实体在数据库中已经存在,就将当前所有值非 null 的属性复制到原来的数据库实体对象中进行保存. T oldEntity = entityOptional.get(); BeanUtils.copyProperties(entity, oldEntity, this.getNullProperties(entity)); this.em.merge(oldEntity); return entity; } /** * 新增或更新所有实体类中非 null 属性的字段值. * *

注意:该方法仅仅是循环调用 {@link #saveOrUpdateByNotNullProperties(Object)} 方法, * 保存每条数据时会先查询判断是否存在,再进行插入或者更新.

* *
    *
  • 如果实体的主键 ID 为空,说明是新增的情况,就插入一条新的数据;
  • *
  • 如果实体的主键 ID 不为空,会先判断是否存在该 ID 的数据,如果不存在也会新增插入一条数据; * 否则说明是更新的情况,会仅更新实体类属性中不为 null 值的属性字段到数据库中;
  • *
* * @param entities 可迭代的实体类集合 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void saveOrUpdateAllByNotNullProperties(Iterable entities) { Assert.notNull(entities, ENTITIES_NULL_MSG); for (S entity : entities) { saveOrUpdateByNotNullProperties(entity); } } /** * 根据 ID 的集合数据删除这些数据,注意该方法仅是循环调用 {@link #deleteById(Object)} 方法而已,性能并不高. * * @param ids ID 集合 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void deleteByIds(Iterable ids) { Assert.notNull(ids, "The given ids must not be null!"); for (ID id : ids) { super.deleteById(id); } } /** * 根据 ID 的集合批量删除数据这些数据,删除期间会批量转换为 {code in} 条件来匹配删除, * 性能相比 {@link #deleteByIds(Iterable)} 也更高,每次默认的批量大小为 {@link JpaConfigConstant#DEFAULT_BATCH_SIZE}. * * @param ids ID 集合 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void deleteBatchByIds(Iterable ids) { this.deleteBatchByIds(ids, JpaConfigConstant.DEFAULT_BATCH_SIZE); } /** * 根据 ID 的集合批量删除数据这些数据,删除期间会批量转换为 {code in} 条件来匹配删除, * 性能相比 {@link #deleteByIds(Iterable)} 也更高,可自定义批量大小的参数. * * @param ids ID 集合 * @param batchSize 批量大小 */ @Transactional(rollbackFor = RuntimeException.class) @Override public void deleteBatchByIds(Iterable ids, int batchSize) { Assert.notNull(ids, "The given ids must not be null!"); Assert.isTrue(batchSize > 0, "The given batchSize must not be <= 0."); // 获取到实体名称和 ID 属性名称,并生成用于批量删除的 in 条件 SQL. final String entityName = this.entityInformation.getEntityName(); SingularAttribute idAttribute = this.entityInformation.getIdAttribute(); final String idName = idAttribute == null ? "id" : idAttribute.getName(); String sql = StringUtils.format("delete from {} where {} in :batch_ids", entityName, idName); int i = 0; List batchIds = new ArrayList<>(); for (ID id : ids) { if (id == null) { continue; } batchIds.add(id); if (++i % batchSize == 0 && !batchIds.isEmpty()) { this.doBatchDelete(sql, batchIds); batchIds.clear(); } } // 如果最后 batchIds 不为空,则再继续删除剩余的数据. if (!batchIds.isEmpty()) { this.doBatchDelete(sql, batchIds); } } /** * 真正执行批量删除的方法. * * @param sql in 条件的删除 SQL * @param batchIds 要批量删除的 ID 数据 */ private void doBatchDelete(String sql, List batchIds) { this.em.createQuery(sql) .setParameter("batch_ids", batchIds) .executeUpdate(); } /** * 通过反射获取对象实体中所有值为 {@code null} 的属性名称的数组. * * @param entity 实体对象 * @return 数组 */ private String[] getNullProperties(Object entity) { BeanWrapper beanWrapper = new BeanWrapperImpl(entity); PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors(); List nullProperties = new ArrayList<>(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { String propertyName = propertyDescriptor.getName(); Object propertyValue = beanWrapper.getPropertyValue(propertyName); if (propertyValue == null) { nullProperties.add(propertyName); } } return nullProperties.toArray(new String[0]); } }