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