
本项目代码: https://gitee.com/yunjiao-source/tutorials4j
在企业级 Java 应用中,数据访问层往往需要处理大量重复性工作:主键生成、乐观锁、审计信息(创建人/时间、修改人/时间)、数据状态管理(正常、禁用、删除等)。同时,为了保持代码的整洁与可维护性,我们通常会抽取公共的基类和接口。
本文分析的代码正是这样一套基于 Spring Data JPA 的通用数据访问层设计。它通过定义一系列核心实体接口、基础 DTO/Entity 类、Repository 与 Service 抽象,以及一个JPA 查询工具,为业务开发提供了开箱即用的能力。下面我们将逐一拆解其设计意图与实现技巧。
所有实体相关类均位于 tutorials4j.framework.common.core.entity 包中,它们定义了不同维度的数据契约。
Entitypublic interface Entity extends Serializable { }Serializable,确保实体可以安全地在分布式环境(如 RPC、Session)中传输。IdEntity<ID>public interface IdEntity<ID extends Serializable> extends Entity {
ID getId();
void setId(ID pk);
default boolean isNew() { return getId() == null; }
}Long、String、UUID 等)。isNew() 方法:方便判断实体是否未持久化,可用于 EntityManager.persist() 与 merge() 的智能选择。VersionEntitypublic interface VersionEntity extends Entity {
Integer getVersion();
void setVersion(Integer version);
}@Version 注解提供标准方法,实现乐观锁并发控制。AuditingEntitypublic interface AuditingEntity extends Entity {
String getCreatedBy(); // 创建人
LocalDateTime getCreatedDate(); // 创建时间
String getLastModifiedBy(); // 最后修改人
LocalDateTime getLastModifiedDate();
// 对应的 setter ...
}@CreatedBy、@CreatedDate 等注解配合使用。StatusEntitypublic interface StatusEntity extends Entity {
DataStatus getDataStatus();
void setDataStatus(DataStatus status);
default boolean isNormal() { return DataStatus.NORMAL.equals(getDataStatus()); }
default boolean isDeleted() { ... }
// 其他状态判断 ...
}DataStatus:定义了 NORMAL、RESERVED、DISABLED、LOCKED、EXPIRED、DELETED 六种常见数据状态。entity.isDeleted() 提升可读性。BaseDTO —— 跨层传输的数据载体BaseDTO 实现了 IdEntity<Long>、VersionEntity、AuditingEntity:
public class BaseDTO implements IdEntity<Long>, VersionEntity, AuditingEntity {
private Long id;
private Integer version;
private LocalDateTime createDate = LocalDateTime.now();
private LocalDateTime lastModifiedDate = LocalDateTime.now();
private String createBy;
private String lastModifiedBy;
// 实现方法及 equals/hashCode(基于 id)
}equals/hashCode:仅基于 id 比较,符合 DTO 的语义(只要 id 相同即代表同一数据记录)。BaseEntity —— JPA 实体的超级父类@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements IdEntity<Long>, VersionEntity, AuditingEntity {
@Id
@SnowflakeIdGenerator // 自定义雪花算法主键生成器
private Long id;
@Version
private Integer version;
@Column(updatable = false)
@CreatedDate
private LocalDateTime createDate = LocalDateTime.now();
@LastModifiedDate
private LocalDateTime lastModifiedDate;
@CreatedBy
private String createBy;
@LastModifiedBy
private String lastModifiedBy;
// getter/setter ...
}@MappedSuperclass:子实体可以继承这些字段映射,无需重复定义。AuditingEntityListener 配合 @CreatedDate 等注解,由 Spring Data 自动填充时间与操作人(需要配置审计基础设施)。@SnowflakeIdGenerator 是一个自定义的 ID 生成器注解,可集成分布式 ID 方案,避免数据库自增的局限性。BaseStatusEntity —— 带状态管理的实体@MappedSuperclass
public class BaseStatusEntity extends BaseEntity implements StatusEntity {
@Enumerated(EnumType.STRING)
private DataStatus status;
@PrePersist
public void prePersist() {
if (status == null) status = DataStatus.NORMAL;
}
@PreRemove
public void preRemove() {
if (isReserved()) throw new RuntimeException("保留数据,不能删除");
}
}NORMAL。RESERVED(保留),则抛出异常,防止误删。这是软删除的一种补充保护机制。BaseRepository@NoRepositoryBean
public interface BaseRepository<E extends Entity, ID extends Serializable>
extends JpaRepository<E, ID>, JpaSpecificationExecutor<E> {
}ReadableService → WriteableService → BaseServiceReadableService 提供了丰富的查询方法:
default E findById(ID id) { ... } // 不存在抛异常
default List<E> findAll(Specification<E> spec) { ... }
default Page<E> findByPage(Pageable pageable) { ... }
// 重载:排序、Example 查询、分页快捷方式等WriteableService 扩展写入能力:
default E save(E domain);
default void delete(E entity);
default void deleteAllInBatch();
// ... flush, saveAllAndFlush 等BaseService 目前只是 WriteableService 的别名,预留后续扩展。
default 方法实现通用逻辑,减少实现类的重复代码。getRepository() 的实现,即可获得全套 CRUD 能力。@Service
public class UserService implements BaseService<User, Long> {
@Autowired
private UserRepository userRepository;
@Override
public BaseRepository<User, Long> getRepository() {
return userRepository;
}
// 业务方法可直接调用继承的 findAll, save 等
public Page<User> findNormalUsers(Pageable pageable) {
Specification<User> spec = (root, q, cb) -> cb.equal(root.get("status"), DataStatus.NORMAL);
return findByPage(spec, pageable);
}
}JPAUtils 接口(静态方法工具)解决了一个痛点:使用 JPA Criteria 时重复 join 会导致 from ... join ... join ... 中产生多个相同的 join。它提供了复用已有 join 的能力:
static <Z, X> Join<Z, X> join(Root<Z> root, String attribute, JoinType joinType) {
// 如果已存在相同属性和类型的 join,直接返回,否则创建新的
}root.join("orders") 而导致的 SQL 语法错误(重复 join)。innerJoin、leftJoin、rightJoin,以及 like(String) 自动拼接 %。组件 | 解决的问题 | 带来的价值 |
|---|---|---|
| 泛型约束 | 类型安全,避免混入非实体对象 |
| 区分新建/已有实体 | 便于 |
| 审计字段重复定义 | 自动填充,统一维护 |
| 数据状态散落各处 | 集中管理状态,提供语义化判断 |
| 每个 service 都要写 CRUD | 减少样板代码 90% |
| Criteria 重复 join | 提升动态查询的可靠性 |
SnowflakeIdGenerator 的实现是否考虑了时钟回拨、高并发场景。BaseStatusEntity.preRemove 抛出 RuntimeException 较为粗暴,建议定义明确的业务异常(如 DataReservedException)。DELETED 状态仅是一个标记,未在查询时自动过滤。可以结合 @Where 注解或 BaseRepository 覆盖 findAll 默认行为来实现自动过滤。AuditorAware 从当前上下文中获取用户信息,代码中未体现。通过分析以上代码,我们看到了一套设计清晰、层次分明的数据访问层抽象。它充分利用了 Spring Data JPA 的特性(审计、Specification、@MappedSuperclass),并通过接口继承和默认方法提供了极高的可复用性。开发者只需要继承 BaseEntity(或 BaseStatusEntity)和 BaseService,就能获得健壮的 CRUD 能力,从而专注于核心业务逻辑。
这种设计模式在企业级项目中有非常高的参考价值,值得在实践中借鉴与演进。
扩展阅读建议:
AuditorAware。BaseStatusEntity 改造为真正的软删除(@SQLDelete + @Where)。BaseService 增加通用缓存切面(如 Spring Cache)。原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。