使用 jakarta persistence 创建基础的 BaseRepository

220 阅读3分钟

背景

在业务系统中,大多数功能都是对数据库表的操作,一般的软件开发组织会有一些对数据操作的规范性要求。如:每一条数据都要记录创建人、创建时间、最后更新人、最后更新时间;删除数据使用字段标记,而不是真的彻底删除等。

这种需求在一些ORM工具中早已有了实现,如Mybatis-Plus,因此我这里并没有什么创新性的工作。

需求

根据我的需要,拟定需求如下:

  1. 记录创建人、创建时间、最后更新人、最后更新时间;
  2. 使用字段标记来删除记录,即软删除;
  3. 只使用jakarta persistence;
  4. 基类包含常用的CRUD实现,子类只需继承即可获得相应能力;

实现

实体类基类

public class BaseEntity implements Serializable {

    @Serial
    private static final long serialVersionUID = -2375924982927170763L;

    @Id
    @GeneratedValue(generator = "snowflakeIdGenerator")
    @GenericGenerator(name = "snowflakeIdGenerator", type = SnowflakeIdGenerator.class)
    @Column(name = "id", length = 18, updatable = false, nullable = false)
    private String id;

    @Column(name = "deleted")
    private Boolean deleted = false;

}
@Data
@SuperBuilder
@MappedSuperclass
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@EntityListeners(AuditEventListener.class)
public class BaseEntityAudit extends BaseEntity {

    @Serial
    private static final long serialVersionUID = 5650218908042423624L;

    @Column(name = "created_by", updatable = false)
    private String createdBy;

    @CreationTimestamp
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    // TODO
    @Column(name = "updated_by")
    private String updatedBy;

    @UpdateTimestamp
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}
@Data
@SuperBuilder
@MappedSuperclass
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class BaseDO extends BaseEntityAudit{
}

Repository 基类

@Slf4j
@NoRepositoryBean
@Transactional(readOnly = true)
public abstract class BaseRepository<DO extends BaseDO> {

    protected final static String ID = "id";
    protected final static String DELETED = "deleted";
    protected final static String CREATED_BY = "createdBy";
    protected final static int DELETED_FLAG = 1;
    protected final static int NOT_DELETED_FLAG = 0;

    @Autowired
    protected EntityManager entityManager;

    protected abstract Class<DO> getDomainClass();

    @Transactional
    public DO create(DO item) {
        item.setId(null);
        entityManager.persist(item);
        entityManager.flush();
        return item;
    }

    public Optional<DO> find(String id) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<DO> query = cb.createQuery(getDomainClass());
        Root<DO> root = query.from(getDomainClass());

        query
                .select(root)
                .where(cb.and(cb.equal(root.get(ID), id)
                ));

        return entityManager.createQuery(query).getResultStream().findFirst();
    }

    public Optional<DO> find(String id, String userId) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<DO> query = cb.createQuery(getDomainClass());
        Root<DO> root = query.from(getDomainClass());

        query
                .select(root)
                .where(cb.and(cb.equal(root.get(ID), id),
                        cb.equal(root.get(CREATED_BY), userId)
                ));

        return entityManager.createQuery(query).getResultStream().findFirst();
    }

    public List<DO> findAll(String userId) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<DO> query = cb.createQuery(getDomainClass());
        Root<DO> root = query.from(getDomainClass());

        query
                .select(root)
                .where(cb.and(cb.equal(root.get(CREATED_BY), userId),
                        cb.equal(root.get(DELETED), NOT_DELETED_FLAG)
                ));

        return entityManager.createQuery(query).getResultStream().collect(Collectors.toList());
    }

    public Page<DO> findAll(Pageable pageable) {
        BiFunction<CriteriaBuilder, Root<DO>, Predicate> condition = (cb, root)
                -> cb.and(cb.equal(root.get(DELETED), NOT_DELETED_FLAG));
        return innerFindAll(pageable, condition);
    }

    public Page<DO> findAll(Pageable pageable, String userId) {
        BiFunction<CriteriaBuilder, Root<DO>, Predicate> condition = (cb, root)
                -> cb.and(cb.equal(root.get(DELETED), NOT_DELETED_FLAG), cb.equal(root.get(CREATED_BY), userId));
        return innerFindAll(pageable, condition);
    }

    private Page<DO> innerFindAll(Pageable pageable, BiFunction<CriteriaBuilder, Root<DO>, Predicate> condition) {
        // SELECT LIST //
        // basic objects
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<DO> query = cb.createQuery(getDomainClass());
        Root<DO> root = query.from(getDomainClass());

        // build sql
        query.select(root).where(condition.apply(cb, root));
        if (pageable.getSort().isSorted()) {
            List<Order> orders = new ArrayList<>();
            for (Sort.Order order : pageable.getSort()) {
                Order jpaOrder = order.isAscending() ? cb.asc(root.get(order.getProperty())) : cb.desc(root.get(order.getProperty()));
                orders.add(jpaOrder);
            }
            query.orderBy(orders);
        }

        // execute sql
        List<DO> resultList = entityManager.createQuery(query)
                .setFirstResult((int) pageable.getOffset())
                .setMaxResults(pageable.getPageSize())
                .getResultList();

        // SELECT COUNT //
        // basic objects
        CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
        Root<DO> countRoot = countQuery.from(getDomainClass());

        // build sql
        countQuery.select(cb.count(countRoot));
        query.select(root).where(condition.apply(cb, countRoot));

        // execute sql
        Long total = entityManager.createQuery(countQuery).getSingleResult();

        return new PageImpl<>(resultList, pageable, total);
    }

    @Transactional
    public void updateSelective(DO entity) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaUpdate<DO> update = cb.createCriteriaUpdate(getDomainClass());
        Root<DO> root = update.from(getDomainClass());

        Field[] fields = entity.getClass().getFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value;
            try {
                value = field.get(entity);
            } catch (IllegalAccessException e) {
                throw BaseException.builder()
                        .exceptionEnum(ExceptionEnum.UPDATE_FAILED)
                        .build();
            }
            if (value != null) {
                update.set(root.get(field.getName()), value);
            }
        }

        update
                .where(cb.equal(root.get(ID), entity.getId()));

        entityManager.createQuery(update).executeUpdate();
    }


    @Transactional
    public void update(DO entity) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaUpdate<DO> update = cb.createCriteriaUpdate(getDomainClass());
        Root<DO> root = update.from(getDomainClass());

        update
                .set(root, entity)
                .where(cb.equal(root.get(ID), entity.getId()));

        entityManager.createQuery(update).executeUpdate();
    }

    @Transactional
    public void delete(@NotBlank String id) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaUpdate<DO> update = cb.createCriteriaUpdate(getDomainClass());
        Root<DO> root = update.from(getDomainClass());

        update
                .set(root.get(DELETED), DELETED_FLAG)
                .where(cb.and(cb.equal(root.get(ID), id),
                        cb.equal(root.get(DELETED), NOT_DELETED_FLAG)
                ));

        entityManager.createQuery(update).executeUpdate();
    }

    @Transactional
    public void delete(String id, String userId) {

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaUpdate<DO> update = cb.createCriteriaUpdate(getDomainClass());
        Root<DO> root = update.from(getDomainClass());

        update
                .set(root.get(DELETED), DELETED_FLAG)
                .where(cb.and(cb.equal(root.get(ID), id),
                        cb.equal(root.get(CREATED_BY), userId),
                        cb.equal(root.get(DELETED), NOT_DELETED_FLAG)
                ));

        entityManager.createQuery(update).executeUpdate();
    }

}

项目开源地址:github.com/xufeiranfre…