spring5整理:(九)事务_fenfeng2012的博客-程序员宅基地

技术标签: spring  java  Transaction  jdbc  

目录

一、何为数据库事务

二、数据库并发问题

三、事务的隔离级别

四、事务类型

Spring事务管理

六、使用XML配置JDBC事务 

七、tx:method标签设置​

八、配置一个CRUD通用的事务配置

九、使用注解配置JDBC事务


一、何为数据库事务

事务是一系列操作组成的工作单元,该单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做

事务必需满足ACID特性,缺一不可:

1.原子性(Atomicity):事务不可分割的最小工作单元,事务内的操作要么全做,要么全不做

2.一致性(Consistency):在事务执行前数据库的数据处于正确的状态,需事务执行完后数据库的数据依然处于正确的状态,即数据完整性约束没有被破坏,如A给B转帐,不论转帐是否成功,转帐之后的A和B的帐户总额和转帐之前是相同的

3.隔离性(Isolation):当多个事务处于并发访问同一个数据库资源时,事务之间相互影响响度,不同的隔离级别决定了各个事务对数据资源访问的不同行为

4.持久性(Durability):事务一旦执行成功,它对数据库的数据的改变是不可逆的

 

二、数据库并发问题

三、事务的隔离级别

为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:

READ UNCOMMITED < READ COMMITED < REPEATABLE READ < SERIALIZABLE

Oracle支持READ COMMITED(缺省)和SERIALIZABLE

MySql支持四种隔离级别,缺省为REPEATABLE READ

 SQL92推荐使用REPEATABLE READ以保证数据的读一致性,不过用户可以根据具体的需求选择适合的事务隔离级别。

默认性况下:Mysql不会出现幻读,除非(select * from 表名 lock in share mode);MySql中锁基于索引机制,也不会出现第一类丢失更新

如何选择:

隔离级别越高,数据库事务并发执行能力越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用READ COMMITED,它能避免丢失更新和脏读,尽管不可重复和幻读不能避免,更多的情况下使用悲观锁或乐观锁来解决来解决.

 

1.悲观锁

 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

在数据库中,悲观锁的流程如下:

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。

如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。

如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

MySQL InnoDB中使用悲观锁:

 要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。set autocommit=0;

//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;

上面的查询语句中,我们使用了select…for update的方式,这样就通过开启排他锁的方式实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改

上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

优点:悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。

不足:但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

 

2.乐观锁

 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做.

使用乐观锁

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据

1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods 
set status=2,version=version+1
where id=#{id} and version=#{version};

优点:乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

不足:但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

 

四、事务类型

1、划分本地事务和分布式事务:

1)本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上

2)分布式事务:涉及多个数据源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组务),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;

2、划分JDBC事务和JTA事务:

1)JDBC事务:就是数据库事务中的本地事务。通过Connection对象的控制来管理事务

2)JTA指(Java Transaction API),是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商提供实现 ,JTA事务比JDBC更强大,支持分布式事务

3、按是否通过编程实现事务:

1)编程式事务:通过编写代码来管理事务

2)通过注解或XML配置来管理事务

 

五.Spring事务管理

 Spring的事务管理主要包括3个接口:

1)PlatformTransactionManager:根据TransactionDefinition提供的事务属性配置信息,创建事务.

2)TransactionDefinition:封状事务的隔离级别、超时时间、是否只读事务和传播规则等事务属性.

3)TransactionStatus:封装了事务的具体运行状态,如是否是新事务,是否已经提交事务,设置当前事务为rollback-only等;

 

1.PlatformTransactionManager

 接口统一抽象处理事务操作相关的方法,是其他事务的规范,方法解析:

1)TransactionStatus getTransaction(@Nullable TransactionDefinition definition):根据事务定义信息从事事务环境返回一个已存在的事务,或者创建一个新的事务。

2)void commit(TransactionStatus status):根据事务的状态提交事务,如果事务状态已经标识为rollback-only,该方法执行回滚事务的操作

3)void rollback(TransactionStatus status):将事务回滚,当commit方法抛出异常时,rollback会被隐式调用 

常用的事务管理器: 

DataSourceTransactionManager:支持JDBC,MyBatis等;

HibernateTransactionManager:支持Hibernate

 

2.TransactionDefinition

 

事务隔离级别:用来解决并发事务出现的问题

1)ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别;

