数据库事务包含:acid。
atomic 原子性:最小业务单元,要么全部成功,要么全部失败
consistency 一致性:事务完成时,所有的数据都保持一致
iso lation 隔离性: 多个线程同时访问同一数据,产生丢失更新
dura bility 持久性:事务结束可断电
第一类丢失更新:
一个事务回滚,另外一个事务提交,引发不一致。
100,a -1=99,b-1=99,b提交=99,a回滚=100
第二类丢失更新:
两个事务都提交,引发的不一致。
100,a-1=99,b-1=99并提交。a提交=99
事务a无法感知事务b的提交
隔离级别: 四种。
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1), //未提交读
READ_COMMITTED(2),//读写提交 oracle
REPEATABLE_READ(4),//可重复读 mysql
SERIALIZABLE(8); //串行化
}
完全避免丢失更新,就是付出锁的代价(性能不好)
1,未提交读:准许一个事务读取 另外一个事务,没有提交的数据
出现脏读:库存2,a-1=1。b-1=0(b读a未提交的结果),b提交,库存为0。a回滚=0(回滚到b提交时)
2,读写提交:是 一个事务 只能读取 另外一个 事务已经提交的数据。
克服脏读:库存2,a-1=1。b读取2,-1=1 提交事务。a回滚为1(回滚到b提交时)
出现不可重复读:库存1,a-1=0(未提交)。b认为库存为1。a提交,变成0。 b此时无法扣减库存。
3,可重复读(mysql):
克服不可重复读:等待事务提交
a读取库存,扣减。b 无法读取,等待a提交。
出现幻读:库存5,交易记录5。b查询记录为5,a库存-1,提交,交易记录为6。b打印交易记录为6(于查询的不一致)
记录数据库的值,是一个统计的值。
4,串行化。SQL 都会按照 顺序执行
未提交读:出现 脏 不可重复 幻 读
读写提交:出现 不可重复 幻 读
可重复读:出现 幻读
串行化:都不会出现。
更高的隔离级别,保证了数据的一致性,但也要付出锁的代价。
-
掌握数据库事务机制 至关重要
-
保证数据的一致性
-
有效提高系统性能,避免系统产生宕机
-
数据库事务 通过 Aop 技术来提供服务
-
数据库隔离级别
-
数据库事务传播行为:一个批处理,在一些交易中发生了异常,不能所有交易都回滚。
spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver #指定数据库连接池的类型 (配置dbcp2数据源) #spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource #最大等待连接中的数量,设 0 为没有限制 spring.datasource.dbcp2.max-idle=10 #最大连活动接数 spring.datasource.dbcp2.max-total=50 #最大等待毫秒数, 单位为 ms, 超过时间会出错误信息 spring.datasource.dbcp2.max-wait-millis=10000 #数据库连接池初始化连接数 spring.datasource.dbcp2.initial-size=5 #日志配置 logging.level.root=DEBUG logging.level.org.springframework=DEBUG logging.level.org.org.mybatis=DEBUG
-
数据库事务
- 编程式事务:淘汰
- 声明式事务
jdbc的数据库事务
Connection conn = null;
int result = 0;
try {
//先注册驱动
Class.forName("com.mysql.jdbc.Driver");
//在通过驱动管理器,获取数据事务连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
//非自动提交事务。开启事务
conn.setAutoCommit(false);
//设置事务的隔离级别 READ_COMMITTED(2),//read_commited 读_提交 conn.setTransactionIsolation(TransactionIsolationLevel.READ_COMMITTED.getLevel());
//执行层
PreparedStatement ps = null;
try {
//预设sql
ps = conn.prepareStatement("insert into user(user_name,note) values (?,?)");
//参数指定
ps.setString(1, "张三");
ps.setString(2, "study");
//执行
result = ps.executeUpdate();
} finally {
//怎样都关闭连接
ps.close();
}
//提交事务
conn.commit();
} catch (Exception e) {
try {
//回滚事务
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
//释放连接池
if (conn != null && !conn.isClosed() ) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
if (result == 0) {
} else {
}
//另外一种 获得 dataSource的方法
@Autowired
private DataSource dataSource=null;
public int insertUser(){
Connection conn = dataSource.getConnection();
}
public enum TransactionIsolationLevel {
NONE(0), //none
READ_COMMITTED(2),//read_commited 读_提交
READ_UNCOMMITTED(1),//read_uncommitted 读_不提交
REPEATABLE_READ(4),//repeatable_read 可重复的_读
SERIALIZABLE(8);//serializable 序列化的
private final int level;//定义最终的 int 变量
private TransactionIsolationLevel(int level) {
this.level = level;//构造对level赋值
}
public int getLevel() {
return this.level;//提供返回 level
}
}
- 开启事务——执行SQL——发生异常——回滚事务
- 不发生异常——提交事务
- ——释放事务资源
Spring 声明式 事务的使用
-
@Transactional
-
类上 或 方法上,
-
类上代表这个类 所有公共 非静态 的方法 都启用事务功能
-
在此注解上,还可以 配置 事务的隔离级别 和 传播行为 异常类型(发生什么异常回滚 或 不回滚)
-
配置的内容 Spring Ioc 容器 在加载 时 就会 将这些配置信息 解析出来,
-
然后把这些信息 存到事务 定义器 TransactionDefinition 接口 的实现类 里
-
并且 记录 那些类 或者 方法需要启动 事务功能
-
采取什么策略 去 执行 事务。
-
我们 只需要 配置属性即可
-
Spring Ioc启动时,解析 和 保存事务配置
-
开启 和 设置 事务
-
执行方法逻辑 (开发者提供)
-
发生异常 且 满足回滚配置 ——回滚事务——释放资源
-
没异常——提交事务——释放资源
-
Spring数据库 事务 拦截器 通过数据库 事务管理器 实现
-
转播行为:属于 事务方法之间调用的行为
-
隔离级别
-
超时时间
-
只读
@Autowired
private UserDao userDao = null;
@Override
@Transactional
public int insertUser(User user) {
return userDao.insertUser(user);
}
@Transactional的配置项
- 放在接口上 将使得 你的类 基于接口的代理 时 它 才会生效。
@Target({ElementType.METHOD, ElementType.TYPE}) //方法 和 类上(接口 也可以)
@Retention(RetentionPolicy.RUNTIME) //运行时
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")//bean name指定 事务管理器
String value() default "";
@AliasFor("value")//同 value 属性。都是配置一个Spring的事务管理器
String transactionManager() default "";
//指定 传播行为
Propagation propagation() default Propagation.REQUIRED;
//指定 隔离级别
Isolation isolation() default Isolation.DEFAULT;
//指定超时 时间 单位秒。事务可以允许存在的 时间戳。单位为秒
int timeout() default -1;
//是否只读事务。默认不是 只读事务。
boolean readOnly() default false;
//发生指定异常回滚,默认所有异常都回滚
Class<? extends Throwable>[] rollbackFor() default {};
//方法发生在 指定异常 名字回滚
String[] rollbackForClassName() default {};
//发生指定异常不回滚,默认所有异常都回滚
Class<? extends Throwable>[] noRollbackFor() default {};
//方法发生在 指定异常 名字不回滚
String[] noRollbackForClassName() default {};
}
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, //类上
/** Field declaration (includes enum constants) */
FIELD, //字段上
/** Method declaration */
METHOD,//方法上
/** Formal parameter declaration */
PARAMETER,//参数上
/** Constructor declaration */
CONSTRUCTOR,//构造上
/** Local variable declaration */
LOCAL_VARIABLE,//本地变量上
/** Annotation type declaration */
ANNOTATION_TYPE,//注解 类型上
/** Package declaration */
PACKAGE,//包上
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,//类型 参数上
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE//类型 使用上
}
public enum RetentionPolicy {
/**
* Annotations are to be discarded丢弃的;废弃的 by the compiler.
*/
SOURCE, //源码 在编译的时候 丢弃
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,//类上
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME //运行时候
}
Spring的事务管理器
顶层接口为,Platform Transaction Manager
子类:DataSource Transaction Manager。mybatis依赖后,会自动创建
public interface PlatformTransactionManager {
//获取事务,设置数据 属性
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
- TransactionDefinition 是一个事务定义器
- 依赖我们配置的 @Transactional 的配置项生成
测试事务管理器
CREATE TABLE `t_user` (
`id` int(12) NOT NULL AUTO_INCREMENT,
`user_name` varchar(60) NOT NULL,
`sex` int(3) NOT NULL DEFAULT '1',
`note` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
@Alias(value = "user")//指定别名
@Data
public class User {
private Long id;
private String userName;
private String note;
}
dao
@Repository
public interface UserDao {
User getUser(Long id);
int insertUser(User user);
}
userMap.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.springboot.chapter6.dao.UserDao">
<select id="getUser" parameterType="long" resultType="user">
select id, user_name as userName, note from t_user where id = #{id}
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user(user_name, note) value(#{userName}, #{note})
</insert>
</mapper>
- useGeneratedKeys=“true” keyProperty=“id” 插入之后,使用数据库生成机制 回填对象的主键
service
public interface UserService {
// 获取用户信息
public User getUser(Long id);
// 新增用户
public int insertUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao = null;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public User getUser(Long id) {
return userDao.getUser(id);
}
}
- Isolation.READ_COMMITTED 读写 的隔离级别
- 超时时间,设置为了1秒
action
@Controller
@RequestMapping("/user")
public class UserController {
// 注入Service
@Autowired
private UserService userService = null;
@Autowired
private DataSource dataSource=null;
@Transactional
public int insertUser(){
Connection conn = dataSource.getConnection();
}
// 测试获取用户
@RequestMapping("/getUser")
@ResponseBody
public User getUser(Long id) {
return userService.getUser(id);
}
// 测试插入用户
@RequestMapping("/insertUser")
@ResponseBody
public Map<String, Object> insertUser(String userName, String note) {
User user = new User();
user.setUserName(userName);
user.setNote(note);
// 结果会回填主键,返回插入条数
int update = userService.insertUser(user);
Map<String, Object> result = new HashMap<>();
result.put("success", update == 1);
result.put("user", user);
return result;
}
}
MyBatis 配置
mybatis.mapper-locations=classpath:com/springboot/chapter6/mapper/*.xml
mybatis.type-aliases-package=com.springboot.chapter6.pojo
#日志配置
logging.level.root=DEBUG
logging.level.org.springframework=DEBUG
logging.level.org.org.mybatis=DEBUG
启动类配置
@MapperScan(
basePackages = "com.springboot.chapter6",
annotationClass = Repository.class)
@SpringBootApplication(scanBasePackages = "com.springboot.chapter6")
public class Chapter6Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Chapter6Application.class, args);
}
// 注入事务管理器,它由Spring Boot自动生成
@Autowired
PlatformTransactionManager transactionManager = null;
// 使用后初始化方法,观察自动生成的事务管理器
@PostConstruct
public void viewTransactionManager() {
// 启动前加入断点观测
System.out.println(transactionManager.getClass().getName());
}
}
测试
http://localhost:8080/mybatis/insertUser?userName=李四¬e=22222
{
"success": true,
"user": {
"id": 2,
"userName": "李四",
"note": "22222",
"sex": null
}
}
隔离级别
-
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
-
商品库存,机会出现多个事务 同时访问 统一记录 的 情况 (丢失更新)
数据库事务的知识
- acid
- atomic 原子性 : 最小业务单元,要么全部成功,要么全部失败
- consistency 一致性:事务完成时,所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性
- iso lation 隔离性: 多个线程同时访问同一数据,同样的数据 就会在各个不同的事务中 被访问,会产生丢失更新。 为了压制丢失更新,定义了隔离级别。选择不同程度上压制丢失更新的发生。
- dura bility 持久性:事务结束,数据固化到一个地方,断电也没事
第一类丢失更新
-
一个事务回滚 另外一个事务提交 而引发的数据不一致 的情况
- 库存100,
- 事务1,减去库存,剩余99
- 事务2,减去库存,剩余99,提交事务剩余99个。(事务2,的结果 丢失了)
- 事务1,回滚事务,剩余100 个。
- 大部分数据库已经克服了此种问题,不在讨论
第二类丢失更新
-
多个事务同时提交
- 库存100,
- 事务1,减去库存,剩余99
- 事务2,减去库存,剩余99,提交事务剩余99个。(事务2,的结果 丢失了)
- 事务1,提交事务,剩余99 个。
-
因为事务1,无法感知事务2的操作,事务1提交,引发事务2提交结果的丢失,
- 为了解决这样的问题,提出了事务之间的隔离级别
隔离级别详情
- 4类隔离级别
- 未提交读
- 读写提交
- 可重复读
- 串行化
- 数据的一致性,数据的性能
- 完全避免丢失更新,就是付出锁的代价(系统的性能不好)
- 抢购,必然会导致大量的线程被挂机 和 恢复,整个系统就会十分缓慢,千万用户同时访问,就会宕机
- 用户体检十分糟糕,互联网的相应时间超过5秒,就会认为不友好。
- 既要考虑数据的一致性 避免脏数据,又要考虑系统性能 的问题
未提交读 (read uncommitted)
-
准许一个事务读取 另外一个事务,没有提交的数据
-
是危险的隔离级别
-
并发能力高
-
适应对 数据一致性 没有要求 ,而追求 高并发 的场景
-
会出现脏读
-
库存为2
-
事务1 : 读取库存为2,减去库存,库存为1
-
事务2: 扣减库存(读的是事务1,未提交的库存数据),库存为0。提交事务,库存为0
-
事务1:回滚事务,丢失更新已经克服,不会回滚为2,会回滚为 0
-
未提交读,事务2 可以读取事务1未提交的库存。减库存 提交 库存为0
-
事务1回滚,第一类丢失更新已经被克服,所以不会回滚为2,结果变成了0.
-
脏读 是比较危险的隔离级别。 为了克服脏读 还提供了 读写提交
-
读写提交(read committed)
读写提交 隔离级别,是 一个事务 只能读取 另外一个 事务已经提交的数据,不能读取 未提交的数据
克服脏读:
- 库存为2
- 事务1:读取库存2,扣减库存 库存为1
- 事务2:扣减库存,库存为1,(读取不到事务1 未提交的库存数据)
- 事务2:提交事务,库存保存为1
- 事务1:回滚事务,第一类丢失更新已经克服,所以不会回滚为2,会变成库存为1
事务2:扣减库存,库存为1,不能读取到事务1中未提交的库存1
不可重读场景,
-
商品初始化 为 1
-
事务1:读取 库存为 1,扣减库存,(未提交)
-
事务2:读取 库存为1,认为可扣减,库存为0
-
事务1:提交事务,库存变成0
-
事务2:扣减库存失败,因为此时 库存为0,无法扣减
库存对事务2 而言 是一个可变化 的值,称为 不可重复读,
就是 读写提交的一个不足。
为了克服这个不足,提出了 可重复读
可重复读
克服读写提交中 出现的不可重复读 的 现象
读写提交的时候,可能出现一些值的变化,
- 商品初始化为1
- 事务1:读取库存为1,扣减库存 (事务未提交)
- 事务2:尝试读取库存,不允许 读取,等待事务1 提交
- 事务1:提交事务,库存变成0
- 读取事务,库存为0,无法扣减
数据库会 阻塞 事务2 的读取,直到 事务1提交。
这样会引发新的问题:幻读
-
事务1:读取库存50件,(最早为100,已经销售了50件)
-
事务2:查询交易记录50笔
-
事务1:扣减库存,插入1笔交易记录,提交事务(库存49件,交易记录51笔)
-
事务2:打印交易记录 51笔,(与查询的不一致,事务2看来 有1笔是虚幻的,与之前查询的不一致)
-
交易记录50笔,不是数据库的值,而是一个统计的值,商品的库存才是真实的值。
-
幻读是:针对 数据库 的多条记录的。(这51笔交易数 ,就是多条数据库统计出来的)
-
可重复读,是针对数据库的单一记录的(商品的库存)。
串行化(serializable)
是:数据库最高级别的隔离级别
要求所有的 SQL 都会按照 顺序执行,就会 克服上述 隔离级别出现的各种问题。
使用 合适的隔离级别
项目类型 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读 | √ | √ | √ |
读写提交 | X | √ | √ |
可重复度 | X | X | √ |
串行化 | X | X | X |
更高的隔离级别,保证了数据的一致性,但也要付出锁的代价
在现实中 一般而言,选择隔离级别会 以读写提交为主。能够防止脏读。而 不能避免不可重读(扣减库存失败),和 幻读 (查询和打印交易记录不一致),
为了克服数据不一致问题, 还设计了 乐观锁。
甚至不再使用数据库 使用其他手段。(redis)
oracle 只能支持 读写提交 和 串行化 (默认读写提交,读取不到未提交的,库存可能无法扣减)
mysql 支持 4种,默认是可重复读 (事务未提交,不准许读取)
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
Isolation.READ_COMMITTED 读写提交,读取不到未提交的,库存可能无法扣减
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8); //序列化隔离级别,将阻塞其他的事务进行并发。用在低并发,又需保证数据一致的场景下,
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
#隔离级别数字配置的含义:
第一类丢失更新:第二个事务提交,第一个事务回滚,结果不包含第二个结果
第二类丢失更新:第一,第二事务 同时读取后,扣减库存,同时提交,结果不对
#-1 数据库默认隔离级别
#1 未提交读:允许一个事务读取另一个事务未提交的数据。
脏读:事务1扣减库存,库存为1,事务2扣减库存,库存为0,提交。事务1回滚,变成库存为0
#2 读写提交:只能读取到 另一个事务 已经提交 的数据 (未提交的,读取不到。oracle)
克服脏读:事务1读到结果2,扣减为1。事务2读到结果为2,扣减为1,提交后也为1。事务1回滚为1。
不可重复读:总共1个,A读取了,没提交,B读取了,A提交了变成0。B读取的是错误的。
#4 可重复读:一个事务只能读取,另一个事务提交过的数据(一个事务没提交,另一个等待 mysql)
#8 串行化:按照顺序执行SQL
#tomcat数据源默认隔离级别
spring.datasource.tomcat.default-transaction-isolation=2
#dbcp2数据库连接池默认隔离级别
#spring.datasource.dbcp2.default-transaction-isolation=2
传播行为(propagation = Propagation.REQUIRED)
方法之间调用事务采取的策略问题。
绝大部分情况,要么全部成功,要么全部是失败。
在一个批量任务执行过程中,调用多个交易时,如果有一些交易发生了异常,只回滚那些出现异常的数据。
当一个方法调用另一个方法时,可以让事务 采取不同的策略工作,如:新建事务,挂起当前事务。
这便是 事务的传播行为。
-
批量任务 ——》 单个交易 独立事务
-
当前方法,调用子方法的时候。
-
让每一个子方法 不在 当前事务中执行,
-
而是创建一个新的 事务去之行子方法。
-
即:当前方法调用 子方法的传播行为 为新建事务。还可以让子方法在 无事务,独立事务中执行。
传播行为的定义
7种传播行为,通过枚举 propagation定义的
public enum Propagation {
REQUIRED(0),//需要事务。如果当前事务存在 就沿用当前事务,否则就新建一个事务。 常用
SUPPORTS(1),//支持事务。如果当前事务存在 就沿用当前事务,如果不存在,采用无事务
MANDATORY(2),//必须用事务,如果没,就抛异常。如果存在事务,就沿用当前事务。
REQUIRES_NEW(3),//无论当前事务是否存在,都创建新的事务。新事务有新的锁 和 隔离级别,与当前事务独立。 常用
NOT_SUPPORTED(4),//不支持事务,当前存在事务时,将挂起事务
NEVER(5),//不支持事务,如果存在事务,则抛异常,否则继续使用无事务机制 运行
NESTED(6);//调用子方法,如果子方法发生异常,只回滚子方法执行过的sql,不回滚当前方法的事务。
常用
required 当前存在就沿用,否则就新建 常用
supports 当前存在就沿用,否则就 无事务
mandatory 没事务就抛异常,存在就沿用
requires_new 无论与否,都创建新事务 常用
not_supporited 不支持事务,如果存在,将挂起
nerver 不支持事务,如果存在,抛异常
nested 子方法发生异常,只回滚子方法 常用
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
//下载了源码,如此表示
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
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_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
@Nullable
String getName();
}
测试传播行为
- required 当前存在就沿用,否则就新建 常用
- requires_new 无论与否,都创建新事务 常用
- nested 子方法发生异常,只回滚子方法 常用
@Resource
private UserBatchService userBatchService;
// 测试插入用户
@RequestMapping("/insertUserList")
@ResponseBody
public Map<String, Object> insertUserList(String userName, String note) {
User user = new User();
user.setUserName(userName);
user.setNote(note);
User user1=new User();
user1.setUserName("我的名字2");
user1.setNote("哈哈哈,备注一下");
List<User> userList = new ArrayList<>();
userList.add(user);
userList.add(user1);
// 结果会回填主键,返回插入条数
int update = userBatchService.insertUser(userList);
Map<String, Object> result = new HashMap<>();
result.put("success", update > 0);
result.put("user", user);
return result;
}
public interface UserBatchService {
public int insertUser(List<User> users);
}
@Service
public class UserBatchServiceImpl implements UserBatchService {
@Autowired
private UserService userService = null;
//读 提交事务oracle,只能读取到提交的。
// 如果当前事务存在 就沿用当前事务
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public int insertUser(List<User> users) {
int c=0;
for(User u:users){
c+=userService.insertUser(u);
}
return c;
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
http://localhost:8080/mybatis/insertUserList?userName=李四哈哈2¬e=22222啦啦啦
REQUIRED (主方法上)
-
当前存在就沿用,否则就新建 常用
-
insertUser (单个) 中没有定义传播行为。沿用当前的事务。
-
Participating in existing transaction 看到的日志是: 沿用已经存在的 当前事务
REQUIRES_NEW (子方法)
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW,timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
-
子方法改成了:requires_new 无论与否,都创建新事务 常用
-
Creating a new SqlSession 创建当前方法事务
-
设置当前方法事务隔离级别为 读写提交
Acquired Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] for JDBC transaction Changing isolation level of JDBC Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] to 2 Switching JDBC Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] to manual commit
-
创建子方法 事务
Suspending current transaction, creating new transaction with name [com.example.demobenaware.service.UserServiceImpl.insertUser]
-
设置子方法的隔离级别
Changing isolation level of JDBC Connection [HikariProxyConnection@1427116246 wrapping com.mysql.jdbc.JDBC4Connection@5c4db693] to 2
启用了新的数据库事务 去运行每一个insertUser 方法,并且独立提交
这样就完全脱离了原有事务的管控,每一个事务都可以拥有自己独立的隔离级别和锁
NESTED (子方法上)
nested 子方法发生异常,只回滚子方法 常用
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.NESTED,timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
Creating nested transaction with name [com.example.demobenaware.service.UserServiceImpl.insertUser]
Releasing transaction savepoint
- 一段sql语句设置一个标识位
- 后面的代码如果有异常,只是回滚到这个标识位 的 数据状态
- 标识位,在数据库成为保存点。spring也是使用此方法。子事务回滚,而不回滚主事务。
- spring: 当数据库支持保存点技术,就启动。如果不支持,就新建一个事务 去运行你的代码 =REQUIRES_NEW
@Transactional 自调失效问题
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW,timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
//读 提交事务oracle,只能读取到提交的。
// 如果当前事务存在 就沿用当前事务
//@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public int insertUser(List<User> users) {
int c=0;
for(User u:users){
c+=insertUser(u);
}
return c;
}
自调用,并没有创建任何新的事务 独立运行insertUser方法
我们的子方法 注解 ,事务失效了。
aop的原型是动态代理,在自调用的过程中,是类自身的调用,而不是代理对象去调用。那么就不会产生aop。(Spring就不能把你的代码织入到约定的流程中)
我们可以在抽一层
也可以从 ioc容器中获取 代理对象 去启动 Aop
@Service //集成上下文对象 aware
public class UserServiceImpl implements UserService, ApplicationContextAware {
@Autowired
private UserDao userDao = null;
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW,timeout = 1)
public int insertUser(User user) {
return userDao.insertUser(user);
}
//读 提交事务oracle,只能读取到提交的。
// 如果当前事务存在 就沿用当前事务
//@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public int insertUser(List<User> users) {
int c=0;
//从 IOC 容器中获取代理对象
UserService bean = applicationContext.getBean(UserService.class);
for(User u:users){
//使用代理对象 调用方法插入用户,此时会织入 Spring 数据库事务流程中
c+=bean.insertUser(u);
}
return c;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, timeout = 1)
public User getUser(Long id) {
return userDao.getUser(id);
}
private ApplicationContext applicationContext=null;
@Override //用声明周期的方法,设置ioc容器
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
- bean = applicationContext.getBean(UserService.class); 获取到的是代理对象。
- 它能够克服自调用的问题
- 这样代码要依赖于 Spring的api ,造成了代码的侵入。在抽取一层更好。