事务的概念及其特性
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元。
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
MyBatis 的事务回滚例子
注意:在 MyBatis 中,每一个 SqlSession(SQL 会话)都对应一个 Transaction(事务):
/**
* <p> 从数据源中打开一个会话
* @param execType 执行类型
* @param level 事务隔离级别
* @param autoCommit 自动提交
* @return SqlSession(SQL 会话)
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
// 事务。一个 SqlSession 对应一个事务
Transaction tx = null;
try {
// 获取环境参数,即 MyBatis 配置文件中定义的环境
final Environment environment = configuration.getEnvironment();
// 从环境中获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从事务工厂中获取事务
// 在 MyBatis 中有两种类型的事务管理器:JDBC 和 MANAGED
// JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
// MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。
// 参考:https://mybatis.org/mybatis-3/zh/configuration.html#environments
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 返回 SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
一个操作异常,事务回滚的例子:
package com.mk;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;
import com.mk.pojo.Flower;
public class Application {
private static final Logger logger = Logger.getLogger(Application.class);
public static void main(String[] args) {
InputStream is = null;
try {
is = Resources.getResourceAsStream("mybatis.xml");
System.out.println(is);
} catch (IOException e) {
logger.error("Exception: ", e);
}
if (is != null) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); // 获取 SQL 会话工厂
SqlSession session = factory.openSession(); // 获取 SQL 会话
// 待插入数据 1
String name = "火龙果";
BigDecimal price = new BigDecimal("999.99");
String production = "中美洲";
Flower f1 = new Flower(null, name, price, production);
// 待插入数据 2
name = "葡萄";
price = new BigDecimal("1000"); // 价格不满足表中字段类型要求,将导致错误发生
production = "亚洲西部";
Flower f2 = new Flower(null, name, price, production);
// 新增
Integer result = 0;
try {
result += session.insert("com.mk.mapper.FlowerMapper.insert", f1);
result += session.insert("com.mk.mapper.FlowerMapper.insert", f2);
} catch (Exception e) {
logger.error("Exception: ", e);
session.rollback(); // 回滚
result = 0;
} finally {
session.commit(); // 提交
session.close(); // 关闭会话
try {
if (is != null) {
is.close();
}
} catch (IOException e) {
logger.error("Exception: ", e);
}
}
System.out.println("执行结果:" + result);
}
}
}
控制台输出:
java.io.BufferedInputStream@2f4d3709
16:48:05:688 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger 143 - ==> Preparing: insert into t_flower values(default, ?, ?, ?)
16:48:05:716 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger 143 - ==> Parameters: 火龙果(String), 999.99(BigDecimal), 中美洲(String)
16:48:05:721 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger 143 - <== Updates: 1
16:48:05:721 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger 143 - ==> Preparing: insert into t_flower values(default, ?, ?, ?)
16:48:05:722 [main] DEBUG org.apache.ibatis.logging.jdbc.BaseJdbcLogger 143 - ==> Parameters: 葡萄(String), 1000(BigDecimal), 亚洲西部(String)
16:48:05:729 [main] ERROR com.mk.Application 49 - Exception:
org.apache.ibatis.exceptions.PersistenceException:
### Error updating database. Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Out of range value for column 'price' at row 1
### The error may exist in com/mk/mapper/FlowerMapper.java (best guess)
### The error may involve com.mk.mapper.FlowerMapper.insert-Inline
### The error occurred while setting parameters
### SQL: insert into t_flower values(default, ?, ?, ?)
### Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Out of range value for column 'price' at row 1
at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:184)
at com.mk.Application.main(Application.java:47)
执行结果:0
数据库中表的记录:
mysql> select * from t_flower;
+----+--------+-------+------------+
| id | name | price | production |
+----+--------+-------+------------+
| 1 | 矮牵牛 | 2.50 | 南美阿根廷 |
| 2 | 百日草 | 5.00 | 墨西哥 |
| 3 | 半枝莲 | 4.30 | 巴西 |
+----+--------+-------+------------+
3 rows in set (0.00 sec)
因为一个事务中的某些操作失败,导致事务回滚,所以最终不会影响到数据库的数据。