2)ISOLATION_READ_UNCOMMITTED:未提交读

3)ISOLATION_READ_COMMITTED :提交读,一般情况我们使用这个

4)ISOLATION_REPEATABLE_READ :可重复读

5)ISOLATION_SERIALIZABLE : 序列化

注:除第一个外,后面四个都是spring通过java代码模拟出来的

传播规则:在一个事务中调用其他事务方法,此时事务该如何传播,按照什么规则传播,用谁的事务,还是都不用等

Spring共支持7种传播行为:

情况一:遵从当前事务

1)REQUIRED:必须存在事务,如果当前存在一个事务,则加入该事务,否则将新建一个事务(缺省

2)SUPPORTS:支持当前事务,指如果当前存在逻辑事务,就加入到该事务,如果当前没有事务,就以非事务方式执行

3)MANDATORY:必须有事务,使用当前事务执行,如果当前没有事务,则抛出异常IllegalTransactionStateException

情况二:不遵从当前事务

1)REQUIRES_NEW:不管当前是否存在事务,每次都创建新事务

2)NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务暂停,以非事务方式执行

3)NEVER:不支持事务,如果当前存在事务,则抛出异常:IllegalTransactionStateException

情况三:寄生事务(外部事务和寄生事务)

NESTED:如果当前存在事务,则在内部事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。 

 

六、使用XML配置JDBC事务 

 1.表account结构

2.domain类

@Data
public class Account {
    private Long id;
    private int balance;
}

 3.dao接口及实现类

public interface IAccountDAO {

    /**
     * 从指定帐户转出多少钱
     * @param outId
     * @param money
     */
    void transOut(Long outId,int money);
    
    /**
     * 从指定帐户转入多少钱
     * @param inId
     * @param money
     */
    void transIn(Long inId,int money);
}
public class AccountDAOImpl implements IAccountDAO {

    private JdbcTemplate jdbcTemplate;
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
    }
    
    @Override
    public void transOut(Long outId, int money) {
        System.out.println("outId:"+outId+",money:"+money);
        this.jdbcTemplate.update("update account set balance = balance - ? where id=?", money,outId);

    }

    @Override
    public void transIn(Long inId, int money) {
        System.out.println("inId:"+inId+",money:"+money);
        this.jdbcTemplate.update("update account set balance = balance + ? where id=?", money,inId);

    }

}

3.service接口及实现类

public interface IAccountService {
    /**
     * 从指定帐户转出另一个帐户多少钱
     * @param outId
     * @param inId
     * @param money
     */
    void trans(Long outId,Long inId,int money);
}
public class AccountServiceImpl implements IAccountService {

    private IAccountDAO dao;
    
    public void setDao(IAccountDAO dao) {
        this.dao = dao;
    }
    
    @Override
    public void trans(Long outId, Long inId, int money) {
        //转出
        this.dao.transOut(outId, money);
        //转入
        int a = 1/0;//模拟异常
        this.dao.transIn(inId, money);

    }

}

4.XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

	<!-- 从classpath的根路径去加载db.properties文件 -->
	<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER" />

	<!-- 配置一个druid的连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="initialSize" value="${jdbc.initialSize}" />
	</bean>
	
	<!-- DAO配置 -->
	<bean id="accountDAO" class="com.bigfong.txxml.dao.impl.AccountDAOImpl">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<!-- Service配置 -->
	<bean id="accountService" class="com.bigfong.txxml.service.impl.AccountServiceImpl">
		<property name="dao" ref="accountDAO"/>
	</bean>
	
	<!-- =============配置事务 start============= -->
	<!-- 1:WHAT 配置JDBC事务管理器 -->
	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<!-- 2:WHEN配置事务管理器增强 -->
	<tx:advice id="txAdvice" transaction-manager="txManager">
		<tx:attributes>
			<tx:method name="trans"/>
		</tx:attributes>
	</tx:advice>
	<!-- 3:WHERE 配置切面 -->
	<aop:config>
		<!-- 接口路径 -->
		<aop:pointcut expression="execution(* com.bigfong.txxml.service.*Service.*(..))" id="txPC"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPC"/>
	</aop:config>
	<!-- =============配置事务 end============= -->
</beans>

注意以上关联关系 

5.测试代码

@SpringJUnitConfig
public class App {
    
    @Autowired
    private IAccountService service;
    
