Java Persistence API(JPA) 是 Java 的 ORM 框架标准,它为管理关系数据提供了一个标准、基于面向对象的API,开发者可以用极简的代码即可实现对数据的访问和操作。JPA维护了一个 Persistence Context (持久化上下文),在持久化上下文中维护实体的生命周期。主要包含三个方面的内容:
ORM元数据。JPA支持annotion或xml两种形式描述对象-关系映射。
实体操作API。实现对实体对象的CRUD操作。
查询语言。约定了面向对象的查询语言JPQL(Java Persistence Query Language) 。
JPA核心接口
Repository 接口是 Spring Data 的一个核心接口, 是一个标记接口。业务接口继承Repository,则该接口会被IOC容器识别为一个Repository Bean注入到IOC容器中,只要遵循Spring Data的方法定义规范,就无需写实现类。
CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 T save (T entity) ; Iterable save (Iterable<? extends T> entities) ; T findOne (ID id) ; boolean exists (ID id) ; Iterable findAll () ; long count () ; void delete (ID id) ; void delete (T entity) ; void delete (Iterable<? extends T> entities) ; void deleteAll () ;
PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法:
1 2 3 4 Iterable findAll (Sort sort) ; Page findAll (Pageable pageable) ;
JpaRepository:实现了一组 JPA 规范相关的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 List findAll () ; List findAll (Sort sort) ; List save (Iterable<? extends T> entities) ; void flush () ; T saveAndFlush (T entity) ; void deleteInBatch (Iterable entities) ;
在实际开发中直接实现 JpaRepository 接口:
1 2 3 @Repository public interface OrderRepository extends JpaRepository <OrderEntity, Long> { }
查询方式 JPA提供了多种查询方式实现:
JPA接口默认实现接口
基于命名规则查询:find + [By + 属性名 + 查询条件] + [And/Or + 属性名 + 查询条件], 条件关键字如:
Like:模糊查询
In:集合包含查询
NotContaining:不包含查询
Containing:包含查询
OrderBy:排序
First/Top:限制结果数量
@Query 查询
JPA接口默认实现接口 JpaRepository 实现了一些基本的默认 CRUD 方法:
1 2 3 4 5 List<T> findAll () ; Page<T> findAll (Pageable pageable) ; T getOne (ID id) ; T S save (T entity) ;void deleteById (ID id) ;
基于命名规则查询
find + [By + 属性名 + 查询条件] + [And/Or + 属性名 + 查询条件]
命名规则解析机制: Spring Data JPA会根据方法名称进行解析,它会根据方法名解析出查询条件和参数,将其转换为对应的JPQL查询。关键字与SQL对应关系如下:
Keyword
JPQL snippet
And
… where x.lastname = ?1 and x.firstname = ?2
Or
… where x.lastname = ?1 or x.firstname = ?2
Is, Equals
… where x.firstname = ?1
Between
… where x.startDate between ?1 and ?2
LessThan
… where x.age < ?1
LessThanEqual
… where x.age ⇐ ?1
GreaterThan
… where x.age > ?1
GreaterThanEqual
… where x.age >= ?1
After
… where x.startDate > ?1
Before
… where x.startDate < ?1
IsNull
… where x.age is null
IsNotNull, NotNull
… where x.age not null
Like
… where x.firstname like ?1
NotLike
… where x.firstname not like ?1
StartingWith
… where x.firstname like ?1 (parameter bound with appended %)
EndingWith
… where x.firstname like ?1 (parameter bound with prepended %)
Containing
… where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy
… where x.age = ?1 order by x.lastname desc
Not
… where x.lastname <> ?1
In
… where x.age in ?1
NotIn
… where x.age not in ?1
TRUE
… where x.active = true
FALSE
… where x.active = false
IgnoreCase
… where UPPER(x.firstame) = UPPER(?1)
示例:
1 2 Order findByOrderId (Long orderId) ; List<Order> findByUserId (Long userId) ;
@Query 注解查询
1 2 3 4 5 6 7 8 9 10 11 @Query("select count(o) from OrderEntity o where o.userId = ?1") Long countWithFirstname (Long userId) ;@Query("select o from OrderEntity o where o.orderId = :orderId or o.userId = :userId") List<OrderEntity> findByOrderIdAndUserId (@Param("orderId") Long orderId, @Param("userId") Long userId) ;@Query("select o from OrderEntity o where o.userId = :#{#order.orderId}") List<OrderEntity> findByOrderIdAsSpELExpression (@Param("order") OrderEntity order) ;
复杂条件查询 1 2 3 4 5 6 7 Specification<OrderEntity> specification = (root, criteriaQuery, criteriaBuilder) -> { Subquery subQuery = criteriaQuery.subquery(String.class); Root from = subQuery.from(OrderEntity.class); subQuery.select(from.get("userId" )).where(criteriaBuilder.equal(from.get("orderId" ), "123456" )); return criteriaBuilder.and(root.get("userId" ).in(subQuery)); };return orderRepository.findAll(specification);
基于命名规则实现机制 Spring Data JPA在启动时会解析Repository接口中的方法名称,根据命名规则将方法名转换为JPQL查询,如果有@Query注解,则使用注解中定义的查询语句。在底层通过通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象来处理这些方法。
代理对象创建核心类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class JpaRepositoryFactory extends RepositoryFactorySupport { private final EntityManager entityManager; private final QueryExtractor extractor; private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor; private final CrudMethodMetadata crudMethodMetadata; .... public JpaRepositoryFactory (EntityManager entityManager) { Assert.notNull(entityManager, "EntityManager must not be null" ); this .entityManager = entityManager; this .extractor = PersistenceProvider.fromEntityManager(entityManager); this .crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor (); this .entityPathResolver = SimpleEntityPathResolver.INSTANCE; this .queryMethodFactory = new DefaultJpaQueryMethodFactory (extractor); this .queryRewriterProvider = QueryRewriterProvider.simple(); addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor); addRepositoryProxyPostProcessor((factory, repositoryInformation) -> { if (isTransactionNeeded(repositoryInformation.getRepositoryInterface())) { factory.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); } }); ..... } }
RepositoryFactorySupport:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public abstract class RepositoryFactorySupport implements BeanClassLoaderAware , BeanFactoryAware { private final Map<RepositoryInformationCacheKey, RepositoryInformation> repositoryInformationCache; private final List<RepositoryProxyPostProcessor> postProcessors; private Optional<Class<?>> repositoryBaseClass; private @Nullable QueryLookupStrategy.Key queryLookupStrategyKey; private List<QueryCreationListener<?>> queryPostProcessors; private List<RepositoryMethodInvocationListener> methodInvocationListeners; private NamedQueries namedQueries; private ClassLoader classLoader; private QueryMethodEvaluationContextProvider evaluationContextProvider; private BeanFactory beanFactory; private Lazy<ProjectionFactory> projectionFactory; ..... }
QueryExecutorMethodInterceptor:
1 2 3 4 5 6 7 8 9 10 11 12 13 class QueryExecutorMethodInterceptor implements MethodInterceptor { private final RepositoryInformation repositoryInformation; private final Map<Method, RepositoryQuery> queries; private final Map<Method, RepositoryMethodInvoker> invocationMetadataCache = new ConcurrentReferenceHashMap <>(); private final Map<Method, MethodParameter> returnTypeMap = new ConcurrentHashMap <>(); private final QueryExecutionResultHandler resultHandler; private final NamedQueries namedQueries; private final List<QueryCreationListener<?>> queryPostProcessors; private final RepositoryInvocationMulticaster invocationMulticaster; .... }
代理创建主要步骤
1 2 3 @EnableJpaRepositories( bootstrapMode = BootstrapMode.DEFERRED // 可选:DEFAULT, LAZY, DEFERRED )
1 2 3 4 5 6 7 8 public class JpaRepositoryFactoryBean <T extends Repository <S, ID>, S, ID> extends RepositoryFactoryBeanSupport <T, S, ID> { @Override protected RepositoryFactorySupport createRepositoryFactory () { return new JpaRepositoryFactory (entityManager); } }
代理实现的核心机制
1 2 3 4 @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return SimpleJpaRepository.class; }
1 2 3 4 5 6 7 8 9 10 11 12 @Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy (@Nullable Key key, ValueExpressionDelegate valueExpressionDelegate) { JpaQueryConfiguration queryConfiguration = new JpaQueryConfiguration (); return Optional.of(JpaQueryLookupStrategy.create( entityManager, queryMethodFactory, key, queryConfiguration )); }
1 2 3 4 5 6 7 8 public JpaRepositoryFactory (EntityManager entityManager) { addRepositoryProxyPostProcessor((factory, repositoryInformation) -> { if (isTransactionNeeded(repositoryInformation.getRepositoryInterface())) { factory.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); } }); }
代理对象的创建过程: CrudMethodMetadata 代理创建
1 2 3 4 5 6 7 8 class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor { CrudMethodMetadata getCrudMethodMetadata () { ProxyFactory factory = new ProxyFactory (); factory.addInterface(CrudMethodMetadata.class); factory.setTargetSource(new ThreadBoundTargetSource ()); return (CrudMethodMetadata) factory.getProxy(this .classLoader); } }
动态方法实现
对于查询方法,生成对应的 JPQL 或 SQL
对于默认方法,使用 SimpleJpaRepository 中的实现
对于自定义查询(@Query),使用注解中定义的查询语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 protected RepositoryFragments getRepositoryFragments ( RepositoryMetadata metadata, EntityManager entityManager, EntityPathResolver resolver, CrudMethodMetadata crudMethodMetadata) { boolean isQueryDslRepository = QUERY_DSL_PRESENT && QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()); if (isQueryDslRepository) { .... QuerydslJpaPredicateExecutor<?> querydslJpaPredicateExecutor = new QuerydslJpaPredicateExecutor <>( getEntityInformation(metadata.getDomainType()), entityManager, resolver, crudMethodMetadata); invokeAwareMethods(querydslJpaPredicateExecutor); return RepositoryFragments.just(querydslJpaPredicateExecutor); } return RepositoryFragments.empty(); }
1 2 3 protected RepositoryFragments getRepositoryFragments (RepositoryMetadata metadata) { }
小结 整个过程的工作流程:
启动时扫描 Repository 接口
为每个接口创建 JpaRepositoryFactory
通过工厂创建代理对象:
基础实现来自 SimpleJpaRepository
查询方法通过方法名或注解解析
添加事务、缓存等切面
注入代理对象到使用处
实体注解 基础注解 @Entity 标记一个类为实体类,表示该类将映射到数据库表。
1 2 @Entity public class Employee { ... }
@Table 指定实体类映射到的数据库表信息。
1 2 3 4 @Entity @Table(name = "employees", schema = "hr", uniqueConstraints = {@UniqueConstraint(columnNames = {"email", "company_id"})}) public class Employee { ... }
@Id 标记实体类的主键字段。
@GeneratedValue 指定主键的生成策略。
1 2 3 @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
生成策略包括:
GenerationType.IDENTITY: 依赖数据库的自增长特性
GenerationType.SEQUENCE: 使用数据库序列生成主键
GenerationType.TABLE: 使用表来模拟序列
GenerationType.AUTO: 由持久性提供者自动选择合适的策略
@Column 指定字段映射到表中列的详细信息。
1 2 @Column(name = "user_name", length = 50, nullable = false, unique = true) private String username;
主要属性:
name: 列名(默认为字段名)
length: 列长度
nullable: 是否允许为空
unique: 是否唯一约束
insertable: 插入时是否包含此字段
updatable: 更新时是否包含此字段
precision: 浮点数的精度
scale: 浮点数的小数位数
@Transient 标记字段不会被持久化到数据库。
1 2 @Transient private String tempValue;
@Temporal 指定日期类型字段映射到的SQL日期类型。
1 2 3 4 5 @Temporal(TemporalType.DATE) private Date birthDate;@Temporal(TemporalType.TIMESTAMP) private Date createdAt;
类型包括:
TemporalType.DATE: 只包含日期信息
TemporalType.TIME: 只包含时间信息
TemporalType.TIMESTAMP: 包含日期和时间信息
有用的注解 @Version 用于实现乐观锁。
1 2 @Version private Long version;
@Where 定义实体类的查询条件。
1 2 3 @Entity @Where(clause = "deleted = false") public class Document { ... }
定义计算列。
1 2 @Formula("(SELECT COUNT(c.id) FROM comments c WHERE c.post_id = id)") private Long commentCount;
@SQLRestriction:实体级原生SQL条件,简化动态查询 1 2 3 4 5 6 7 8 9 10 @Entity @Table(name = "product") @SQLRestriction("deleted = 0") public class Product { @Column(columnDefinition = "int default 0") private Integer deleted ; }
@Filter: 参数化过滤条件,支持会话级控制 1 2 3 4 5 6 7 8 9 10 @Entity @Table(name = "product") @FilterDef(name = "filterByDeletedAndStock", parameters = { @ParamDef(name = "state", type = Integer.class), @ParamDef(name = "stock", type = Integer.class) }) @Filters({ @Filter(name = "filterByDeletedAndStock", condition = "deleted=:state and stock >:stock") }) public class Product {}