Spring Data JPA Hibernate => JPA(Java Persistence API) => Spring Data JPA,三者是层层封装的关系,Spring Data JPA基于JPA,而JPA又基于Hibernate。
Hibernate本身为自动ORM框架,所以基于此框架再封装的框架也皆为自动ORM框架。
Persistence Context
一个临时的、和当前事务绑定的“工作内存” ,所有在这个上下文里的实体,JPA 都会帮你跟踪变化、保证唯一性,并在事务提交时把变化同步到数据库 。
持久化上下文(Persistence Context) 是JPA中一个核心概念,它本质上是一个由 EntityManager 管理的“实体缓存 + 变更追踪区” 。
Entity
在Spring Data JPA中,实体(Entity)具有三种状态,瞬态(Transient)、托管态(Managed)、游离态(Detached)。
后文讲解将围绕以下简化过的实体代码且默认这两种实体的Repository存在:
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "t_user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private List<Order> orders; }
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "t_order") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNo; private Integer status; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; }
Transient(瞬态)
瞬态实体一般为刚创建的实体,这个实体没有被载入到持久化上下文,同时这个实体在数据库中也并不存在。
存在依据为主键是否存在。
@Override public void registerNewUser (String username) { User user = new User (null , username, null ); userRepository.save(user); }
Managed(托管态)
托管态实体是经过持久化操作后被载入到持久化上下文的实体,它的确在数据库中存在,从数据库查询得到的实体一般托管态。
托管态的实体在事务结束临界点 会自动检查实体是否修改并落库。
@Transactional @Override public void changeUsername (Long id, String username) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername(username); }
Detached(游离态)
游离态实体确实在数据库中存在,但是不在持久化上下文中,也就不被JPA管理,发生的更改也不会被追踪,这类实体一般出现在事务结束后。
游离态实体可由持久化操作再次被JPA管理,转化为托管态。
以下示例代码块与托管态示例代码块相似,值得注意的是,以下示例代码块方法并没有@Transactional注解。@Override public void changeUsername (Long id, String username) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername(username); userRepository.save(user); }
Removed(删除态)
删除态实体为被delete方法处理后的实体,若事务仍在,他们依然存在于持久化上下文中,他们会在事务结束后将删除落库。
删除态实体不要进行再次持久化进入托管态。你仍然可以使用实体中的一些属性,但不建议这么做,尤其是懒加载属性,会抛出LazyInitializationException。
@Override public void deleteUser (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); userRepositoy.delete(user); }
Repository Repository<T, ID> ← 最顶层标记接口 ↑ CrudRepository<T, ID> ← 提供基本 CRUD ↑ PagingAndSortingRepository<T, ID> ← 在 Crud 基础上增加分页、排序 ↑ JpaRepository<T, ID> ← JPA 专用扩展(Flush、批量操作等) ↑ 自定义 Repository 接口(继承 JpaRepository 或其他)
By default, methods inherited from CrudRepository inherit the transactional configuration from SimpleJpaRepository . For read operations, the transaction configuration readOnly flag is set to true. All others are configured with a plain @Transactional so that default transaction configuration applies. Repository methods that are backed by transactional repository fragments inherit the transactional attributes from the actual fragment method.
默认情况下,从 CrudRepository继承的方法会从 SimpleJpaRepository继承其事务配置。对于读操作 ,事务配置的readOnly标志会被设为true;其他(写)操作则会使用普通的 @Transactional注解,以便应用默认的事务配置。那些由事务性仓库片段(transactional repository fragments) 支持的方法,会继承该片段方法实际的事务属性。
—— Spring Data JPA Docs
在Spring Data JPA下SimpleJpaRepository实现了JpaRepository、CrudRepository、PagingAndSortingRepository中的通用CRUD方法,同时也为这些方法添加了@Transactional注解。
以下是SimpleJpaRepository常用且带有@Transactional注解的方法源码:
@Repository @Transactional(readOnly = true) public class SimpleJpaRepository <T, ID> implements JpaRepositoryImplementation <T, ID> { @Override @Transactional public void deleteById (ID id) { } @Override @Transactional public void delete (T entity) { } @Override @Transactional public void deleteAllById (Iterable<? extends ID> ids) { } @Override @Transactional public void deleteAll (Iterable<? extends T> entities) { } @Override @Transactional public <S extends T > S save (S entity) { } @Override @Transactional public <S extends T > List<S> saveAll (Iterable<S> entities) { } }
我们都知道delete相关方法负责删除实体,save方法负责插入、更新实体,不难发现带有@Transactional注解的方法都是处理写入操作的。而在SimpleJpaRepository类上有@Transactional(readOnly = true)注解,说明除去这几个涉及写入操作的方法,其余方法都具有只读事务。
值得注意的是,在我们使用自定义方法(根据命名规则自动生成的数据库操作方法)时是不带有事务的,这对读操作不会产生较大影响,除非你要求保证读一致性(在同一事务内多次读保持数据一致),但是写操作必须显式声明事务,这解释了为什么同样的操作,为什么自定义的写方法执行不成功,而默认实现方法却没有问题,因为默认实现方法自带事务。
public interface UserRepository extends JpaRepository <User, Long> { void deleteUserById (Long id) ; }
@RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; public void defaultDeleteMethod1 (Long id) { userRepository.deleteById(id); } public void defaultDeleteMethod2 (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); userRepository.delete(user); } public void myDeleteMethod (Long id) { userRepository.deleteUserById(id); } @Transactional public void myTransactionalDeleteMethod (Long id) { userRepository.deleteUserById(id); } }
Spring Transaction
事务(Transaction)是一组操作的集合,这些操作要么全部成功,要么什么都不做(rollback)。
事务特性
特性
说明
原子性 (Atomicity)
事务是不可分割的工作单位,要么一起成功,要么什么都不做,事务在执行过程中出现错误会执行回滚,回到事务执行前的状态。
一致性 (Consistency)
一个事务执行后,数据库的所有数据必须符合所有要求,一致性是事务的最终目标,其余特性均为一致性的实现手段。
隔离性 (Isolation)
数据库允许多个事务同时对数据进行读写,同时防止并发条件下出现数据不一致情况。
持久性 (Durability)
事务结束后,对数据的修改是永久性的(只要不是删除或再次修改)。
其中,原子性 与隔离性 较为重要。
事务隔离级别 在介绍事务隔离级别前,需要引入以下几个概念:
问题
说明
脏读
一个事务读到了另一个未提交事务 修改过的数据。
不可重复读
在同一个事务中,两次读取同一行数据 ,结果却不一样,因为中间被别的事务修改并提交 了。
幻读
在同一个事务中,两次执行相同条件的查询 ,返回的行数或内容集合不同,因为中间有事务插入或删除了符合该条件的新行 。
在了解由事务并发可能导致的问题后我们介绍事务隔离级别:
隔离级别
脏读
不可重复读
幻读
READ_UNCOMMITTE
✅
✅
✅
READ_COMMITTED
✗
✅
✅
REPEATABLE_READ
✗
✗
✅
SERIALIZABLE
✗
✗
✗
事务的隔离级别越高,异常发生概率越低,但性能也越低,所以要根据实际情况选择合适的隔离级别,不要总是选择隔离性最高,最安全的SERIALIZABLE。
事务传播
类型
说明
REQUIRED (default)
如果当前存在事务,则加入;否则新建一个事务。
SUPPORTS
如果当前存在事务,则加入;否则以非事务方式执行。
MANDATORY
必须存在事务,否则抛出异常。
REQUIRES_NEW
新建事务,如果当前存在事务则挂起。
NOT_SUPPORTED
以非事务方式执行,如果当前存在事务则挂起。
NEVER
以非事务方式执行,如果当前存在事务则抛出异常。
NESTED
如果当前存在事务,则在嵌套事务中执行。
在讲解事务传播机制前我们需要补充代码背景,代码背景如下,所需依赖已注入,后续讲解将专注于方法代码块,而不是类与依赖。
我们假设用户一定存在,且订单也一定存在,异常情况不在我们考虑范围内。
@RequiredArgsConstructor @Service public class UserService { private final UserRepository userRepository; private final OrderService orderService; public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } } @RequiredArgsConstructor @Service public class OrderService { private final OrderRepository orderRepository; public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); } }
REQUIRED @Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
SUPPORTS @Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.SUPPORTS) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.SUPPORTS) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
MANDATORY @Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.MANDATORY) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.MANDATORY) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
REQUIRES_NEW @Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
NOT_SUPPORTED @Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.NOT_SUPPORTED) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
NEVER @Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.NEVER) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.NEVER) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
NESTED
⚠️注意与REQUIRES_NEWS进行区分。
REQUIRES_NEWS会开启平级事务,而NESTED会开启子事务。
REQUIRES_NEWS开启的平级事务不会受调用方所处事务控制,调用方事务回滚不会引起新开启事务的回滚,因为他们是两个事务,平级的。NESTED开启的子事务受调用方所处事务控制,调用方事务(父事务)的回滚会引起新开启事务的回滚。
简而言之,REQUIRES_NEWS事务、NESTED事务都不会影响调用方事务,但REQUIRES_NEWS事务不受调用方事务影响,而NESTED事务会受调用方事务影响。
@Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.NESTED) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } }
@Transactional public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); } @Transactional(propagation = Propagation.NESTED) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
public void userMethod (Long id) { User user = userRepository.findById(id).orElseThrow( () -> new EntityNotFoundException ("User not found." ) ); user.setUsername("Jackson" ); userRepository.save(user); try { orderService.orderMethod(id); } catch (Exception e) {} user.setUsername("Michael" ); userRepository.save(user); throw new RuntimeException (); } @Transactional(propagation = Propagation.NESTED) public void orderMethod (Long id) { List<Order> orders = orderRepository.findOrdersByUser_Id(id); for (Order order : orders) { order.setStatus(1 ); orderRepository.save(order); } throw new RuntimeException (); }
控制方式
声明式事务
编程式事务
定义方式
通过注解/XML配置
通过代码手动控制
侵入性
低(非侵入)
高(侵入业务代码)
灵活性
一般
高
维护成本
低
高
适用场景
大多数标准业务
复杂事务逻辑
事务边界
方法级
任意代码块
异常处理
自动(根据配置)
手动控制
动态控制
困难
灵活
性能
略好(AOP优化)
略差(对象创建开销)
感谢腾讯元宝生成的以下示例代码。
声明式事务 @Transaction Annotation @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final UserRepository userRepository; @Transactional public Order createOrderWithNestedPointDeduction (Long userId, String orderNo) { User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException ("用户不存在" )); Order order = new Order (); order.setOrderNo(orderNo); order.setStatus(0 ); order.setUser(user); order = orderRepository.save(order); try { deductPointsNested(user.getId(), 100 ); } catch (Exception e) { log.warn("扣减积分失败,但订单已创建: {}" , e.getMessage()); } return order; } @Transactional(propagation = Propagation.NESTED) public void deductPointsNested (Long userId, Integer points) { User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException ("用户不存在" )); if (user.getPoints() < points) { throw new RuntimeException ("积分不足" ); } } }
编程式事务 TransactionTemplate @Service @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final UserRepository userRepository; private final TransactionTemplate transactionTemplate; public Order createOrderProgrammatic (Long userId, String orderNo) { return transactionTemplate.execute(status -> { try { User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException ("用户不存在" )); Order order = new Order (); order.setOrderNo(orderNo); order.setStatus(0 ); order.setUser(user); return orderRepository.save(order); } catch (Exception e) { status.setRollbackOnly(); log.error("创建订单失败" , e); throw e; } }); } public void createOrderWithStockCheck (Long userId, String orderNo) { User user = transactionTemplate.execute(status -> { return userRepository.findById(userId) .orElseThrow(() -> new RuntimeException ("用户不存在" )); }); Order order = transactionTemplate.execute(status -> { Order o = new Order (); o.setOrderNo(orderNo); o.setStatus(0 ); o.setUser(user); return orderRepository.save(o); }); try { transactionTemplate.execute(status -> { return true ; }); } catch (Exception e) { log.warn("扣减库存失败: {}" , e.getMessage()); order.setStatus(2 ); orderRepository.save(order); } } }
@Service @RequiredArgsConstructor public class ComplexOrderService { private final OrderRepository orderRepository; private final UserRepository userRepository; private final PlatformTransactionManager transactionManager; public void processComplexOrder (OrderRequest request) { DefaultTransactionDefinition def = new DefaultTransactionDefinition (); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setTimeout(60 ); TransactionStatus status = transactionManager.getTransaction(def); try { User user = userRepository.findByUsername(request.getUsername()) .orElseGet(() -> { User newUser = new User (); newUser.setUsername(request.getUsername()); return userRepository.save(newUser); }); Order mainOrder = new Order (); mainOrder.setOrderNo(generateOrderNo()); mainOrder.setStatus(0 ); mainOrder.setUser(user); mainOrder = orderRepository.save(mainOrder); for (OrderItem item : request.getItems()) { Order subOrder = new Order (); subOrder.setOrderNo(generateSubOrderNo()); subOrder.setStatus(0 ); subOrder.setUser(user); orderRepository.save(subOrder); } transactionManager.commit(status); log.info("订单处理完成,主订单号: {}" , mainOrder.getOrderNo()); } catch (DataAccessException e) { log.error("数据操作失败,回滚事务" , e); transactionManager.rollback(status); throw e; } catch (Exception e) { if (isCriticalError(e)) { log.error("严重错误,回滚事务" , e); transactionManager.rollback(status); } else { log.warn("非严重错误,提交事务" , e); transactionManager.commit(status); } throw e; } } private boolean isCriticalError (Exception e) { return e instanceof DataIntegrityViolationException; } private String generateOrderNo () { return "ORD" + System.currentTimeMillis(); } private String generateSubOrderNo () { return "SUB" + System.currentTimeMillis() + ThreadLocalRandom.current().nextInt(1000 ); } }