著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
Spring事务
Spring事务机制主要包括声明式事务和编程式事务,声明式事务让我们从复杂的事务处理中得到解脱,编程式事务在实际开发中得不到广泛使用,仅供学习参考。
事务抽象
spring的事务管理提供了统一的API接口支持不同的资源,提供声明式事务方便与Spring框架集成。spring的事务管理器使用抽象的设计方式实现,以下为spring事务管理器中的逻辑实现代码 (精简了一部分,突出核心逻辑)
## 事务状态 public interface TransactionStatus extends SavepointManager{ boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); boolean isCompleted(); } ## 事务定义 public interface TransactionDefinition{ int getPropagationBehavior(); #传播行为 int getIsolationLevel(); #隔离级别 String getName(); #事务名 int getTimeout(); #超时时间 boolean isReadOnly(); #是否只读 } ## 事务管理器 public interface PlatformTransactionManager{ TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
事务的传播行为
Spring在 TransactionDefinition
接口中规定了 7 种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时,事务是如何进行传播的
事务传播的7种行为:
事务传播行为类型 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
当使用 PROPAGATION_NESTED
时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。
事务隔离级别
spring如果没有指定事务隔离级别的话,则spring的事务隔离级别跟数据库的隔离级别走,数据库是什么隔离级别,spring就是什么隔离级别。
Spring事务的隔离级别:
- ISOLATION_DEFAULT(默认): 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. 另外四个与JDBC的隔离级别相对应
- ISOLATION_READ_UNCOMMITTED(未提交读取): 这是事务最低的隔离级别,它会让另外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。
- ISOLATION_READ_COMMITTED(已提交读取): 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
- ISOLATION_REPEATABLE_READ(可重复读取): 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
- ISOLATION_SERIALIZABLE(序列化): 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。
接下来看一下代码方式与标签方式的事务实现:
## 代码方式实现 public OrderService{ @Autowire PlatformTransactionManager txManager; void buyTicket(BuyTicketDTO dto){ DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // 设置事务传播行为 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try{ ## 执行业务代码 txManager.commit(status); }catch(Exception e){ txManager.rollback(status); } } } ## Transaction标签实现方式 public OrderService{ @Transactonal void buyTick(BuyTicketDTO dto){ // get and begin transaction manager from context try{ /**业务代码*/ // auto commit transaction }catch(Exception e){ // auto rollback transaction } } }
策略接口PlatformTransactionManager
事务管理器的常见实现有:
DataSourceTransactionManager
、JpTransactionManager
、JmsTransactionManager
、JtaTransactionManager
JPA简介及事务实现
JPA是Java的一个规范(Java持久性API)。 它用于在Java对象和关系数据库之间保存数据。 JPA充当面向对象的领域模型和关系数据库系统之间的桥梁。 由于JPA只是一个规范,它本身不执行任何操作。 它需要一个实现。 因此,像Hibernate,TopLink和iBatis这样的ORM工具实现了JPA数据持久性规范。
关于JPA事务实例的代码:
domian实体对象
@Entity(name = "customer") @Data public class Customer { ## id 自增长 @Id @GeneratedValue private Long id; ## 唯一索引 @Column(name = "user_name", unique = true) private String username; private String password; private String role; }
dao 接口
// 继成JpaRepository中的方法,其中已经包含基本的CRUD public interface CustomerRepository extends JpaRepository<Customer, Long> { Customer findOneByUsername(String username); }
service 业务操作
以下2种事务实现的效果是一样的,意在告诉大家如何使用代码的方式实现与注解声明事务相同的效果。
@Service public class CustomerServiceTxInAnnotation { private static final Logger LOG = LoggerFactory.getLogger(CustomerServiceTxInAnnotation.class); @Autowired private CustomerRepository customerRepository; ## 使用注解的方式声明事务存在 @Transactional public Customer create(Customer customer) { LOG.info("CustomerService In Annotation create customer:{}", customer.getUsername()); if (customer.getId() != null) { throw new RuntimeException("用户已经存在"); } customer.setUsername("Annotation:" + customer.getUsername()); return customerRepository.save(customer); } }
@Service public class CustomerServiceTxInCode { private static final Logger LOG = LoggerFactory.getLogger(CustomerServiceTxInCode.class); @Autowired private CustomerRepository customerRepository; @Autowired private PlatformTransactionManager transactionManager; public Customer create(Customer customer) { LOG.info("CustomerService In Code create customer:{}", customer.getUsername()); if (customer.getId() != null) { throw new RuntimeException("用户已经存在"); } ## 使用代码的方式来声明事务存在 DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE); ## 当使用ISOLATION_SERIALIZABLE级别时,如果外部没事务存在,则本身创建事务,,所以submitError方法抛出异常可以回滚 //def.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); ## 当使用PROPAGATION_REQUIRED级别时,如果外部没事务存在,则本身也不存在事务,,所以submitError方法抛出异常依然可以保存数据成功 def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); def.setTimeout(15); TransactionStatus status = transactionManager.getTransaction(def); try { customer.setUsername("Code:" + customer.getUsername()); customerRepository.save(customer); submitError(); transactionManager.commit(status); return customer; } catch (Exception e) { transactionManager.rollback(status); throw e; } } private void submitError(){ throw new RuntimeException("some Data error ") } }
controller 控制层
@RestController @RequestMapping("/api/customer") public class CustomerResource { private static final Logger LOG = LoggerFactory.getLogger(CustomerResource.class); @Autowired private CustomerServiceTxInAnnotation customerService; @Autowired private CustomerServiceTxInCode customerServiceInCode; @Autowired private CustomerRepository customerRepository; @PostMapping("/annotation") public Customer createInAnnotation(@RequestBody Customer customer) { LOG.info("CustomerResource create in annotation create customer:{}", customer.getUsername()); return customerService.create(customer); } @PostMapping("/code") public Customer createInCode(@RequestBody Customer customer) { LOG.info("CustomerResource create in code create customer:{}", customer.getUsername()); return customerServiceInCode.create(customer); } @GetMapping("") public List<Customer> getAll() { return customerRepository.findAll(); } }
接下来看一下程序的执行结果及JPA事务的管理过程:
Loca lhost :~ mav larns Locathost :~ mavlarn $ curl http://localhost:8080/api/customer[] localhost :~ mavlarns localhost :~ mavlarn $ curl - X POST - d '{" username ":" imoocz ", pplication / json 'http://localhost:8080/api/customer/ code (" id ":1," username ":"imooc2"," password ":"111111"," role ":" USER "} localhost :~ mavlarns Locathost :~ mavlarns Locaihost :~ mavtar curl http://localhost:8e80/api/customer [" id ":1," username : 'imooc2," password ":"111111"," role ":" USER "}] localhost :~ mavlarn $ localhost :~ mavlarn $ curl - X I PosT - d '{" username ":"imooc1", " pasSword :"1l11l1", pplication / json 'http://localhost:8080/api/ customer / annotation 注解方式插入一条据 [“ id ":2,“ username ”:“imooc1"," password ":"1lll1"," role ":" USER } localhost :~ mavlarn $ locathost :~ mavlarn $ localhost :~ mavlarn $ curl http://localthost:8080/api/customer 查询出2条数据 [" id ":1," username ":"imooc2"," password ":"111111"," role ":" USER "},(" id ":2," " USER "}] localhost :~ mavlarn $ Localhost :~ mavlarn $ localhost :~ mavlarns $ curl - X POST - d '" username ":"imooc1", pplication / json 'http://localthost:8080/api/customer/annotat1on再次插入相同数据包吃,数据插入失败” timestamp ”:1516894291878," status ":500," error ":" Internal Server Error " exception ”:” org . springf ityViolationException "," message ": could not execute statement ; SQL [ n / a ]; constraint [\" UK _QFRM6 DEX _5 ON PUBLIC . CUSTOMER ( USER _ NAME ) VALUES ('imooc1',2 Statement :\ ninsert into customer ser _ name ) values ( null ,7,7,7)[23505-196]]; nested exception is org . hibernate . exception . Const : could not execute statement "," path ":"/ api / customer / annotation "} localhost :~ mavlarn $ 没有数据 " password “:“111111”," role ":" USER 代码方式插入一条数据 " ro Le ":" USER " username :"inooc1”,“ pas " password :"111111”," role ”:" USER 2) N "; SQL
在整个事务管理过程中使用的是Spring事务控制,并且由相关ORM框架实现JPA规范
JMS事务原理
Spring JMS Session
- 通过Session进行事务管理操作
- Session 是一个thread-bound(线程范围内)
- 事务上下文:一个线程一个Session
2种Spring JMS事务类型
- Session管理的事务:原生事务
- 外部管理的事务:JmsTransactionManager、JTA
Srping JMS事务机制过程
Session原生事务:
JmsTransactionManager 外部管理的事务:
##注解方式注入Spring Bean @EnableJms @Configuration public class JmsConfig { private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class); @Bean public JmsTemplate initJmsTemplate(ConnectionFactory connectionFactory) { LOG.debug("init jms template with converter."); JmsTemplate template = new JmsTemplate(); ## JmsTemplate使用的connectionFactory跟JmsTransactionManager使用的必须是同一个,不能在这里封装成caching之类的。 template.setConnectionFactory(connectionFactory); return template; } ## 这个用于设置 @JmsListener使用的containerFactory @Bean public JmsListenerContainerFactory<?> msgFactory(ConnectionFactory connectionFactory, DefaultJmsListenerContainerFactoryConfigurer configurer, PlatformTransactionManager transactionManager) { DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory(); factory.setTransactionManager(transactionManager); factory.setCacheLevelName("CACHE_CONNECTION"); factory.setReceiveTimeout(10000L); configurer.configure(factory, connectionFactory); return factory; } @Bean public PlatformTransactionManager transactionManager(ConnectionFactory connectionFactory) { return new JmsTransactionManager(connectionFactory); } }
@Service public class CustomerService { private static final Logger LOG = LoggerFactory.getLogger(CustomerService.class); @Autowired JmsTemplate jmsTemplate; @Autowired private PlatformTransactionManager transactionManager; @PostConstruct public void init() { jmsTemplate.setReceiveTimeout(3000); } ## 原生事务 @JmsListener(destination = "customer:msg:new", containerFactory = "msgFactory") public void handle(String msg) { LOG.debug("Get JMS message to from customer:{}", msg); String reply = "Replied - " + msg; jmsTemplate.convertAndSend("customer:msg:reply", reply); if (msg.contains("error")) { simulateError(); } } ## JmsTransactionManager事务 @JmsListener(destination = "customer:msg2:new", containerFactory = "msgFactory") public void handle2(String msg) { LOG.debug("Get JMS message2 to from customer:{}", msg); DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setTimeout(15); TransactionStatus status = transactionManager.getTransaction(def); try { String reply = "Replied-2 - " + msg; jmsTemplate.convertAndSend("customer:msg:reply", reply); if (!msg.contains("error")) { transactionManager.commit(status); } else { transactionManager.rollback(status); } } catch (Exception e) { transactionManager.rollback(status); throw e; } } private void simulateError() { throw new RuntimeException("some Data error."); } }
Spring 本地事务
Spring 本地事务紧密依赖于底层资源管理器(例如数据库连接 ),事务处理局限在当前事务资源内。此种事务处理方式不存在对应用服务器的依赖,因而部署灵活却无法支持多数据源的分布式事务。
- Spring容器管理事务的生命周期
- 通过Spring事务接口调用
- 业务代码与具体事务的实现无关
在数据库连接中使用本地事务Demo:
public void transferAccount() { Connection conn = null; Statement stmt = null; try{ conn = getDataSource().getConnection(); ## 将自动提交设置为 false, ## 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交 conn.setAutoCommit(false); stmt = conn.createStatement(); ## 将 A 账户中的金额减少 500 stmt.execute("\ update t_account set amount = amount - 500 where account_id = 'A'"); ## 将 B 账户中的金额增加 500 stmt.execute("\ update t_account set amount = amount + 500 where account_id = 'B'"); ## 提交事务 conn.commit(); ## 事务提交:转账的两步操作同时成功 } catch(SQLException sqle){ try{ ## 发生异常,回滚在本事务中的操做 conn.rollback(); ## 事务回滚:转账的两步操作完全撤销 stmt.close(); conn.close(); }catch(Exception ignore){ } sqle.printStackTrace(); } }
本地事务机制过程图:
Spring 外部(全局)事务
- 外部事务管理器提供事务管理
- 通过Spring事务接口,调用外部管理器
- 使用JNDI等方式获取外部事务管理器的实例
- 外部事务管理器一般由应用服务器提供、如JBoss等
什么是JNDI?
参考==>JNDI到底是什么?