背景
在业务系统中,大多数功能都是对数据库表的操作,一般的软件开发组织会有一些对数据操作的规范性要求。如:每一条数据都要记录创建人、创建时间、最后更新人、最后更新时间;删除数据使用字段标记,而不是真的彻底删除等。
这种需求在一些ORM工具中早已有了实现,如Mybatis-Plus,因此我这里并没有什么创新性的工作。
需求
根据我的需要,拟定需求如下:
- 记录创建人、创建时间、最后更新人、最后更新时间;
- 使用字段标记来删除记录,即软删除;
- 只使用jakarta persistence;
- 基类包含常用的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…