第三部分:事务控制
一、场景
如果不进行事务控制,在进行批量插入数据时,如果部分数据有错误,插入过程中,可能会把部分正确的数据插入到数据库。这样做出现的问题就是可能会破坏数据的完整性。
我们想要当插入的数据都正确时,我们才会全部插入,如果数据中有错误的话,就全部回滚,都不插入。即使数据有错误,也不会对数据库造成影响。
1. 测试
模拟一下如果不做事务控制,可能出现的问题。
开始时数据中的数据是这样的:
如果不做事务控制,直接插入数据:
StudentService
// 模拟批量插入
int insertBatch();
StudentServiceImpl
向输入库中插入id为9-11的学生信息
@Override
public int insertBatch() {
int count = 0;
for (int i=9; i<12; i++) {
Student student = new Student();
student.setId(i);
this.studentMapper.insertSelective(student);
count++;
}
return count;
}
StudentTest
@Test
public void insertBatch() {
// 获取bean组件
StudentService studentService = (StudentService) ac.getBean("studentServiceImpl");
// 插入数据
studentService.insertBatch();
}
批量添加数据后,会报异常主键11冲突,但数据库的数据是这样的:
说明虽然有部分数据是错误的,但是仍可能会插入部分正确的数据。这样就会对数据库造成影响,破坏数据的完整性。
二、事务概述
事务控制是通过AOP实现的,通过前置通知、后置通知将一组操作结合在一起构成一个事务,通过异常通知,来检测事务执行过程中的错误。
1. 什么是数据的事务
事务是一组操作的执行单元。相对于数据库单条SQL语句的操作,事务管理是将一组SQL指令看做一个整体。
2. 事务的四大特性
- 原子性(atomic):事务里的操作,要么都执行,要么都不执行
- 一致性(consistent):如果事务有错误,数据库中原有的数据不会被破坏
- 隔离性(isolate):事务和事务之间不会相互混淆,有隔离级别
- 持久性(durable):事务一旦执行成功,就会将数据永久的保存到数据库或硬盘中
3. spring提供的两种事务管理
3.1 编程事务管理
程序员自己写程序来控制事务的开始和结束。优点是可以将事务管理的更精细。缺点在于不利于团队开发,会引起事务管理混乱。
3.2 声明事务管理(常用)
使用spring事务管理器,调用的是第三方组件来完成事务控制。我们需要在spring配置文件中做一些配置,就可以将数据库的访问纳入到事务管理中,解除了和代码的耦合,这是对应用程序影响最小的选择。当不需要事务管理时,直接从配置文件中移出该配置即可。
事务管理器的实现:
org.springframework.jdbc.datasource.DataSourceTransactionManager
在单一的JDBC Datasource中的管理事务
三、事务控制相关概念
一般是在service层设置事务控制的。这里需要理解三个概念,传播策略、隔离级别、只读事务。
1. 传播策略
传播策略研究的是怎么合并事务的问题。
这里重点介绍 REQUIRED
:
该策略的作用是在调用其他方法时,会取消掉其它方法的事务,只使用本方法的事务。这样的话,如果事务中出现错误,就全部回滚。
2. 隔离级别
一般选取的是 DEFAULT
通过设置不同的隔离级别可能引发的问题有以下三种:
2.1 脏读
脏读就是一个事务读到了另一个事务修改但并未提交的数据。可能引发的后果是,如果该数据被回滚,则读到的数据是无效的。
2.2 幻读
幻读是一个事务读取到了另一个事务新增的记录,导致读到的记录数不同。
2.3 不可重复读
不可重复读是一个事务多次读同一个数据,但每次读到的内容不同。
3. 只读与读写
只读和读写与数据库或数据库驱动程序相关,并不是一个强制选项。如果一个事务声明为只读,那么数据库或驱动程序就会对这个事务进行一定的优化。比如说不安排相应的数据库锁,不记录回滚日志等,可以减轻事务对数据库的压力,毕竟事务也是需要消耗数据库资源的。
3.1 使用场景
只读事务:单纯的数据库查询
读写事务:对数据库进行修改的操作
四、事务控制的实现
1. 引入事务控制的标签
2. 配置事务管理器
<!--配置事务管理器-->
<!--将事务管理器转为spring容器的组件-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--将访问这个数据源的数据库纳入事务控制-->
<property name="dataSource" ref="dataSource"></property>
</bean>
3. 启动事务控制的注解驱动
使用注解来实现管理事务的话,需要配置该驱动:
<!--启动事务控制的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
4. 使用注解完成事务控制
含义/作用 | 推荐 | |
---|---|---|
@Transactional | 事务控制的注解 | |
propagation | 设置传播策略 | REQUIRED |
isolation | 设置隔离级别 | 数据库默认级别 |
readOnly | 设置读写 | 如果只是查询函数,使用只读,效率更高 |
StudentServiceImpl
service的实现类里设置事务。
@Override
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
public int insertBatch() {
int count = 0;
for (int i=9; i<12; i++) {
Student student = new Student();
student.setId(i);
this.studentMapper.insertSelective(student);
count++;
}
return count;
}
这样,如果插入的数据存在主键冲突。就不会将任何一条数据插入到数据库中,而是全部回滚。
第四部分:定时器
一、定时任务
按照指定的时间周期运行指定的任务。
二、定时器配置
1. 引入定时器的标签
引入命名空间和xsd文件
2. 引入定时任务管理器,启动注解驱动
<task:scheduler id="qbScheduler" pool-size="5" />
<!--启动注解驱动,设置定时任务的线程池为5-->
<task:annotation-driven scheduler="qbScheduler" mode="proxy"/>
3. MyTask.java
模拟定时器任务,spring框架启动后,每1s执行一次任务
package com.tentact.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyTask {
@Scheduled(fixedRate = 1000)
public void showMsg() {
System.out.println("好好学习,天天向上");
}
}
4. 测试
启动spring框架,用来测试定时器任务
@Test
public void testTask() {
try {
// 休眠500000s
Thread.sleep(500000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
三、cron时间表达式
功能比较强大,可用来指定具体的时间和周期。
一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素
cron时间表达式设置时间的次序为:秒 分 时 日 月 周几 年,注意这里日和周几是互斥的,只能设置一个。
1. 相关符号
符号 | 含义 |
---|---|
? | 来表示不设置 |
, | 表示并集,并列的时间 |
0/x | 每隔x的时间触发 |
* | 表示任意 |
2. 相关示例
表达式 | 含义 |
---|---|
“0 0 10,14,16 * * ?” | 每天上午10点,下午2点,4点 |
“0 0/30 9-17 * * ?” | 朝九晚五工作时间内每半小时 |
“0 0 12 ? * WED” | 表示每个星期三中午12点 |
“0 0 12 * * ?” | 每天中午12点触发 |
“0 15 10 ? * *” | 每天上午10:15触发 |
“0 15 10 * * ?” | 每天上午10:15触发 |
“0 15 10 * * ? 2005” | 2005年的每天上午10:15触发 |
0 0/5 14 * * ?” | 在每天下午2点到下午2:55期间的每5分钟触发 |
3. 相关代码
MyTask.java
package com.tentact.scheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MyTask {
// 每一秒执行一次
@Scheduled(cron = "0/1 * * * * ?")
public void showMsg() {
System.out.println("好好学习,天天向上");
}
}