zoukankan      html  css  js  c++  java
  • Spring事务详解

    Spring Boot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可。

    关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。

    你可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是 PlatformTransactionManager 接口的哪个实现类。

    @EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
    @SpringBootApplication
    public class ProfiledemoApplication {
    
        @Bean
        public Object testBean(PlatformTransactionManager platformTransactionManager){
            System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
            return new Object();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(ProfiledemoApplication.class, args);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这些SpringBoot为我们自动做了,这些对我们并不透明,如果你项目做的比较大,添加的持久化依赖比较多,我们还是会选择人为的指定使用哪个事务管理器。 
    代码如下:

    @EnableTransactionManagement
    @SpringBootApplication
    public class ProfiledemoApplication {
    
        // 其中 dataSource 框架会自动为我们注入
        @Bean
        public PlatformTransactionManager txManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
        @Bean
        public Object testBean(PlatformTransactionManager platformTransactionManager) {
            System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
            return new Object();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(ProfiledemoApplication.class, args);
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。

    然后在Service中,被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。

    对于同一个工程中存在多个事务管理器要怎么处理,请看下面的实例,具体说明请看代码中的注释。

    @EnableTransactionManagement // 开启注解事务管理,等同于xml配置文件中的 <tx:annotation-driven />
    @SpringBootApplication
    public class ProfiledemoApplication implements TransactionManagementConfigurer {
    
        @Resource(name="txManager2")
        private PlatformTransactionManager txManager2;
    
        // 创建事务管理器1
        @Bean(name = "txManager1")
        public PlatformTransactionManager txManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
        // 创建事务管理器2
        @Bean(name = "txManager2")
        public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
            return new JpaTransactionManager(factory);
        }
    
        // 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
        @Override
        public PlatformTransactionManager annotationDrivenTransactionManager() {
            return txManager2;
        }
    
        public static void main(String[] args) {
            SpringApplication.run(ProfiledemoApplication.class, args);
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    
    @Component
    public class DevSendMessage implements SendMessage {
    
        // 使用value具体指定使用哪个事务管理器
        @Transactional(value="txManager1")
        @Override
        public void send() {
            System.out.println(">>>>>>>>Dev Send()<<<<<<<<");
            send2();
        }
    
        // 在存在多个事务管理器的情况下,如果使用value具体指定
        // 则默认使用方法 annotationDrivenTransactionManager() 返回的事务管理器
        @Transactional
        public void send2() {
            System.out.println(">>>>>>>>Dev Send2()<<<<<<<<");
        }
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注: 
    如果Spring容器中存在多个 PlatformTransactionManager 实例,并且没有实现接口 TransactionManagementConfigurer 指定默认值,在我们在方法上使用注解 @Transactional 的时候,就必须要用value指定,如果不指定,则会抛出异常。

    对于系统需要提供默认事务管理的情况下,实现接口 TransactionManagementConfigurer 指定。

    对有的系统,为了避免不必要的问题,在业务中必须要明确指定 @Transactional 的 value 值的情况下。不建议实现接口 TransactionManagementConfigurer,这样控制台会明确抛出异常,开发人员就不会忘记主动指定。

    http://blog.csdn.net/catoop/article/details/50595702

    注意的几点:

    1  @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.

    2 默认情况下,一个有事务方法, 遇到RuntiomeException 时会回滚 .  遇到 受检查的异常 是不会回滚 的. 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .

    @Transactional  的所有可选属性如下:

    属性 类型 默认值 说明
    propagation Propagation枚举 REQUIRED 事务传播属性 (下有说明)
    isolation isolation枚举 DEFAULT 事务隔离级别 (另有说明)
    readOnly boolean false 是否只读
    timeout int -1 超时(秒)
    rollbackFor Class[] {} 需要回滚的异常类
    rollbackForClassName String[] {} 需要回滚的异常类名
    noRollbackFor Class[] {} 不需要回滚的异常类
    noRollbackForClassName String[] {} 不需要回滚的异常类名

     事务的隔离级别 有如下可选:

    可以去看spring源码 : org.springframework.transaction.annotation.Isolation

    (用时,导入org.springframework.transaction.annotation.Isolation,再在Transactional括号里用如isolation = Isolation.DEFAULT)

    我的"隔离级别"相关文章   有不明白的,可以去看看.

    DEFAULT 采用数据库默认隔离级别
    READ_UNCOMMITTED 请看"隔离级别"相关文章
    READ_COMMITTED 请看"隔离级别"相关文章
    REPEATABLE_READ 请看"隔离级别"相关文章
    SERIALIZABLE 请看 "隔离级别"相关文章

    事务的传播属性 ,有如下可选

    可以去看spring源码 : org.springframework.transaction.annotation.Propagation

    (用时,导入org.springframework.transaction.annotation.Propagation,再在Transactional括号里用如propagation = Propagation.REQUIRED)

    REQUIRED 业务方法需要在一个事务中运行,如果方法运行时,已处在一个事务中,那么就加入该事务,否则自己创建一个新的事务.这是spring默认的传播行为.
    SUPPORTS 如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行.
    MANDATORY 只能在一个已存在事务中执行,业务方法不能发起自己的事务,如果业务方法在没有事务的环境下调用,就抛异常
    REQUIRES_NEW 业务方法总是会为自己发起一个新的事务,如果方法已运行在一个事务中,则原有事务被挂起,新的事务被创建,直到方法结束,新事务才结束,原先的事务才会恢复执行.
    NOT_SUPPORTED 声明方法需要事务,如果方法没有关联到一个事务,容器不会为它开启事务.如果方法在一个事务中被调用,该事务会被挂起,在方法调用结束后,原先的事务便会恢复执行.
    NEVER 声明方法绝对不能在事务范围内执行,如果方法在某个事务范围内执行,容器就抛异常.只有没关联到事务,才正常执行.
    NESTED 如果一个活动的事务存在,则运行在一个嵌套的事务中.如果没有活动的事务,则按REQUIRED属性执行.它使用了一个单独的事务, 这个事务拥有多个可以回滚的保证点.内部事务回滚不会对外部事务造成影响, 它只对DataSourceTransactionManager 事务管理器起效.
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    [java] view plain copy
     
    1. //事务传播属性  
    2.     @Transactional(propagation=Propagation.REQUIRED) //如果有事务,那么加入事务,没有的话新建一个(不写的情况下)  
    3.     @Transactional(propagation=Propagation.NOT_SUPPORTED) //容器不为这个方法开启事务  
    4.     @Transactional(propagation=Propagation.REQUIRES_NEW) //不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务  
    5.     @Transactional(propagation=Propagation.MANDATORY) //必须在一个已有的事务中执行,否则抛出异常  
    6.     @Transactional(propagation=Propagation.NEVER) //必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)  
    7.     @Transactional(propagation=Propagation.SUPPORTS) //如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.  
    8.       
    9.    
    10.     @Transactional(propagation=Propagation.NESTED)   
    11.     @Transactional (propagation = Propagation.REQUIRED,readOnly=true) //readOnly=true只读,不能更新,删除   
    12.     @Transactional (propagation = Propagation.REQUIRED,timeout=30)//设置超时时间   
    13.     @Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)//设置数据库隔离级别  
     
    用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.
    默认遇到运行期例外(throw new RuntimeException("注释");)会回滚,即遇到不受检查(unchecked)的例外时回滚;
    而遇到需要捕获的例外(throw new Exception("注释");)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚 ,如下:
    @Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
        public void methodName() {
           throw new Exception("注释");
            
        } 
    @Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚
        public ItimDaoImpl getItemDaoImpl() {
            throw new RuntimeException("注释");
        } 

    http://blog.csdn.net/hz_chenwenbiaotmb/article/details/5793538

    什么是事务?

    我们在开发企业应用时,对于业务人员的一个操作实际是对数据读写的多步操作的结合。由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作数据的并不可靠,需要在这种情况下进行回退。

    事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务开始未进行操作的状态。

    事务管理是Spring框架中最为常用的功能之一,我们在使用Spring Boot开发应用时,大部分情况下也都需要使用事务。

    快速入门

    在Spring Boot中,当我们使用了spring-boot-starter-jdbc或spring-boot-starter-data-jpa依赖的时候,框 架会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager。所以我们不需要任何额外 配置就可以用@Transactional注解进行事务的使用。

    在该样例工程中(若对该数据访问方式不了解,可先阅读该文章),我们引入了spring-data-jpa,并创建了User实体以及对User的数据访 问对象UserRepository,在ApplicationTest类中实现了使用UserRepository进行数据读写的单元测试用例,如下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(Application.class)
    public class ApplicationTests {
    
        @Autowired
        private UserRepository userRepository;
    
        @Test
        public void test() throws Exception {
    
            // 创建10条记录
            userRepository.save(new User("AAA", 10));
            userRepository.save(new User("BBB", 20));
            userRepository.save(new User("CCC", 30));
            userRepository.save(new User("DDD", 40));
            userRepository.save(new User("EEE", 50));
            userRepository.save(new User("FFF", 60));
            userRepository.save(new User("GGG", 70));
            userRepository.save(new User("HHH", 80));
            userRepository.save(new User("III", 90));
            userRepository.save(new User("JJJ", 100));
    
            // 省略后续的一些验证操作
        }
    
    
    }
    

    可以看到,在这个单元测试用例中,使用UserRepository对象连续创建了10个User实体到数据库中,下面我们人为的来制造一些异常,看看会发生什么情况。

    通过定义User的name属性长度为5,这样通过创建时User实体的name属性超长就可以触发异常产生。

    @Entity
    public class User {
    
        @Id
        @GeneratedValue
        private Long id;
    
        @Column(nullable = false, length = 5)
        private String name;
    
        @Column(nullable = false)
        private Integer age;
    
        // 省略构造函数、getter和setter
    
    }
    

    修改测试用例中创建记录的语句,将一条记录的name长度超过5,如下:name为HHHHHHHHH的User对象将会抛出异常。

    // 创建10条记录
    userRepository.save(new User("AAA", 10));  
    userRepository.save(new User("BBB", 20));  
    userRepository.save(new User("CCC", 30));  
    userRepository.save(new User("DDD", 40));  
    userRepository.save(new User("EEE", 50));  
    userRepository.save(new User("FFF", 60));  
    userRepository.save(new User("GGG", 70));  
    userRepository.save(new User("HHHHHHHHHH", 80));  
    userRepository.save(new User("III", 90));  
    userRepository.save(new User("JJJ", 100));
    

    执行测试用例,可以看到控制台中抛出了如下异常,name字段超长:

    2016-05-27 10:30:35.948  WARN 2660 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1406, SQLState: 22001  
    2016-05-27 10:30:35.948 ERROR 2660 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Data truncation: Data too long for column 'name' at row 1  
    2016-05-27 10:30:35.951  WARN 2660 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Warning Code: 1406, SQLState: HY000  
    2016-05-27 10:30:35.951  WARN 2660 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Data too long for column 'name' at row 1
    
    org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not execute statement  
    

    此时查数据库中,创建了name从AAA到GGG的记录,没有HHHHHHHHHH、III、JJJ的记录。而若这是一个希望保证完整性操作的情况 下,AAA到GGG的记录希望能在发生异常的时候被回退,这时候就可以使用事务让它实现回退,做法非常简单,我们只需要在test函数上添加 @Transactional 注解即可。

    @Test
    @Transactional
    public void test() throws Exception {
    
        // 省略测试内容
    
    }
    

    这里主要通过单元测试演示了如何使用 @Transactional 注解来声明一个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独立,会使用 @Rollback 注解让每个单元测试都能在结束时回滚。而真正在开发业务逻辑时,我们通常在service层接口中使用 @Transactional 来对各个业务逻辑进行事务管理的配置,例如:

    public interface UserService {
    
        @Transactional
        User login(String name, String password);
    
    }
    

    事务详解

    上面的例子中我们使用了默认的事务配置,可以满足一些基本的事务需求,但是当我们项目较大较复杂时(比如,有多个数据源等),这时候需要在声明事务时,指定不同的事务管理器。对于不同数据源的事务管理配置可以见 《Spring Boot多数据源配置与使用》 中的设置。在声明事务时,只需要通过value属性指定配置的事务管理器名即可,例如: @Transactional(value="transactionManagerPrimary") 。

    除了指定不同的事务管理器之后,还能对事务进行隔离级别和传播行为的控制,下面分别详细解释:

    隔离级别

    隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

    我们可以看 org.springframework.transaction.annotation.Isolation 枚举类中定义了五个表示隔离级别的值:

    public enum Isolation {  
        DEFAULT(-1),
        READ_UNCOMMITTED(1),
        READ_COMMITTED(2),
        REPEATABLE_READ(4),
        SERIALIZABLE(8);
    }
    
    • DEFAULT :这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是: READ_COMMITTED 。
    • READ_UNCOMMITTED :该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
    • READ_COMMITTED :该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
    • REPEATABLE_READ :该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
    • SERIALIZABLE :所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

    指定方法:通过使用 isolation 属性设置,例如:

    @Transactional(isolation = Isolation.DEFAULT)
    

    传播行为

    所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

    我们可以看 org.springframework.transaction.annotation.Propagation 枚举类中定义了6个表示传播行为的枚举值:

    public enum Propagation {  
        REQUIRED(0),
        SUPPORTS(1),
        MANDATORY(2),
        REQUIRES_NEW(3),
        NOT_SUPPORTED(4),
        NEVER(5),
        NESTED(6);
    }
    
    • REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    • SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    • MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
    • NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
    • NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。

    指定方法:通过使用 propagation 属性设置,例如:

    @Transactional(propagation = Propagation.REQUIRED)
    

    http://www.cnblogs.com/sllina/p/5694805.html

  • 相关阅读:
    WPF中如何确定一个页面是否是在浏览器中承载的
    WPF中Image控件的Source属性
    WPF中如何使用代码操作数据模板生成的控件
    WPF中连续旋转时角度错误的问题
    在没有 IIS 的条件下运行 ASMX(转自 msdn)
    在 ASP.NET 2.0 中上载文件 —— 解决文件大小限制
    javascript专题:如何构建自己的js库
    WPF4多点触摸事件
    事件冒泡
    《精通javascript》几个简单的函数(一)
  • 原文地址:https://www.cnblogs.com/zhuyeshen/p/10907843.html
Copyright © 2011-2022 走看看