1.什么是事务?
事务是逻辑上的一组操作,要么全部执行,要么都不执行。
2.事务的特性(ACID)
- 原子性
事务是最小的执行单位,事务是一个原子操作。事务的原子性保证要么全部执行,要么都不执行 - 一致性
执行事务的前后,数据保持一致性 - 隔离性
并发访问数据库时,一个用户的事务不被其他事务干扰,各并发事务之间数据库是独立的。 - 持久性
一个事务被提交之后,它对数据库中的数据的改变的持久的。无论发生什么系统错误,它的结果都不应该受到影响。
3.Spring事务管理接口
- latformTransactionManager: (平台)事务管理器
- TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
- TransactionStatus: 事务运行状态
4.TrancactionDefinition:事务定义信息
事务定义信息有:
- 隔离级别
- 传播行为
- 超时行为
- 是否只读
(1)如果不考虑隔离性引发的安全性问题
隔离级别:定义了一个事务可能受其他并发事务影响的程度。
并发事务引起的问题:多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发是必须的,但是会导致以下的问题:
- 脏读:当一个事务正在访问数据并对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据就是“脏数据”,根据“脏数据”所做的操作可能是不正确的。
- 丢失修改:当一个事务读取一个数据时,另外一个事务也访问了该数据,那么第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结构就会被丢失,因此称为丢失修改。
- 不可重复读:在一个事务内多次读同一个数据,在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- 幻读:一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就发现多了一些原本不存在的记录,就好像发生了幻觉一样,称为幻读。
不可重复读和幻读的区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
5.例子(没有使用事务)
模拟转账
(1)搭建数据库
DROP TABLE IF EXISTS `salary`;
CREATE TABLE `salary` (
`id` int(20) NOT NULL DEFAULT '0',
`name` varchar(20) DEFAULT NULL,
`money` double(20,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `salary` VALUES ('1', 'aa', '1000');
INSERT INTO `salary` VALUES ('2', 'bb', '1000');
(2)导入依赖包
spring-beans
spring-context
spring-jdbc
mysql-connector-java
(3)配置xml文件(开启注解,配置数据库连接池以及jdbcTemplate)
db.properties
jdbc.driver = com.mysql.jdbc.Driver
#MySQL jdbc 6.0 版本以上必须配置“serverTimezone”参数, UTC代表的是全球标准时间
jdbc.url = jdbc:mysql:///jdbcTest?serverTimezone=UTC
jdbc.username = root
jdbc.password = msj
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!--引入配置文件-->
<context:property-placeholder location="classpath:db.properties"/>
<!--加载数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userDao" class="com.msj.demo01.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="userService" class="com.msj.demo01.UserService">
<property name="userDao" ref="userDao"/>
</bean>
</beans>
(4)具体实现(接口及实现类)
Account.java
public class Account {
private String username;
private Double money;
}
UserDao.java
public class UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void lessen(int id, double m){
String sql = "update account set money = money - ? where id = ? ";
jdbcTemplate.update(sql,m,id);
}
public void add(int id,double m){
String sql = "update account set money = money + ? where id = ?";
jdbcTemplate.update(sql,m,id);
}
}
UserService.java
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void account(int lessenId, int addId, double m){
userDao.add(addId,m);
userDao.lessen(lessenId,m);
}
}
测试类TestDemo.java
public class TestDemo {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.account(1,2,500);
}
}
运行结果:
(5)如果转账的过程中出现了一些异常,比如服务器宕机,银行断电等,那就会出现一个问题,就是一个账号转钱了,另一个账号没有收到钱。
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void account(int lessenId, int addId, double m){
userDao.add(addId,m);
int a = 1/0; //错误
userDao.lessen(lessenId,m);
}
}
6.Spring使用事务
- 编程式事务管理(了解)
- 声明式事务管理
- 基于xml配置文件方式
- 基于注解的方式
1. 声明式事务管理---xml方式:思想就是AOP。
添加依赖包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>
在spring.xml中添加事务
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="account"/>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.msj.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
运行结果:
运行时出现异常,查看数据库中的数据,并没有减少,事务被回滚了。
Exception in thread "main" java.lang.ArithmeticException: / by zero
2.Spring声明式事务--注解方式
AppConfig.java
@Configuration //定义配置文件
@ComponentScan("com.msj.demo02") //扫包
@EnableTransactionManagement //事务管理
@PropertySource("classpath:db.properties") //引入配置文件
public class AppConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//设置数据源
@Bean
public DriverManagerDataSource dataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean
public DataSourceTransactionManager transactionManager(DriverManagerDataSource ds){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(ds);
return transactionManager;
}
@Bean
public JdbcTemplate jdbcTemplate(DriverManagerDataSource ds){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(ds);
return jdbcTemplate;
}
}
UserDao.java
@Repository("dao")
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void lessen(int id, double m){
String sql = "update account set money = money - ? where id = ? ";
jdbcTemplate.update(sql,m,id);
}
public void add(int id,double m){
String sql = "update account set money = money + ? where id = ?";
jdbcTemplate.update(sql,m,id);
}
}
UserService.java
@Service("service")
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void account(int lessenId, int addId, double m){
userDao.add(addId,m);
int a = 1/0;
userDao.lessen(lessenId,m);
}
}
运行结果,发生异常 ,数据库的数据没有发生改变。这就是事务,要么全部做,要么全部不做;当发生异常,事务回滚,全部不做。