    @Test
    void testTrans() {
        service.trans(10002L, 10010L, 100);
    }
}

 七、tx:method标签设置

1.name:匹配到的方法模拟,必须配置;

2.read-only:如果为true,开启一个只读事务,只读事务的性能较高,但是不能只读事务中操作DML;

3.isolation:代表数据库事务隔离级别(就使用默认),DEFAULT:让Spring使用数据库默认的事务隔离级别;其他:Spring模拟

4.no-rollback-for:如果遇到的异常是匹配的异常类型,就不回滚事务

5.rollback-for:如果遇到的异常是指定匹配的异常类型,才回滚事务;

6.propagation:事务的传播方式(当一个方法已在一个开启的事务当中了,应该怎么处理自身的事务);

 

八、配置一个CRUD通用的事务配置

<tx:advice id="crudAdvice" transaction-manager="txManager">
	<tx:attributes>
		<tx:method name="get*" read-only="true" propagation="REQUIRED"/>
		<tx:method name="list*" read-only="true" propagation="REQUIRED"/>
		<tx:method name="query*" read-only="true" propagation="REQUIRED"/>
		<!-- service其他方法(非查询方法) -->
		<tx:method name="*" propagation="REQUIRED"/>
	</tx:attributes>
</tx:advice>

 

九、使用注解配置JDBC事务

 1.XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

	<!-- 从classpath的根路径去加载db.properties文件 -->
	<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER" />

	<!-- 配置一个druid的连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="initialSize" value="${jdbc.initialSize}" />
	</bean>
	
	<!-- DI注解解析器 -->
	<context:annotation-config/>
	<!-- Ioc注解解析器 -->
	<context:component-scan base-package="com.bigfong.txcode"/>
	<!-- 配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	<!-- TX注解解析器 -->
	<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

2.domain类似上面JDBC的方式

3.dao接口同上,实现类如下,添加注解:@Repository

@Repository
public class AccountDAOImpl implements IAccountDAO {

    private JdbcTemplate jdbcTemplate;
    
    @Autowired
    public void setDataSource(DataSource ds) {
        this.jdbcTemplate = new JdbcTemplate(ds);
    }
    
    @Override
    public void transOut(Long outId, int money) {
        System.out.println("outId:"+outId+",money:"+money);
        this.jdbcTemplate.update("update account set balance = balance - ? where id=?", money,outId);

    }

    @Override
    public void transIn(Long inId, int money) {
        System.out.println("inId:"+inId+",money:"+money);
        this.jdbcTemplate.update("update account set balance = balance + ? where id=?", money,inId);

    }

}

 4.service接口同上,实现类如下,添加注解:@[email protected]

@Service
@Transactional
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDAO dao;

    @Override
    public void trans(Long outId, Long inId, int money) {
        //转出
        this.dao.transOut(outId, money);
        //转入
        int a = 1/0;//模拟异常
        this.dao.transIn(inId, money);

    }
    
    @Transactional(readOnly=true)
    public void  listAcount() {
        
    }

}

 可以在指定方法配置指定规则,如上述: @Transactional(readOnly=true)

5.测试类同上 

 

上一篇:spring5整理:(八)DAO

下一篇:spring5整理:(十)JavaConfig配置 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/fenfeng2012/article/details/83653023

智能推荐

java 输出验证码_输出 验证码图片_知乎职场的博客-程序员宅基地

[java]代码库package pm_cn.itcast.response;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.image.BufferedImage;import java.io.IOException;imp...

Wannafly挑战赛1 C MMSet2 虚树_weixin_30848775的博客-程序员宅基地

题目链接:https://www.nowcoder.com/acm/contest/15/C思路:虚树,取两点间的lca,构造成一颗新的树;求(直径+1)/2即可#pragma comment(linker, "/STACK:1024000000,1024000000")#include&lt;iostream&gt;#include&lt;cstdio&gt;#inclu...

5.MPEG-4 压缩编码标准_adl30141的博客-程序员宅基地

1.MPEG-4标准概述与MPEG1和MPEG2标准相比,MPEG-4 更加注重多媒体系统的交互性和灵活性,主要应用于可视电话、视频会议等。MPEG-4 标准主要包含音视频对象编码工具集和编码对象句法语言两个部分。MPEG-4 标准的编码基于对象,便于操作和控制对象,MPEG-4 的对象操作使用户可在终端直接将不同对象进行拼接,得到用户合成图像。MPEG-4 具有很...

