博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring事务管理
阅读量:7057 次
发布时间:2019-06-28

本文共 14245 字,大约阅读时间需要 47 分钟。

  对于Spring相信很多做web开发的小活动一定不陌生,Spring中我们经常谈到的就是IOC和AOP,但是对于Spring的事务管理,相信大家一定也很感兴趣,今天我们就探讨一下Spring中的事务管理。

  首先谈一下事务使用的场景,我们能想到的最常见场景就是银行转账,A给B转账,第一步扣除A中的账户金额,第二步将扣除的金额加入到B账户,这个过程就要求,这两步,必须同时成功,如果失败就需要进行事务回滚,不然我们的数据就会出现异常。这个怎么来进行实现呢?这就要从今天的事务说起了。首先谈一下事务的4大特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。

⑴ 原子性(Atomicity)

  原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

⑵ 一致性(Consistency)

  一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

  拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

⑶ 隔离性(Isolation)

  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

  即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

  关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

⑷ 持久性(Durability)

  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

  例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

  以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:1、脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。2、不可重复读:不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。3、虚读(幻读):幻读是事务非独立执行时发生的一种现象。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

  说了这么多,下面我们我们开始通过程序来简单实现一下转账场景中如何使用事务。

  首先我们这里需要搭建一个Spring下的转账场景,我使用的是jdk8+maven+spring+jdbc来模拟转账场景:

  1、打开eclipse(最新版本的直接支持maven环境),创建我们的maven项目

  2、打开pom.xml添加我们的jar依赖:

4.0.0
com.edu.hpugs.spring
springTransation
0.0.1-SNAPSHOT
jar
springTransation
http://maven.apache.org
UTF-8
mysql
mysql-connector-java
6.0.6
org.springframework
spring-jdbc
4.3.11.RELEASE
org.springframework
spring
2.5.6
org.springframework
spring-context
4.3.11.RELEASE
org.springframework
spring-core
4.3.11.RELEASE
org.springframework
spring-beans
4.3.11.RELEASE
org.springframework
spring-test
4.3.11.RELEASE
test
org.apache.logging.log4j
log4j-core
2.9.0
junit
junit
4.12
test

  这里我们使用的是mysql数据库,所以添加mysql驱动jar包;这里我们使用spring-jdbc连接数据,所以引入spring-jdbc包;其次就是我们的spring相关jar包了

  3、编写我们的service、dao层:

    a、service接口:

public interface IBlankService {        void transferAccounts(final String outName, final String inName, final double monery);}

    b、service实现:

public class BlankServiceImpl implements IBlankService {        private IBlankDao blankDao;        public void setBlankDao(IBlankDao blankDao) {        this.blankDao = blankDao;    }    public void transferAccounts(String outName, String inName, double monery) {        // TODO Auto-generated method stub        blankDao.outMonery(outName, monery);        int i = 1 / 0;        blankDao.inMonery(inName, monery);    }}

    c、dao接口:

public interface IBlankDao {    void outMonery(final String userName, final double monery);        void inMonery(final String userName, final double monery);    }

    d、dao实现:

public class BlankDaoImpl implements IBlankDao {        private DataSource dataSource;        public DataSource getDataSource() {        return dataSource;    }    public void setDataSource(DataSource dataSource) {        this.dataSource = dataSource;    }    public void outMonery(String userName, double monery) {        // TODO Auto-generated method stub        String sql = "UPDATE userAccount SET monery = monery - ? WHERE name = ?";        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);        jdbcTemplate.update(sql, monery, userName);    }    public void inMonery(String userName, double monery) {        // TODO Auto-generated method stub        String sql = "UPDATE userAccount SET monery = monery + ? WHERE name = ?";        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);        jdbcTemplate.update(sql, monery, userName);    }}

  4、这里我们通过junit单元测试的方法来调用我们的service:

public class UserTest {        private IBlankService blankService;        @Before    public void before(){        ApplicationContext appletContext = new ClassPathXmlApplicationContext("applicationContext.xml");        blankService = (IBlankService) appletContext.getBean("blankService");    }        @Test    public void userTest(){        blankService.transferAccounts("aa", "bb", 100);    }}

  5、我们的resource文件:

    a、applicationContext.xml

    b、db.properties

jdbc.dirverClass=com.mysql.jdbc.Driverjdbc.url=jdbc\:mysql\://127.0.0.1\:3306/blank?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCjdbc.user=rootjdbc.password=root

