Spring 声明式事务

概念

编程式事务管理

在代码中显式调用bginTransaction,commit,rollback等与事物处理相关的方法,这就是编程式事务管理

Connection conn= null;
try{
	 Class.forName("com.mysql.cj.jdbc.Driver");
	 conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC","root","123456");
    .......
    //省略代码
    conn.commit();   //提交    
}catch(Exception e){
    conn.rollback();   //回滚
}

类似这样的代码,显式调用,就是编程式事务管理。当只有少数事务管理才比较合适

声明式事务管理

事务控制代码已经由 spring 写好.程序员只需要声明出哪些方 法需要进行事务控制和如何进行事务控制.

Spring的声明式管理是通过AOP技术实现的事务管理,其本质是对方法前后进行拦截,然后在目标方法开始创建之前创建或加入一个事务,在执行完目标方法之后根据情况提交或回滚事务。

与编程式事务管理相比,声明式事务管理唯一不足的地方是最细粒度只能作用到方法级别,无法做到编程式事务那样可以作用到代码块级别。但即便有这样的需求,也可以通过变通的方法进行解决,例如可以将需要进行事物处理的代码块独立为方法等

基于AOP的原理,在方法执行开始前开启事务,方法执行后提交或回滚事务。所以我们只需要关注逻辑代码

配置声明式事务管理

1.创建dao层
接口

package cn.com.dao;

public interface UserDao {
    public int add(String sql,Object param[]);
    public int delete(String sql,Object param[]);
}

实现类

package cn.com.dao.impl;

import cn.com.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

public class UserDaoImpl implements UserDao {
    private JdbcTemplate jdbcTemplate;

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public int add(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }

    @Override
    public int delete(String sql, Object[] param) {
        return jdbcTemplate.update(sql,param);
    }
}

2.创建service层
接口

package cn.com.service;

public interface UserService {
    public int add(String sql,Object param[]);
    public int delete();
}

实现

ppackage cn.com.service.impl;

import cn.com.dao.UserDao;
import cn.com.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;


public class UserServiceImpl implements UserService {
    private UserDao userDao;

    public UserDao getUserDao() {
        return userDao;
    }

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public int addUser() {
        String sql="insert into user values(?,?)";
        Object param[]={"wangwu","123456"};
        int index=userDao.add(sql,param);
        index+=userDao.add(sql,param);
        return index;
    }
    @Override
    public int deleteUser() {
        return 0;
    }
}


3.创建Controller

package cn.com.controller;

import cn.com.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;


public class UserController {


    private UserService userService;

    public UserService getUserService() {
        return userService;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void test(){
        userService.addUser();
    }
}

测试插入两条相同数据,主键重复

4.配置文件

<?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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/cache
                            http://www.springframework.org/schema/cache/spring-cache.xsd
                            http://www.springframework.org/schema/tx
                            http://www.springframework.org/schema/tx/spring-tx.xsd
                            http://www.springframework.org/schema/aop
                            http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="cn.com"/>

    <!--数据源封装类-->
    <bean id="dataSouce" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/db_contracts?serverTimezone=UTC" ></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!--配置JDBC模板-->
    <bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate">
        <!--数据库连接信息来源于dataSouce-->
        <property name="dataSource" ref="dataSouce"></property>
    </bean>

    <!--为数据源添加事务管理器-->
   <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="dataSouce"></property>
   </bean>


    <!--编写通知声明事务-->
    <tx:advice id="myAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!--任意方法-->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--AOP-->
    <aop:config>
        <aop:pointcut id="mypoint" expression="execution(* cn.com.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="myAdvice" pointcut-ref="mypoint"/>
    </aop:config>

    <bean id="userDao" class="cn.com.dao.impl.UserDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <bean id="userService" class="cn.com.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
    </bean>

    <bean id="userController" class="cn.com.controller.UserController">
        <property name="userService" ref="userService"/>
    </bean>
</beans>

4.测试

package cn.com.test;

import cn.com.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("appliactionContext.xml");
        UserController userController=ac.getBean("userController",UserController.class);
        userController.test();
    }
}

在这里插入图片描述

<tx:method>的标签中除了name属性还有其他属性

  <tx:method name="*" read-only="" propagation="" isolation=""  rollback-for=“” no-rollback-for=””/>

