作为单个逻辑工作单元执行的一系列操作,要么完全执行,要么完全不执行
原子性(Atomicity):要么成功,要么失败。一个事务内的所有SQL语句同步执行(依靠undo.log日志保证)
一致性(Consistency):事务前后总量不变,数据库完整性约束没有被破坏
隔离性(Isolation):一个事务执行不被其他事务干扰(锁+MVCC)
持久性(Durability):事务一旦提交,结果就是永久性的(依靠redo.log日志保证)
关闭自动提交,提交 / 回滚
/**
* @description: JDBC事务管理
* @author: panhong
* @date: 2025/3/19
*/
public class JDBCMain {
public static void main(String[] args) throws SQLException {
Statement stmt = null;
Connection conn = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String sql = "insert into user values(null,'zs',25)";
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/btest_engine",
"root", "root");
stmt = conn.createStatement();
// 开启事务
conn.setAutoCommit(false);
int count = stmt.executeUpdate(sql);//影响的行数
// 模拟异常
int i = 1 / 0;
// 提交事务
conn.commit();
System.out.println(count);
if(count > 0){
System.out.println("添加成功!");
}else{
System.out.println("添加失败!");
}
} catch (Exception e) {
// 回滚事务
conn.rollback();
e.printStackTrace();
} finally {
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
编程式事务
声明式事务
添加@EnableTransactionManagement注解
添加@Transactional注解
① PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务。如果当前存在事务,则加入这个事务。
② PROPAGATION_NESTED:如果当前没有事务,就新建一个事务。如果当前事务存在,则执行一个嵌套事务。
③ PROPAGATION_REQUIRES_NEW:如果当前没有事务,就新建一个事务。如果当前存在事务,把当前事务挂起,并且自己创建一个新的事务给自己使用。
④ PROPAGATION_SUPPORTS:如果当前没有事务,就以非事务方式执行。 如果当前有事务,则使用事务。
⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
⑥ PROPAGATION_MANDATORY:以事务方式执行,如果当前没有事务,就抛出异常。
⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
① DEFAULT:使用数据库的默认隔离级别。
MySQL 的 InnoDB 存储引擎默认使用 REPEATABLE_READ,而 Oracle 通常使用 READ_COMMITTED
② READ_UNCOMMITTED:读未提交
会出现脏读、不可重复读和幻读问题
③ READ_COMMITTED:读已提交
会出现不可重复读和幻读问题
④ REPEATABLE_READ:可重复读
会出现幻读问题
⑤ SERIALIZABLE:可串行化
可以避免所有并发问题,但是效率极低
不同隔离级别产生的并发问题。
① 脏读:事务能够读取未提交的数据。
② 不可重复读:当一个事务A在执行过程中,数据被另外一个事务B修改,造成本次事务前后读取的信息不一样
③ 幻读:当一个事务A读取某一个范围的数据时,另一个事务B在这个范围插入一行数据,A事务再次读取这个范围的数据时,会产生幻读
Spring 事务是基于AOP的,只有使用代理对象调用某个方法时,Spring 事务才能生效
而在一个方法中使用this.xxx()调用方法,this并不是代理对象,而是当前对象,所以会导致事务失效
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void methodA() {
jdbcTemplate.update("insert into user values(null,'zhangsan',18)");
int i = 1 / 0;
}
public void methodB() {
this.methodA(); // 方法内的自调用
}
}
执行后结果:
解决:
① 自己注入自己
// 自己注入自己
@Autowired
private UserService userService;
public void methodB() {
userService.methodA();
}
② 把调用方法拆分到第二个Bean中
③ AopContext.currentProxy() + @EnableAspectJAutoProxy(exposeProxy = true)
public void methodB() {
UserService userService = (UserService)AopContext.currentProxy();
userService.methodA();
}
@Configuration
@ComponentScan(basePackages = "com.pandy")
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
......
}
Spring事务会基于CGLIB来进行AOP,而CGLIB会基于父子类来代理,子类是代理类,父类是被代理类。如果父类中的某个方法是private修饰的,那么子类就没有方法重写它,也就没有方法额外增加Spring事务的逻辑。
@Transactional
private void methodA() {
jdbcTemplate.update("insert into user values(null,'zhangsan',18)");
int i = 1 / 0;
}
原因同上
MyBatis或jdbcTemplate执行SQL时,会从ThreadLocal中去获取数据库连接对象。如果开启事务的线程和执行SQL的线程是用一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿不到数据库连接对象。这样MyBatis或jdbcTemplate就会自己去新建一个数据库连接来执行SQL,此数据库连接的autoCommit是true,那么执行SQL就会提交。后续再抛异常也就不能再回滚之前已经提交的SQL了。
@Transactional
public void methodA() {
new Thread(() -> {
jdbcTemplate.update("insert into user values(null,'zhangsan',18)");
}).start();
int i = 1 / 0;
}
如果用SpringBoot基本没有这个问题。
但是如果用Spring,那么可能会有问题。MyBatis或jdbcTemplate会从ThreadLocal中去获取数据库连接对象,ThreadLocal中存储的是一个Map,Map的key为DataSource对象,value为Connection对象。如果我们没有添加@Configuration注解的话,会导致Map中存储的DataSource对象和MyBatis或jdbcTemplate中存储的DataSource对象不相等,从而拿不到数据库连接信息,导致自己去创建数据库连接对象。
//@Configuration
@ComponentScan(basePackages = "com.pandy") // 扫描指定包下的组件
@EnableTransactionManagement
public class AppConfig {
......
}
默认情况下,Spring会捕获RuntimeException和Error。
@Transactional
public void methodA() {
jdbcTemplate.update("insert into user values(null,'zhangsan',18)");
try {
throw new IOException();
} catch (IOException e) {
e.printStackTrace();
}
}
后面两种方式Spring事务失效显而易见,就不详述了。