Spring之事务管理TranscationManager(大合集)

来源:互联网 发布:传奇霸业移动网络 编辑:程序博客网 时间:2024/06/10 21:41

想到哪说到哪
事务四个特性:

  1. 原子性
    事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。

  2. 一致性
    事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

  3. 隔离性
    一个事务的执行不能被其他事务干扰。

  4. 持续性
    一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。

在正常的web项目中,事务和DAO一直如影随行,所以有人认为配置事务和DAO的关系是密不可分,不能分离的。其实不然,DAO可以完全脱离容器独立存在。

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"    xmlns:p="http://www.springframework.org/schema/p" 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:mvc="http://www.springframework.org/schema/mvc"    xmlns:tx="http://www.springframework.org/schema/tx"    xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"    xsi:schemaLocation="    http://www.springframework.org/schema/beans    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd    http://www.springframework.org/schema/context    http://www.springframework.org/schema/context/spring-context-3.2.xsd    http://www.springframework.org/schema/mvc    http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd    http://www.springframework.org/schema/tx    http://www.springframework.org/schema/tx/spring-tx-3.2.xsd    http://www.springframework.org/schema/aop    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd    http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring         http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">    <!-- 打开springmvc自动注解 -->    <mvc:annotation-driven />    <!-- 扫描的包 -->    <context:component-scan base-package="com.zhu" />    <!-- 数据源默认将autoCommit设置为true -->    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"        destroy-method="close"         p:driverClassName="com.mysql.jdbc.Driver"        p:username="zhu"        p:password="zhu" />    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"        p:dataSource-ref="dataSource" /></beans>

zhu_test.sql

CREATE TABLE IF NOT EXISTS zhu_test (NAME VARCHAR(20),age INT(100)) ENGINE = INNODB;

测试类:

package com.zhu.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import com.alibaba.druid.pool.DruidDataSource;@Service("service1")public class UserJdbcWithoutTransManagerService {    @Autowired    private JdbcTemplate jdbcTemplate;    public void addScore(String userName,int toAdd){        String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?";        jdbcTemplate.update(sql,toAdd,userName);    }    public static void main(String[] args) {        ApplicationContext ctx =         new ClassPathXmlApplicationContext("file:src/main/resources/applicationContext.xml");        UserJdbcWithoutTransManagerService service =             (UserJdbcWithoutTransManagerService)ctx.getBean("service1");        JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");        DruidDataSource druidDataSource = (DruidDataSource)jdbcTemplate.getDataSource();        //①.检查数据源autoCommit的设置        System.out.println("autoCommit:"+ druidDataSource.isDefaultAutoCommit());        //②.插入一条记录,初始分数为10        jdbcTemplate.execute(        "INSERT INTO zhu_test VALUES('tom',10)");        //③.调用工作在无事务环境下的服务类方法,将分数添加20分        service.addScore("tom",20);         //④.查看此时用户的分数        @SuppressWarnings("deprecation")        int score = jdbcTemplate.queryForInt("SELECT age FROM zhu_test WHERE name ='tom'");        System.out.println("score:"+score);        jdbcTemplate.execute("DELETE FROM zhu_test WHERE name='tom'");    }}

运行结果:

这里写图片描述

applicationContext.xml中并没有配置事务,但是还是持久化到数据库中去了。DataSource默认设置是自动提交的,也就是说,在执行了CRUD之后,会马上持久化到数据库中。如果设置自动提交为false,那么在jdbcTemplate执行完之后并不会马上持久化到数据库中,除非手动提交。
虽说没有事务管理,程序依然可以进行数据的CRUD操作,但是没有事务管理,在数据安全同步方面会面临很大的挑战。栗子太多,不一一举例,看代码
我们故意提交一条错误的sql语句

package com.zhu.service;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Service;import com.alibaba.druid.pool.DruidDataSource;@Service("service1")public class UserJdbcWithoutTransManagerService {    @Autowired    private JdbcTemplate jdbcTemplate;    public void addScore(String userName,int toAdd){        String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?";        jdbcTemplate.update(sql,toAdd,userName);    }    public static void main(String[] args) {        ApplicationContext ctx =         new ClassPathXmlApplicationContext("file:src/main/resources/applicationContext.xml");        UserJdbcWithoutTransManagerService service =             (UserJdbcWithoutTransManagerService)ctx.getBean("service1");        JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");        DruidDataSource druidDataSource = (DruidDataSource)jdbcTemplate.getDataSource();        //①.检查数据源autoCommit的设置        System.err.println("autoCommit:"+ druidDataSource.isDefaultAutoCommit());        //②.插入一条记录,初始分数为10        jdbcTemplate.execute(        "INSERT INTO zhu_test VALUES('tom',10)");        //③.执行一条错误sql语句        jdbcTemplate.execute(        "INSERT INTO zhu_test VALUES('tom1',10,00)");        //④.调用工作在无事务环境下的服务类方法,将分数添加20分(不会执行)        service.addScore("tom",20);    }    }

清空数据,然后执行结果是:②成功插入数据tom 10 然后③错误的sql抛出异常 然后④更新操作也不会执行。
正常的逻辑是在同一方法或者类中执行一系列的CRUD操作,其中一条出出现问题会抛出异常,然后已经执行完的语句回滚到发生异常之前的状态。
这种情况在正常的开发环境是最基本的常识性错误,开发过程中一定要避免。
然后是配置事务。
applicationContext.xml文件中添加事务和模型视图的配置

package com.zhu.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class NoTMController {    //②.自动注入JdbcTemplate    @Autowired    private JdbcTemplate jdbcTemplate;    //③.通过Spring MVC注解映URL请求    @RequestMapping("/logon")        @ResponseBody    public String logon(String userName,String password){        String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?";        if(isRightUser(userName,password)){            //执行更新操作(年龄加20)            jdbcTemplate.update(sql,20,"tom");            //执行错误语句            jdbcTemplate.execute(                "INSERT INTO zhu_test VALUES('tom1',10,00)");            return "success";        }else{            return "fail";        }    }    private boolean isRightUser(String userName,String password){        //do sth...        return true;    }}

启动项目 输入网址
影响了一条数据,操作已经完成
这里写图片描述
检测到错误,回滚
这里写图片描述
所以结果是数据没有变化。
注释掉错误的sql语句则完成执行所有的sql语句。
事务在进行嵌套调用的时候不会分解为多个事务,比如说一个事务中的方法调用其他事务的方法不会产生多余的事务。
spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法:
int getPropagationBehavior():事务的传播行为
int getIsolationLevel():事务的隔离级别
int getTimeout():事务的过期时间
boolean isReadOnly():事务的读写特性。

所谓事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring 支持 7 种事务传播行为:

  • PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
  • PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
  • PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
  • PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。

Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。

事务与线程
由于 Spring 的事务管理器是通过线程相关的 ThreadLocal 来保存数据访问基础设施,再结合 IOC 和 AOP 实现高级声明式事务的功能,所以 Spring 的事务天然地和线程有着千丝万缕的联系。

我们知道 Web 容器本身就是多线程的,Web 容器为一个 Http 请求创建一个独立的线程,所以由此请求所牵涉到的 Spring 容器中的 Bean 也是运行于多线程的环境下。在绝大多数情况下,Spring 的 Bean 都是单实例的(singleton),单实例 Bean 的最大的好处是线程无关性,不存在多线程并发访问的问题,也即是线程安全的。

一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。我们知道,在传统的编程中,DAO 必须执有一个 Connection,而 Connection 即是状态化的对象。所以传统的 DAO 不能做成单实例的,每次要用时都必须 new 一个新的实例。传统的 Service 由于将有状态的 DAO 作为成员变量,所以传统的 Service 本身也是有状态的。

但是在 Spring 中,DAO 和 Service 都以单实例的方式存在。Spring 是通过 ThreadLocal 将有状态的变量(如 Connection 等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring 不遗余力地将状态化的对象无状态化,就是要达到单实例化 Bean 的目的。

由于 Spring 已经通过 ThreadLocal 的设施将 Bean 无状态化,所以 Spring 中单实例 Bean 对线程安全问题拥有了一种天生的免疫能力。不但单实例的 Service 可以成功运行于多线程环境中,Service 本身还可以自由地启动独立线程以执行其它的 Service。

于是我们修改我们的代码

package com.zhu.controller;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;@Controllerpublic class NoTMController {    //②.自动注入JdbcTemplate    @Autowired    private JdbcTemplate jdbcTemplate;    //③.通过Spring MVC注解映URL请求    @RequestMapping("/logon")        @ResponseBody    public String logon(String userName,String password){        System.err.println(userName);        if(isRightUser(userName,password)){           Thread h1 = new Thread(){               String sql = "UPDATE zhu_test u SET u.age = u.age + ? WHERE name =?";                @Override                public void run() {                     jdbcTemplate.update(sql,20,"tom");                }            };            h1.start();            jdbcTemplate.execute(                    "INSERT INTO zhu_test VALUES('tom1',10,00)");            return "success";        }else{            return "fail";        }    }    private boolean isRightUser(String userName,String password){        //do sth...        return true;    }}

代码还是原来的代码,只是将更新年龄的操作放到其他线程中,然后再进项上面的操作。
结果就是更新成功,插入失败,抛出异常。
在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,不同线程下的事务方法工作在独立的事务中。

0 0
原创粉丝点击