首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

Spring引文方式管理事务与传播行为详解

2012-09-08 
Spring注解方式管理事务与传播行为详解23.使用Spring注解方式管理事务与传播行为详解文章分类:Java编程现

Spring注解方式管理事务与传播行为详解
23.使用Spring注解方式管理事务与传播行为详解

文章分类:Java编程



现在要删除id为5的那条记录
PersonServiceBean.java
?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
    package cn.itcast.service.impl;import java.util.List;import javax.sql.DataSource;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;@Transactionalpublic class PersonServiceBean implements PersonService {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}// unchecked ,// checkedpublic void delete(Integer personid){jdbcTemplate.update("delete from person where id=?", new Object[]{personid},new int[]{java.sql.Types.INTEGER});throw new RuntimeException("运行期例外");} ............................................}

    PersonServiceTest.java
    ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
      package junit.test;import org.junit.BeforeClass;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;public class PersonServiceTest {private static PersonService personService;@BeforeClasspublic static void setUpBeforeClass() throws Exception {try {ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");personService = (PersonService) cxt.getBean("personService");} catch (RuntimeException e) {e.printStackTrace();}}........................................@Test public void delete(){ personService.delete(5);} ........................................}
      运行单元测试代码:JUnit抛出运行期例外,查看数据库,id为5那条记录还在,没被删掉。如图
      Spring引文方式管理事务与传播行为详解

      因为抛出了运行期例外,容器检测到业务方法抛出的是运行期例外,那么它就会把这个事务进行回滚;如果抛出的是需要checked的这种例外呢?是否它也会进行回滚呢?
      PersonServiceBean.java
      ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
        package cn.itcast.service.impl;import java.util.List;import javax.sql.DataSource;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.transaction.annotation.Transactional;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;@Transactionalpublic class PersonServiceBean implements PersonService {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);//把数据源dataSource作为构造器参数传进去}public void delete(Integer personid) throws Exception{jdbcTemplate.update("delete from person where id=?", new Object[]{personid},new int[]{java.sql.Types.INTEGER});throw new Exception("运行期例外");} ......................................}
        PersonService.java
        ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
          package cn.itcast.service;import java.util.List;import cn.itcast.bean.Person;public interface PersonService {//保存personpublic void save(Person person);//更新personpublic void update(Person person);//获取personpublic Person getPerson(Integer personid);//获取所有personpublic List<Person> getPersons();//删除指定id的personpublic void delete(Integer personid) throws Exception;}
          PersonServiceTest.java
          ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
            package junit.test;import org.junit.BeforeClass;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;public class PersonServiceTest {private static PersonService personService;@BeforeClasspublic static void setUpBeforeClass() throws Exception {try {ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");personService = (PersonService) cxt.getBean("personService");} catch (RuntimeException e) {e.printStackTrace();}}@Test public void delete(){ //这是要checked的例外,需要我们在外部用try/catch语法对它进行包含try {personService.delete(5);} catch (Exception e) {e.printStackTrace();}}}
            看下采用这种方式,它是否会进行回滚?运行单元测试代码,看见。。虽然是出现了例外,但是数据里id为5的记录被删掉了。看图:
            Spring引文方式管理事务与传播行为详解


            所以,这里给大家总结一句:Spring开启的事务,默认情况下,如果碰到运行期例外,运行期例外,我们一般叫它unchecked例外;还有一种是要checked的例外,这种例外事务是不会回滚的。
            大家应该知道了Spring管理事务,什么时候该提交,什么时候该回滚,它的规则。 那么我们能不能改变这个规则呢?比如现在throw new Exception("运行期例外");它抛出的是checked这种例外,默认情况下它是不会进行事务回滚的,但是如果我们需要它进行事务回滚,这时候可以在delete方法上通过@Transaction这个注解来修改它的行为。

            PersonServiceBean.java
            ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
              package cn.itcast.service.impl;import java.util.List;import javax.sql.DataSource;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.transaction.annotation.Transactional;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;@Transactionalpublic class PersonServiceBean implements PersonService {private JdbcTemplate jdbcTemplate;public void setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);//把数据源dataSource作为构造器参数传进去}@Transactional(rollbackFor=Exception.class)//rollbackFor这属性指定了,既使你出现了checked这种例外,那么它也会对事务进行回滚public void delete(Integer personid) throws Exception{jdbcTemplate.update("delete from person where id=?", new Object[]{personid},new int[]{java.sql.Types.INTEGER});throw new Exception("运行期例外");} ....................................}
              PersonServiceTest.java
              ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
                package junit.test;import org.junit.BeforeClass;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;public class PersonServiceTest {private static PersonService personService;@BeforeClasspublic static void setUpBeforeClass() throws Exception {try {ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");personService = (PersonService) cxt.getBean("personService");} catch (RuntimeException e) {e.printStackTrace();}} ................................@Test public void delete(){try {personService.delete(4);} catch (Exception e) {e.printStackTrace();}} ................................}

                现在数据库person表如下,看图
                Spring引文方式管理事务与传播行为详解


                运行单元测试代码,结果,例外是跑出来了,这种例外是checked这种例外,数据库里,id为4这条记录还在,没有被删掉。看图:
                Spring引文方式管理事务与传播行为详解


                原先,如果我们没有指定@Transactional(rollbackFor=Exception.class)里面rollbackFor这个属性的话,它会提交这个事务,现在要求它碰到checked例外的话,要求他也进行回滚。

                同样,如果它抛出的是运行期例外RuntimeException,我们也可以指定它不进行回滚(默认情况下是会进行回滚的,现在要求它不回滚),
                PersonServiceTest.java
                ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
                  package junit.test;import org.junit.BeforeClass;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import cn.itcast.bean.Person;import cn.itcast.service.PersonService;public class PersonServiceTest {private static PersonService personService;@BeforeClasspublic static void setUpBeforeClass() throws Exception {try {ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml");personService = (PersonService) cxt.getBean("personService");} catch (RuntimeException e) {e.printStackTrace();}} ................................@Test public void delete(){try {personService.delete(4);} catch (Exception e) {e.printStackTrace();}} ................................}
                  现在数据库person表记录如下,看图:
                  Spring引文方式管理事务与传播行为详解

                  运行单元测试代码,id为4的记录被删掉了,看图
                  Spring引文方式管理事务与传播行为详解

                  通过noRollbackFor这个属性和刚才说的rollbackFor属性就可以改变容器的事务行为。默认情况下,它碰到unchecked这种例外,它会进行回滚;碰到checked例外,它不会进行回滚的。我们可以通过noRollbackFor这个属性和rollbackFor属性来修改默认行为。

                  它还有几个功能相同的属性,rollbackForClassName,给它传递的是字符串;noRollbackForClassName,它提供的也是一个类的字符串名称;

                  这事务还有什么样的特点?在PersonServiceBean这个业务bean里面,有一些事务是不需要事务管理的,好比说获取数据的getPersons方法,getPerson方法。 那么不需要事务管理的情况下我们该怎样做呢?因为默认情况下它都会给我们开启事务,开启事务本身对性能会有所影响的,一般不需要事务的话要注明一下它是不需要事务的。可以采用propagation这个事务属性@Transactional(propagation=Propagation.NOT_SUPPORTED),propagation这个属性指定了事务传播行为,我们可以指定它不支持事务,当我们这么写了之后,Spring容器在方法执行前就不会开启事务.

                  那么对于有数据更新的操作,我们是需要Spring容器默认为我们打开的事务的,我们可以通过propagation这个属性对事务进行控制,可以看下有什么事务传播属性


                  事务传播属性
                  -------------------------------
                  REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经处在一个事务中,那么加入到该事务,否则为自己创建一个新的事务。(基本在我们应用开发中,80%是使用这种事务传播行为。在没有标注的操作中,默认是这种行为)
                  NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为它开启事务。如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行。
                  REQUIRESNEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务会被挂起,新的事务会被创建,直到方法执行结束,新事务才算结束,原先的事务才会恢复执行。
                  MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有事务的环境下调用,容器就会抛出例外。
                  SUPPORTS:这一事务属性表明,如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分。如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行。
                  Never:指定业务方法绝对不能在事务范围内执行。如果业务方法在某个事务中执行,容器会抛出例外,只有业务方法没有关联到任何事务,才能正常执行。
                  NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效


                  REQUIRED,NOT_SUPPORTED,REQUIRESNEW,MANDATORY,SUPPORTS,Never这几种事务属性他们的作用和EJB里面作用都是一样的,因为这些事务属性原先EJB里面才有的,后来Spring借鉴了EJB里面的一些东西,也把它们引入到Spring里面去了。当然在Spring里面也单独给我们提供了NESTED这种事务属性。

                  我们看下NESTED这个事务属性的行为,我们对它进行解析下,假设现在我们要使用的是这种事务传播行为

                  事务传播属性NESTED介绍
                  -------------------------
                  ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
                    @Resource OtherService otherService;public void xxx(){ stmt.executeUpdate("update person set name='888' where id=1"); otherService.update(); //OtherService的update方法的事务传播属性为NESTED stmt.executeUpdate("delete from person where id=9");}
                    在这个bean里面,通过@Resource注解把otherService这个bean注入进来,otherService这个bean的update方法的事务传播属性为NESTED。 在xxx这个业务方法里面,它默认是打开事务了的,然后执行第一条update语句,然后执行otherService.update();因为update方法上面标注了事务传播属性为NESTED,(暂时不管。。。),执行完最后的update语句后.......
                    对方法做下展开,看下面:
                    ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
                      Connection conn = null;try { conn.setAutoCommit(false);//把自动提交关闭 Statement stmt = conn.createStatement();//然后创建一个语句对象 stmt.executeUpdate("update person set name='888' where id=1");//对应前面的第一条语句 ----------------------以下部分是Spring容器帮忙做的--------------------------- //执行update方法时,判断它的事务属性是NESTED,它会先做一个保存点 Savepoint savepoint = conn.setSavepoint(); try{ conn.createStatement().executeUpdate("update person set name='222' where sid=2"); }catch(Exception ex){ conn.rollback(savepoint); //当在执行语句时出现问题,这时候会回滚,回滚到保存点 } ----------------------以上部分是Spring容器帮忙做的--------------------------- stmt.executeUpdate("delete from person where id=9");//再执行前面的第二个update方法 conn.commit();//再对事务进行提交 stmt.close(); } catch (Exception e) { conn.rollback(); }finally{ try { if(null!=conn && !conn.isClosed()) conn.close(); } catch (SQLException e) { e.printStackTrace(); } }}
                      现在问题是如果上面
                      ?Spring引文方式管理事务与传播行为详解Spring引文方式管理事务与传播行为详解
                        try{ conn.createStatement().executeUpdate("update person set name='222' where sid=2");}catch(Exception ex){ conn.rollback(savepoint); //当在执行语句时出现问题,这时候会回滚,回滚到保存点 }出错了,那么外部的事务(那两条stmt的update语句)是否还会同步到数据库呢?它是可以的。但是try{}的语句是不能提交到数据库的,因为它出现例外了,事务已经回滚到保存点了。它失败了没关系,它对外部事务没有任何影响,我仍然可以执行后面的语句,再提交。
                        由此可见,嵌套的事务中,内部事务的回滚并不会对外部事务构成任何影响。当然如果说外部事务出问题了,好比说第二条stmt.executeUpdate()执行时出错了,被最外面的例外捕获到,然后回滚conn,大家说说前面的操作(第一个stmt.executeUpdate和NESTED那个update操作)会不会起效果啊? 是不会起效果的,因为外部事务的回滚,它会引起整个事务的失败,就是说即使里面NESTED那个update事务是成功的,但是因为外部事务执行失败了,那么会导致整个事务,包括NESTED那个update事务都会失败,这就是NESTED这个事务属性内部执行的过程。

                        ?

                        ? 除了事务传播行为之外,我们还有其它属性可以设置的,
                        readOnly:代表只读,可通过这个设成只读事务,对于只读事务,它就不能进行更新操作,这样能提高效率的。
                        timeout:代表事务的超时时间,
                        isolation:数据库的隔离级别,这个实际上并不是由Spring容器实现的,这个主要是依赖于底层数据库来实现的。



                        数据库系统提供了四种事务隔离级
                        ---------------------------------------
                        数据库系统提供了四种事务隔离级别供用户选择。不同的隔离级别采用不同的锁类型来实现,在四种隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离级别为Read Commited,如SqlServer,当然也有少部分数据库默认的隔离级别为Repeatable Read ,如Mysql
                        Read Uncommited:读未提交数据(会出现脏读,不可重复读和幻读)。
                        Read Commited:读已提交数据(会出现不可重复读和幻读)
                        Repeatable Read:可重复读(会出现幻读)
                        Serializable:串行化

                        脏读:一个事务读取到另一事务未提交的更新新据。
                        不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
                        幻读:一个事务读取到另一事务已提交的insert数据。


热点排行