    c、log4j.properties

#log4j.rootCategory=error,stdout##log4j.appender.stdout=org.apache.log4j.ConsoleAppender#log4j.appender.stdout.layout=org.apache.log4j.PatternLayout#log4j.appender.stdout.layout.ConversionPattern=[HYYT] %p [%t] %C.%M(%L) | %m%n##log4j.logger.com.opensymphony.xwork2.ognl.OgnlValueStack=ERROR##########################\u914d\u7f6erootLogger--\u8f93\u51fa\u6d88\u606f\u7ea7\u522b\uff1aINFO, WARN, ERROR\u548c FATAL ################log4j.rootLogger = ALL, STUDIO, INFO_FILE, WARN_FILE, ERROR_FILE, FATAL_FILE#####################################\u63a7\u5236\u53f0\u8f93\u51fa##################################################log4j.rootLogger = ALL, INFO_FILE, WARN_FILE, ERROR_FILE###############################################################################################log4j.appender.STUDIO = org.apache.log4j.ConsoleAppenderlog4j.appender.STUDIO.Targer = System.outlog4j.appender.STUDIO.Threshold = INFOlog4j.appender.STUDIO.ImmediateFlush = TRUElog4j.appender.STUDIO.layout = org.apache.log4j.PatternLayoutlog4j.appender.STUDIO.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH\:mm\:ss}][%l]%n%m%n####################################INFO_FILE\u8f93\u51fa################################################log4j.appender.INFO_FILE = org.apache.log4j.DailyRollingFileAppender#log4j.appender.INFO_FILE.Targer = /WebLogs/Info_Filelog4j.appender.INFO_FILE.File = /WebLogs/springTransation/Info_File/log.txtlog4j.appender.INFO_FILE.Threshold = INFOlog4j.appender.INFO_FILE.ImmediateFlush = TRUElog4j.appender.INFO_FILE.Append = TRUElog4j.appender.INFO_FILE.Encoding = UTF-8log4j.appender.WARN_FILE.DataPattern = '.'YYYY-MM-ddlog4j.appender.INFO_FILE.layout =  org.apache.log4j.PatternLayoutlog4j.appender.INFO_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n##################################WARN_FILE\u8f93\u51fa#################################################log4j.appender.WARN_FILE = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.WARN_FILE.Targer = /WebLogs/Warn_Filelog4j.appender.WARN_FILE.File = /WebLogs/springTransation/Warn_File/log.txtlog4j.appender.WARN_FILE.Threshold = WARNlog4j.appender.WARN_FILE.ImmediateFlush = TRUElog4j.appender.WARN_FILE.Append = TRUElog4j.appender.WARN_FILE.Encoding = UTF-8log4j.appender.WARN_FILE.DataPattern = '.'YYYY-MM-ddlog4j.appender.WARN_FILE.layout = org.apache.log4j.PatternLayoutlog4j.appender.WARN_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n##################################ERROR_FILE\u8f93\u51fa#################################################log4j.appender.ERROR_FILE = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.ERROR_FILE.Targer = /WebLogs/Error_Filelog4j.appender.ERROR_FILE.File = /WebLogs/springTransation/Error_File/log.txtlog4j.appender.ERROR_FILE.Threshold = ERRORlog4j.appender.ERROR_FILE.ImmediateFlush = TRUElog4j.appender.ERROR_FILE.Append = TRUElog4j.appender.ERROR_FILE.Encoding = UTF-8log4j.appender.ERROR_FILE.DataPattern = '.'YYYY-MM-ddlog4j.appender.ERROR_FILE.layout = org.apache.log4j.PatternLayoutlog4j.appender.ERROR_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n##################################FATAL_FILE\u8f93\u51fa################################################log4j.appender.FATAL_FILE = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.FATAL_FILE.Targer = /WebLogs/Fatal_Filelog4j.appender.FATAL_FILE.File = /WebLogs/springTransation/Fatal_File/log.txtlog4j.appender.FATAL_FILE.Threshold = FATALlog4j.appender.FATAL_FILE.ImmediateFlush = TRUElog4j.appender.FATAL_FILE.Append = TRUElog4j.appender.FATAL_FILE.Encoding = UTF-8log4j.appender.FATAL_FILE.DataPattern = '.'YYYY-MM-ddlog4j.appender.FATAL_FILE.layout = org.apache.log4j.PatternLayoutlog4j.appender.FATAL_FILE.layout.ConversionPattern = [%-5p][%-22d{yyyy/MM/dd HH:mm:ss}][%l]%n%m%n

