深入Spring数据库事务管理(一)


互联网系统时时面对着高并发,在互联网系统中同时跑着成百上千条线程都是十分常见的,尤其是当一些热门网站将刚上市的廉价商品放在线上销售时,这样就会出现多线程的访问网站,进而导致数据库在多个事务访问的环境中,从而引发数据库丢失更新和数据一致性的问题,同时也会给服务器带来很大压力,甚至发生数据库系统死锁和瘫痪进而系统宕机,为了解决这些问题,开发人员需要了解数据库的相关特性,进而规避一些存在的问题,避免数据的不一致性,提高系统性能

一、Spring数据库事务管理器的设计
(一)事务管理器介绍

在Spring中数据库事务是通过PlatformTransactionManager进行管理的,如果只使用jdbcTemplate它自身是无法支持事务的,而能够支持事务的是TransactionTemplate,它是Spring所提供的事务管理器的模板,下面是TransactionTemplate部分源码:

	@Override
	public <T> T execute(TransactionCallback<T> action) throws TransactionException {
		 //使用自定义的事务管理器
		if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
			return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
		}
		else {
			//系统默认事务管理器
			//获取事务状态
			TransactionStatus status = this.transactionManager.getTransaction(this);
			T result;
			try {
				//回调接口方法
				result = action.doInTransaction(status);
			}
			catch (RuntimeException ex) {
                //针对RuntimeException回滚异常
				rollbackOnException(status, ex);
				throw ex;
			}
			catch (Error err) {
				//针对Error回滚异常
				rollbackOnException(status, err);
				throw err;
			}
			catch (Exception ex) {
				//处理Exception回滚异常
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			}
			//事务正常,提交事务
			this.transactionManager.commit(status);
			//返回结果
			return result;
		}
	}

注意事项:
1:事务的创建、提交、回滚都是通过PlatformTransactionManager接口来完成的
2:当事务产生异常时会回滚事务,通过观察上面代码可以发现默认处理了所有的异常,当然Spring也支持自定义异常,我们可以配置那些异常需要回滚那些异常可以放行不进行回滚操作

在这里插入图片描述

在Spring中有多种事务管理器,参考如下
在这里插入图片描述
其中最常用的是DataSourceTransactionManager,它继承的是
AbstractPlatformTransactionManager,而AbstractPlatformTransactionManager又实现了PlatformTransactionManager。

PlatformTransactionManager源码如下:

public interface PlatformTransactionManager {
	//获取事务状态
	TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
    //提交事务
	void commit(TransactionStatus status) throws TransactionException;
    //回滚事务 
	void rollback(TransactionStatus status) throws TransactionException;
}
(二)配置事务管理器

如果匹配的是MyBatis框架的话,使用最多的是DataSourceTransactionManager
如果匹配的是Hibernate框架的话,建议使用HibernateTransactionManager
具体的内部实现不用管它,对外肯定是隐藏,对于开发人员来说使用的方式都差不多

在XML中配置事务管理器,需要三步:
1:添加命名空间
2:配置数据源
3:配置事务管理器,关联数据源

命名空间
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
     http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8</value>
        </property>
        <property name="username">
            <value>root</value>
        </property>
        <property name="password">
            <value>admin</value>
        </property>
    </bean>
    <!--配置jdbcTemplate-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

这里先引入了XML的命名空间,然后定义了数据库连接池,于是使用了DataSourceTransactionManager去定义数据库事务管理器,并且注入了数据库连接池。这样Spring就知道你已经将数据库事务委托给事务管理器管理了.其中使用了jdbcTemplate,如果没有配置事务管理器的话,数据库的资源会由jdbcTemplate来管理,如果已经配置了事务管理器的话,则交给事务管理器负责处理

在Spring中有两种事务规则:
1:编程式事务
2:声明式事务(XML配置方式、注解事务方式)

(三)使用JAVA配置方式实现Spring数据库事务

1:构建JAVA配置类
2:实现接口TransactionManagementConfigurer,重写方法
3:引入数据源

@Configuration
@EnableTransactionManagement
public class MyConfig implements TransactionManagementConfigurer {

    private DriverManagerDataSource dataSource;


    @Bean(name = "dataSource")
    public DataSource initDataSource() {
        if (dataSource != null) {
            return dataSource;
        }
        dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("admin");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate template = new JdbcTemplate();
        template.setDataSource(initDataSource());
        return template;
    }


    @Bean(name = "transactionManager")
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(initDataSource());
        return transactionManager;
    }
}

用Java配置的方式实现Spring数据库事务,需要在配置类中实现接口TransactionManagementConfigurer 的annotationDrivenTransactionManager方法.Spring会把该方法返回的事务管理器作为程序中的事务管理器
上述代码中annotationDrivenTransactionManager方法中注入了DataSource 管理对应的数据库资源
其中使用了注解@EnableTransactionManagement,在Spring上下文中使用注解@Transactional,Spring就会知道使用这个事务管理器去管理事务了

二、编程式事务

编程式事务以代码的方式管理事务,事务将由开发人员通过自己的代码来实现,这里需要一个事务定义类接口TransactionDefinition,我们可以使用它的实现类DefaultTransactionDefinition来完成需要
下面代码测试使用的是上面的JAVA配置

    @Test
    public void DefaultTransactionDefinitionTest() {
        //启动数据库配置和事务管理器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        //获取JdbcTemplate
        JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate");
        //获取事务定义
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        //获取事务管理器
        PlatformTransactionManager transactionManager = (PlatformTransactionManager) context.getBean("transactionManager");
        //设置事务定义
        TransactionStatus status = transactionManager.getTransaction(definition);

        try {
            jdbcTemplate.update("UPDATE USERS SET NAME='LISI' WHERE ID=1");
            //模拟异常
            int i = 1 / 0;
            transactionManager.commit(status);
            System.out.println("正常执行");
        } catch (Exception e) {
            System.out.println("异常回滚");
            transactionManager.rollback(status);
        }
    }

编程式事务,所有的事务都是由开发人员自己管理控制的,由于JdbcTemplate本身的数据库资源已经由事务管理器管理,因此当它执行增删改查操作的时候不会自动提交事务,而是由事务管理器执行commit提交事务和回滚事务

三、声明式事务

声明式事务是一种约定型的事务,在大部分情况下,当使用数据库事务时,大部分的场景是在代码中发生了异常时需要回滚事务,而不发生异常则提交事务,从而保证数据库数据的一致性.
如果使用的是声明式事务,那么当你的业务方法不发生异常或者发生异常,但是该异常被设置为允许提交事务时,Spring就会让事务管理器提交事务,而发生异常并且该异常是的配置信息允许不允许提交事务时,则让事务管理器回滚事务

声明式事务允许自定义事务接口TransactionDefinition,它可以由XML或者注解@Transactional进行配置

使用声明式事务注解@Transactional,需要配置注解驱动
<tx:annotation-driven transaction-manager=“transactionManager”/>

@Transactional

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	@AliasFor("transactionManager")
	String value() default "";

	@AliasFor("value")
	String transactionManager() default "";

	Propagation propagation() default Propagation.REQUIRED;

	Isolation isolation() default Isolation.DEFAULT;

	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	boolean readOnly() default false;

	Class<? extends Throwable>[] rollbackFor() default {};
	
	String[] rollbackForClassName() default {};

	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};
}

相关配置描述:
String value() default “”;
定义事务管理器,即是PlatformTransactionManager的实现类

String transactionManager() default “”;
定义事务管理器,即是PlatformTransactionManager的实现类
功能和value()相同

Propagation propagation() default Propagation.REQUIRED;
传播行为,针对于方法之间调用的问题处理,默认Propagation.REQUIRED;

Isolation isolation() default Isolation.DEFAULT;
隔离级别,针对于多个事务同时存在的处理机制,默认取数据库默认隔离级别

int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
超时时间,单位为秒

boolean readOnly() default false;
是否开启只读事务,默认false

Class<? extends Throwable>[] rollbackFor() default {};
回滚事务的异常类定义,只有当方法产生所定义的异常时,才回滚事务,否则就提交事务

String[] rollbackForClassName() default {};
回滚事务的异常类名定义

Class<? extends Throwable>[] noRollbackFor() default {};
当产生那些异常不回滚事务,当产生对应的异常时,继续提交事务

String[] noRollbackForClassName() default {};
同noRollbackFor,使用类的名称定义

XML配置事务管理器

使用XML的方式来构建事务管理器,需要一个事务拦截器TransactionInterceptor,可以把拦截器想象成AOP编程

<!--配置拦截器-->
    <bean class="org.springframework.transaction.interceptor.TransactionInterceptor" id="transactionInterceptor">
        <!--配置属性 事务管理器-->
        <property name="transactionManager" ref="transactionManager"/>
        <!--配置事务属性-->
        <property name="transactionAttributes">
            <props>
                <!--Key代表的是业务方法的正则匹配,而其内容可以配置各类事务定义参数-->
                <!--添加-->
                <prop key="insert*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="add*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="save*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <!--查询-->
                <prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                <!--删除-->
                <prop key="del*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="remove*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <!--修改更新-->
                <prop key="update*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
            </props>
        </property>
    </bean>

配置transactionAttributes的内容是关注的重点,Spring IOC启动时会解析这些内容,然后放到事务定义类TransactionDefinition中,在运行时会根据正则表达式的匹配度决定方法采取那种策略.
这里使用了拦截器和AOP技术,这也说明了声明式事务的原理就是AOP,即动态代理

上面配置中只配置了Spring所采取的事务策略,并没有告知Spring拦截那些类,因此我们还需要告诉Spring那些类需要使用事务拦截器进行拦截,为此需要在配置一个BeanNameAutoProxyCreator

  <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <list>
                <value>*ServiceImpl</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>transactionInterceptor</value>
            </list>
        </property>
 </bean>

BeanName属性告诉Spring如何拦截类,由于声明为*ServiceImpl,所有关于Service的实现类都会被拦截,然后
interceptorNames则是定义事务拦截器,这样对应的类和方法就会被事务管理器拦截了

事务定义器



public interface TransactionDefinition {
	
    //传播行为,共七个
	int PROPAGATION_REQUIRED = 0;

	int PROPAGATION_SUPPORTS = 1;

	int PROPAGATION_MANDATORY = 2;

	int PROPAGATION_REQUIRES_NEW = 3;

	int PROPAGATION_NOT_SUPPORTED = 4;

	
	int PROPAGATION_NEVER = 5;


	int PROPAGATION_NESTED = 6;
	
    //隔离级别
	int ISOLATION_DEFAULT = -1;  //默认隔离级别

	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
    
	//超时时间,-1表示永不超时
	int TIMEOUT_DEFAULT = -1;
     //获取传播行为 
	int getPropagationBehavior();
    //获取隔离级别
	int getIsolationLevel();
    //获取超时时间
	int getTimeout();
    //是否只读事务 
	boolean isReadOnly();
	//获取事务定义器名称
	String getName();

}

声明式事务的约定流程

通过上面分析可以知道声明式事务存在俩种方式:XML和注解方式,可以作用在类和方法上
@Transactional作用在类上表示该类使用相同的隔离级别和传播行为,作用在方法上表示单个方法的隔离级别,在一个类中添加了该注解的存在事务,不添加就不使用事务,没有添加事务的就是一个动作或者说一行代码就是一个事务
Spring会去读入注解或者XML的配置信息,并且保存到事务定义类中,即TransactionDefinition接口的子类,以备后面使用.当运行时会让Spring拦截注解标注的某一个方法或者类的所有方法。通过AOP的方式,将事务代码织入到AOP的流程中,然后给出它的约定.

四、声明式事务案例测试
前置准备
public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
@Repository
public class UserDaoImpl {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insert(User user) {
        jdbcTemplate.execute("INSERT INTO USERS VALUES (" + user.getId() + ",'" + user.getName() + "')");
    }

    public void update(User user) {
        jdbcTemplate.execute("UPDATE  USERS SET name='" + user.getName() + "' WHERE id=" + user.getId());
    }

}
@Service
public class UserServiceImpl {

    @Autowired
    private UserDaoImpl userDao;
@Transactional
    public void addUser(User addUser, User updateUser) {
        userDao.insert(addUser);
        int i = 1 / 0;
        userDao.update(updateUser);
    }}
测试步骤:
1:切换XML和注解
2:service方法中调用俩个dao层方法
3:在调用方法时异常,观察俩个SQL操作是否一致
XML配置测试

配置文件

<?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:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <tx:annotation-driven transaction-manager="transactionManager"/>
    <!--注解扫描-->
    <context:annotation-config/>
    <context:component-scan base-package="dao,;service"/>
    <!--数据源配置-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8</value>
        </property>
        <property name="username">
            <value>root</value>
        </property>
        <property name="password">
            <value>admin</value>
        </property>
    </bean>
    <!--配置jdbcTemplate-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--配置事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>


    <!--配置拦截器-->
    <bean class="org.springframework.transaction.interceptor.TransactionInterceptor" id="transactionInterceptor">
        <!--配置属性 事务管理器-->
        <property name="transactionManager" ref="transactionManager"/>
        <!--配置事务属性-->
        <property name="transactionAttributes">
            <props>
                <!--Key代表的是业务方法的正则匹配,而其内容可以配置各类事务定义参数-->
                <!--添加-->
                <prop key="insert*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="add*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="save*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <!--查询-->
                <prop key="select*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                <!--删除-->
                <prop key="del*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <prop key="remove*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
                <!--修改更新-->
                <prop key="update*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
            </props>
        </property>
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames">
            <list>
                <value>*ServiceImpl</value>
            </list>
        </property>
        <property name="interceptorNames">
            <list>
                <value>transactionInterceptor</value>
            </list>
        </property>

    </bean>


</beans>

测试类

/**
     * 测试声明式事务XML配置
     */
    @Test
    public void transactionInterceptorTest() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
        User user = new User();
        user.setId(3);
        user.setName("WANGWU");
        User updateUser = new User();
        updateUser.setId(1);
        updateUser.setName("LIHUA");
        userServiceImpl.addUser(user, updateUser);
    }

执行后发现,俩个操作都没有被提交

注解方式测试

Java配置类


@ComponentScan(basePackages = {"dao", "service"})
@Configuration
@EnableTransactionManagement
public class MyConfig implements TransactionManagementConfigurer {

    private DriverManagerDataSource dataSource;


    @Bean(name = "dataSource")
    public DataSource initDataSource() {
        if (dataSource != null) {
            return dataSource;
        }
        dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("admin");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate() {
        JdbcTemplate template = new JdbcTemplate();
        template.setDataSource(initDataSource());
        return template;
    }


    @Bean(name = "transactionManager")
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(initDataSource());
        return transactionManager;
    }
}

测试类

  /**
     *  测试注解事务
     */
    @Test
    public void transactionTest() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl");
        User user = new User();
        user.setId(3);
        user.setName("WANGWU");
        User updateUser = new User();
        updateUser.setId(1);
        updateUser.setName("LIHUA");
        userServiceImpl.addUser(user, updateUser);


    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZWZhangYu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值