thinkphp整合系列之极验滑动验证码geetest_子钦加油的博客-程序员宅基地

给一个央企做官网,登录模块用的thinkphp验证码类。但是2019-6-10到12号,国家要求央企检验官网漏洞,防止黑客攻击,正直贸易战激烈升级时期,所以各事业单位很重视官网安全性,于是乎集团总部就委托了宁波一个专业检测公司用专业工具检测出,后台验证码能用打码工具暴力破解,发函要求整改。so,就有了下面的极速验证图形官网:http://www.geetest.com/一...

学习记录:python获取链接下载_U盘失踪了的博客-程序员宅基地

import tkinter as tk # 使用Tkinter前需要先导入import tkinter.messagebox # 要使用messagebox先要导入模块import requests #获取链接库import random #随机函数库#实例化object,建立窗口windowwindow = tk.Tk() #给窗口的可视化起名字window.title('知乎视频下载') #设定窗口的大小(长 * 宽)window.geometry('450x180') .

云计算_weixin_34315189的博客-程序员宅基地

云计算是一种能够通过网络以便利的、按需付费的方式获取计算资源并提高其可用性的模式,这些资源来自一个共享的、可配置的资源池,并能够以最省力和无人干预的方式获取和释放举例说明:云计算,就像我们使用水电煤气一样,我们从来不会想着去建电厂,也不关心电厂在哪里,只要插上插头,就能用电。打开水龙头,就可以用水云计算是虚拟化基础上的一个演进过程,没有虚拟化就没有云计算、虚拟化是云计算的基础...

随便推点

前后端分离实际容易产生的问题_Java程序员-张凯的博客-程序员宅基地

前后端分离现在火了很多年,在实际中新技术的使用一般是先在一些大厂中采用,比如在招聘网上大厂的前端招聘node要求比较高,而在中小型厂中对node的要求只是会用webpack打包工具以及npm包管理就可以了。最近几年传统公司、中小型公司开始构建前后端分离模式,前后端分离的好处网上文章很多,简单说前端可以专注前端的开发,后端专注后端开发,开发效率和质量都会得到提升,但在实际项目组中因为很多leader...

【Elasticsearch】将 term查询的 integer 字段改成 keyword之后, must 再改成 filter,就造成query_cache剧降_九师兄的博客-程序员宅基地

1.概述请问一下,为什么 elasticsearch 不生成缓存?常见的原因有哪些? 将 term查询的 integer 字段改成 keyword之后, must 再改成 filter,就造成query_cache剧降,有什么道理。求各路神仙指点。下面是聊天正常现象,TermQuery(LUCENE中的)不会被缓存,es中term查询,针对string类型(text、keyword等)会转换为TermQuery(LUCENE中的),针对number类型会转换为RangeQuery本身就是照着

mysql bin的过期时间_Mysql设置binlog过期时间并自动删除_weixin_39861669的博客-程序员宅基地

问题:Mysql数据库由于业务原因,数据量增长迅速,binlog日志会增加较多,占用大部分磁盘空间。解决方案:出于节约空间考虑,可进行删除多余binary日志,并设置定期删除操作。1、查看binlog过期时间mysql&gt; show variables like ‘expire_logs_days‘; 或者mysql&gt; select @@global.expire_logs_days;+...

cvWaitKey()_端午过后的猪的博客-程序员宅基地

原文地址:cvWaitKey()作者:三杯茶原文地址:http://caicaiwh.blogbus.com/logs/65060550.html作者:CAICAI_WH cvWaitKey(int delay) 指延时delay ms的时间delaydelay>0时,延迟"delay"ms,在显示视频时这个函数是有用的,用于设置在显示完一帧图像后程序等待"delay"ms再显示下一

安测云验证有CTA问题_weixin_30480583的博客-程序员宅基地

背景: 现在所有的app 都需要通过工信部的审核。用户不同意之前,不能联网。那么,我怎么知道自己的应用有没有联网呢?那么多sdk ,那么多代码?我怎么测试呢?哈哈,我们测试给的方法真的很管用。logcat的 NetWork 这表示有网络请求,那么你就需要去解决问题。这样就表示OK了。哎,自己当初竟然不相信测...

推荐文章

热门文章

相关标签