1. 简介
Spring Data 的核心价值在于屏蔽底层数据存储的差异,让业务代码更专注于逻辑本身,而不是被各种持久化技术的细节缠住。无论是 JPA、MongoDB 还是其他存储引擎,Spring Data 都提供了一套统一的抽象层。
本文将系统梳理 Spring Data、Spring Data JPA 和 Spring Data MongoDB 中最常用、最实用的注解。这些注解是日常开发的“利器”,掌握它们能让你的数据访问层写得更简洁、更高效,避免踩坑。
2. Spring Data 通用注解
2.1. @Transactional
这是控制事务的“开关”。当你需要精确控制某个方法的事务行为时,直接在方法或类上加这个注解即可。
@Transactional
void pay() {}
- ✅ 类上使用:该类所有 public 方法都启用事务。
- ⚠️ 方法级覆盖:如果类上已标注,但某个方法需要不同的事务配置(比如
propagation
或isolation
),直接在该方法上重新标注即可,会覆盖类级别的设置。
这个注解的参数非常丰富,比如传播行为、隔离级别、超时时间等。具体配置细节可以参考官方文档或相关文章,这里不展开,毕竟读者都是老手了。
2.2. @NoRepositoryBean
这个注解解决了一个很实际的问题:如何创建一个只用来被继承的“基类”Repository,而不想让 Spring 容器真的去实例化它。
想象一下,你希望所有 Repository 都有一个通用的查询方法,比如 Optional<T> findById(ID id)
。你可能会创建一个父接口:
@NoRepositoryBean
interface MyUtilityRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Optional<T> findById(ID id);
}
- ✅
@NoRepositoryBean
的作用就是告诉 Spring Data:别为这个接口生成代理 Bean,它只是个“模板”。 - ✅ 子接口不受影响:当你创建具体的 Repository 时,Spring 会正常为其创建 Bean。
@Repository
interface PersonRepository extends MyUtilityRepository<Person, Long> {}
⚠️ 注意:
findById
方法在 Spring Data JPA 2.0+ 已是CrudRepository
的标准方法,这里仅作示例。但在实际项目中,你可能会定义findActiveByXXX
这类通用方法,这时@NoRepositoryBean
就非常实用。
2.3. @Param
在自定义查询中,给参数起个“名字”,让 JPQL 或原生 SQL 更清晰、更易维护。
@Query("FROM Person p WHERE p.name = :name")
Person findByName(@Param("name") String name);
- ✅ 使用
:name
的语法来引用参数。 - ✅ 代码可读性高,一眼就能看出
:name
对应的是哪个参数。 - ❌ 避免使用
?1
、?2
这种位置参数,一旦参数顺序改变,查询就可能出错,是典型的“脆弱代码”。
2.4. @Id
标记实体类中的主键字段。这是最基础也最重要的注解之一。
class Person {
@Id
Long id;
// ...
}
- ✅ 实现无关性:无论你底层用的是 JPA、MongoDB 还是其他存储,
@Id
的语义都是一致的,这使得你的实体类可以更容易地在不同存储间迁移或复用。
2.5. @Transient
标记一个字段不需要被持久化。这个字段只存在于内存中,不会映射到数据库的任何列。
class Person {
// ...
@Transient
int age; // 可能是根据 birthDate 计算出来的
// ...
}
- ✅ 用途广泛:比如存放临时计算结果、DTO 中的额外字段等。
- ✅ 同样具有实现无关性,与
@Id
一样,是跨存储的通用约定。
2.6. @CreatedBy
, @LastModifiedBy
, @CreatedDate
, @LastModifiedDate
这几个注解是实现数据审计(Auditing)的利器,可以自动填充创建人、修改人、创建时间、修改时间。
public class Person {
// ...
@CreatedBy
User creator;
@LastModifiedBy
User modifier;
@CreatedDate
Date createdAt;
@LastModifiedDate
Date modifiedAt;
// ...
}
- ✅ 只需在实体上标注,Spring Data 会在保存/更新时自动填充。
- ⚠️ 关键前提:要让
@CreatedBy
和@LastModifiedBy
生效,必须集成 Spring Security。Spring 会通过SecurityContextHolder
获取当前认证的用户(Principal)。 - ✅
@CreatedDate
和@LastModifiedDate
通常不需要额外依赖,会自动使用当前时间。
3. Spring Data JPA 注解
3.1. @Query
定义 JPA 查询的“终极武器”,支持 JPQL 和原生 SQL。
JPQL 示例:
@Query("SELECT COUNT(*) FROM Person p") long getPersonCount();
命名参数示例(推荐):
@Query("FROM Person p WHERE p.name = :name") Person findByName(@Param("name") String name);
原生 SQL 示例:
@Query(value = "SELECT AVG(p.age) FROM person p", nativeQuery = true) int getAverageAge();
- ✅
nativeQuery = true
表示使用数据库原生 SQL。 - ✅ 注意原生 SQL 是针对数据库表名(
person
),而 JPQL 是针对实体名(Person
)。
- ✅
3.2. @Procedure
简单粗暴地调用数据库的存储过程(Stored Procedure)。
第一步:在实体上声明存储过程(使用 JPA 原生注解):
@NamedStoredProcedureQueries({ @NamedStoredProcedureQuery( name = "count_by_name", procedureName = "person.count_by_name", parameters = { @StoredProcedureParameter( mode = ParameterMode.IN, name = "name", type = String.class), @StoredProcedureParameter( mode = ParameterMode.OUT, name = "count", type = Long.class) } ) }) class Person {}
name
: 在代码中引用该过程的逻辑名称。procedureName
: 数据库中存储过程的真实名称。parameters
: 定义输入(IN)和输出(OUT)参数。
第二步:在 Repository 中调用:
@Procedure(name = "count_by_name") long getCountByName(@Param("name") String name);
- ✅ Spring Data JPA 会自动处理 IN/OUT 参数的映射,非常方便。
3.3. @Lock
为查询方法指定锁模式,用于处理并发场景。
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Person p WHERE p.id = :id")
Optional<Person> findByIdForUpdate(@Param("id") Long id);
- ✅ 常用场景:在更新前锁定某条记录,防止并发修改。
- ✅ 可用模式包括:
PESSIMISTIC_READ
/PESSIMISTIC_WRITE
(悲观锁)OPTIMISTIC
(乐观锁)NONE
(无锁)
3.4. @Modifying
任何修改数据的操作(UPDATE, DELETE, INSERT)都必须加上这个注解,否则会报错。
@Modifying
@Query("UPDATE Person p SET p.name = :name WHERE p.id = :id")
void changeName(@Param("id") long id, @Param("name") String name);
- ✅
@Modifying
告诉 Spring Data 这是一个修改操作,需要在事务中执行。 - ⚠️ 默认情况下,
@Modifying
操作会清除该实体在当前持久化上下文(Persistence Context)中的缓存。如果不想清除,可以设置clearAutomatically = false
。
3.5. @EnableJpaRepositories
启动 JPA Repository 支持的“总开关”。
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
class PersistenceJPAConfig {}
- ✅ 必须配合
@Configuration
使用。 - ✅
basePackages
指定 Spring 扫描 Repository 接口的包路径。 - ✅ Spring Boot 用户注意:只要 classpath 下有 Spring Data JPA 依赖,这个注解会自动配置,通常无需手动添加。
4. Spring Data MongoDB 注解
4.1. @Document
标记一个类为 MongoDB 的文档(Document),相当于 JPA 中的 @Entity
。
@Document
class User {}
- ✅ 可以指定集合(Collection)名称:
@Document(collection = "user") class User {}
- 如果不指定,MongoDB 会默认使用类名的小写形式作为集合名。
4.2. @Field
定义实体字段在 MongoDB 文档中的键名(Key),相当于 JPA 中的 @Column
。
@Document
class User {
// ...
@Field("email")
String emailAddress;
// ...
}
- ✅ 这样,Java 字段
emailAddress
在 MongoDB 中存储为键email
。 - ✅ 非常适合处理命名规范不一致的情况,比如 Java 用 camelCase,而数据库用 snake_case。
4.3. @Query
在 MongoDB Repository 中定义自定义查询,使用的是 MongoDB 的 JSON 查询语法。
@Query("{ 'name' : ?0 }")
List<User> findUsersByName(String name);
- ✅
?0
表示第一个方法参数,?1
表示第二个,以此类推。 - ✅ 也可以使用命名参数:
@Query("{ 'name' : :#{#name} }") List<User> findUsersByName(@Param("name") String name);
4.4. @EnableMongoRepositories
启动 MongoDB Repository 支持的“总开关”。
@Configuration
@EnableMongoRepositories(basePackages = "com.example.mongo.repository")
class MongoConfig {}
- ✅ 用法和
@EnableJpaRepositories
几乎完全一致。 - ✅ Spring Boot 用户注意:只要 classpath 下有 Spring Data MongoDB 依赖,会自动配置,通常无需手动添加。
5. 总结
本文系统地梳理了 Spring Data 生态中最核心的注解,覆盖了通用、JPA 和 MongoDB 三大场景。
- ✅ 掌握
@Transactional
、@Modifying
、@Query
是玩转数据访问的基础。 - ✅
@NoRepositoryBean
和审计注解能显著提升代码的复用性和规范性。 - ✅ JPA 的
@Procedure
和@Lock
,以及 MongoDB 的@Document
和@Field
,都是处理特定场景的“杀手锏”。
这些注解看似简单,但组合起来威力巨大。理解它们的原理和最佳实践,能让你在开发数据层时游刃有余,少走弯路。
示例代码已整理至 GitHub: