概念
编程式事务管理
在代码中显式调用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()