  到这里我们的转账场景搭建就算完成了,下面我们就简单看一下如何进行事务管理:

  Spring中的事务管理分为:1、编程是事务;2、声明式事务、3、基于注解的事务。

  Spring的事务有分为:

    传播行为:

      PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 

      PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。 

      PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。 

      PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。 

      PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 

      PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。 

    隔离级别:

      Serializable:最严格的级别,事务串行执行,资源消耗最大;   

      REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

      READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

      Read Uncommitted:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。 

   下面我们先一起探讨一下关于编程式的事务管理,首先我们要明白事务应该加载那一层,我们知道Dao是我们的持久层,service是我们的业务层,而我们的事务应当加在业务层,而非持久层。

  1、通过TransactionManager来进行我们的事务管理,修改applicationContext.xml:

  2、同样修改serviceImpl.java

public class BlankServiceImpl implements IBlankService {        private IBlankDao blankDao;        public void setBlankDao(IBlankDao blankDao) {        this.blankDao = blankDao;    }        private TransactionTemplate transactionTemplate;        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {        this.transactionTemplate = transactionTemplate;    }    public void transferAccounts(final String outName, final String inName, final double monery) {        // TODO Auto-generated method stub        transactionTemplate.execute(new TransactionCallbackWithoutResult() {            @Override            protected void doInTransactionWithoutResult(TransactionStatus arg0) {                // TODO Auto-generated method stub                blankDao.outMonery(outName, monery);                int i = 1 / 0;                blankDao.inMonery(inName, monery);            }        });    }}

  到这里Spring编程式的注解就完成了。

  下面我们看一下基于声明式的事务管理:

  1、对于声明式的事务管理,Spring是基于AOP来实现的,所以我们需要在maven中添加aop相关的依赖

org.springframework
spring-aop
4.3.11.RELEASE
org.aspectj
aspectjrt
1.8.10
org.aspectj
aspectjweaver
1.8.10

  2、将我们的场景代码恢复如初,然后修改我们的applicationContext.xml

  好了,到这里关于Spring中的声明式事务就介绍完毕。

  最后时我们基于注解的事务管理

  1、基于注解的事务管理,需要我们通过maven添加注解依赖:

org.springframework
spring-tx
4.3.11.RELEASE

  2、修改applicationContext.xml

  3、在需要添加事务的方法前添加注解

public class BlankServiceImpl implements IBlankService {        private IBlankDao blankDao;        public void setBlankDao(IBlankDao blankDao) {        this.blankDao = blankDao;    }        @Transactional    public void transferAccounts(final String outName, final String inName, final double monery) {        // TODO Auto-generated method stub        blankDao.outMonery(outName, monery);        //int i = 1 / 0;        blankDao.inMonery(inName, monery);    }}

  OK,关于注解式事务的分享就到这里,。到这里关于Spring中事务的相关内容就先和大家分享到这里。感兴趣的童鞋可以留言讨论。下一篇:【】

转载地址:http://bfgol.baihongyu.com/

你可能感兴趣的文章
李洪强漫谈iOS开发[C语言]-045-循环结构
查看>>
验证码识别
查看>>
国内maven 仓库
查看>>
Capture and report JavaScript errors with window.onerror
查看>>
Git系列二之数据管理
查看>>
JNI/NDK开发指南(九)——JNI调用性能測试及优化
查看>>
scrapy-splash抓取动态数据例子十四
查看>>
php中奖概率算法,可用于刮刮卡,大转盘等抽奖算法
查看>>
go 学习资源和GitHub库
查看>>
如何用路由器改成WiFi Pineapple系统镜像网络流量
查看>>
Zabbix触发器函数(取前后差值)
查看>>
Postman
查看>>
STS开发环境搭建与配置
查看>>
moment.js aka underscore.date.js
查看>>
精悍的Python代码段
查看>>
XCode数据类型转换代码 文件读取,写入,XY坐标获取,ASCII转换等
查看>>
定制默认系统帐号不能被更新与删除
查看>>
Ado.Net Entity Framework的使用
查看>>
Android实战技巧: ListView之ContextMenu无法弹出
查看>>
SQL Server 的内存分类
查看>>