1.name属性:表示哪些方法需要有事务控制 ,可以使用*通配符。
*表示所有方法 ins*表示以Ins开头的方法需要有事务控制
2.readonly:表示是否是只读事务
3.propagation:表示控制事务传播行为
4.isolation:表示事务隔离级别
5.rollback-for=”异常类型” :
当出现什么异常时需要进行回滚,手动抛异常一定要给该属性值.
6. no-rollback-for: 当出现什么异常时不滚回事务

事务的传播行为

当一个具有事务控制的方法被另一个有事务控制的方法调用 后,需要如何管理事务

propagation:表示控制事务传播行为,这个属性有很多取值

取值含义
REQUIRED (默认值)如果当前有事务就在事务中执行,如果当前没有事务就新建一个事务
SUPPORTS如果当前有事务就在事务中执行,如果当前没有事务就在没有事务的状态下执行
MANDATORY必须在事务内部执行,如果当前有事务就在事务内执行。如果当前没有事务就报错
REQUIRES_NEW如果当前没有事务就新建事务,如果当前有事务就把当前事务挂起
NOT_SUPPORTED如果当前没有事务就正常执行,如果当前有事务就把当前事务挂起
NEVER必须在非事务状态下执行,如果当前没有事务正常执行。如果当前有事务就报错
NESTED如果没有事务新建事务。如果当前有事务,创建一个嵌套事务。

事务隔离级别

在多线程或并发访问下,保证访问到数据是具有完整性的

在在多线程或并发访问下,容易出现以下三种情况
1.脏读
一个事务A读取到了另一个事务B中未提交的数据,而另一个事务数据可能进行了改变,此时A事务读取的数据就可能和数据库中的数据不一致。
2.不可重复读
当一个事务A第一次读取数据后,另一个事务B对事务A读取的数据进行修改,而当事务A中再次读取的数据时,事务A中再次读取的数据和之前读取的数据不一致
3.幻读
当一个事务A读取了一些数据后,另一个事务B在该表中插入了一些数据。事务A读取的数据就和数据库中的数据不一致。

不可重复读和幻读的区别
不可重复读主要针对的是某行数据或某列数据,主要针对的操作是修改。两次读取在同一个事务内

幻读主要针对的是一个表,主要针对的操作是新增或删除。是两次事务的结果

isolation属性的取值

取值含义
DEFAULT(默认值)由数据库判断使用什么隔离级别
READ_UNCOMMITTED可以读取未提交数据,可能出现脏读,不可重复读和幻读
READ_COMMITTED只能读取其他事务已提交数据.可以防止脏读,可能出现不可重复读和幻读.
REPEATABLE_READ读取的数据被添加锁,防止其他事务修改此数据,可以防止脏读和不可重复读,可能出现幻读
SERIALIZABLE对整个表添加锁.一个事务在操作数据时,其他事务等待事务操作完成后才能操作这个表。最安全,效率最低

在事务处理中捕获异常及回滚

当只在代码中捕获异常时

  @Override
    public int addUser() {
        try {
            String sql="insert into user values(?,?)";
            Object param[]={"wangwu","123456"};
            int index=userDao.add(sql,param);
            index+=userDao.add(sql,param);
            return index;
        } catch (Exception e) {
            System.out.println("回滚");
            System.out.println(e.getMessage());
            return 0;
        }
    }

在这里插入图片描述
在这里插入图片描述
但还是插入了,没有回滚
即使配置rollback-for="java.lang.Exception"也不会回滚

因为默认情况下,Spring只在发生未被捕获的RuntimeException时才回滚事务

如何捕获异常和回滚

 <!--编写通知声明事务-->
    <tx:advice id="myAdvice" transaction-manager="txManager">
        <tx:attributes>
            <!--任意方法-->
            <tx:method name="*" rollback-for="java.lang.Exception"/>
        </tx:attributes>
    </tx:advice>

设置回滚类型

@Override
    public int addUser() {
        try {
            String sql="insert into user values(?,?)";
            Object param[]={"wangwu","123456"};
            int index=userDao.add(sql,param);
            index+=userDao.add(sql,param);
            return index;
        } catch (Exception e) {
            System.out.println("回滚");
            System.out.println(e.getMessage());
            throw new RuntimeException();
        }
    }

在catch中添加throw new RuntimeException()
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值