spring事务
事务有四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
两种实现方式
xml形式的实现方式
1.配置数据源:
事务管理器必须依赖数据源:精髓之处就是要做事务就必须先要统一数据库连接,连接数据库首先先有数据库的基本信息即jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/sunday?characterEncoding=utf8
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=root
数据源的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 导入外部properties文件 -->
<context:property-placeholder location="jdbc.properties" />
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</bean>
</beans>
2.配置事务管理器
<!-- 第一步: 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理器必须依赖数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
3.配置事务通知:
需要注意的是:事务的七大传播行为和四大隔离级别都是在这里面的配置的.
<!-- 第二步: 配置事务通知(不同于我们自己之前配置的前置、后置通知,这个是Spring帮我们封装好的,专门用来做事务管理的通知) -->
<!-- tx:advice封装了切面和通知相关的逻辑,不需要我们自己再去编写切面和通知的逻辑 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 只有触发了特定异常才回滚事务 -->
<tx:method name="*" rollback-for="Exception" />
<!-- 触发以下特定异常,不会回滚事务 -->
<tx:method name="*" no-rollback-for="NullPointerException" />
<!-- 配置只读事务,只能查询,不能修改 -->
<tx:method name="find*" read-only="true" />
<!-- 配置事务超时时间,超时后事务自动回滚,单位:秒,
仅当传播行为propagation设置成REQUIRED或者REQUIRES_NEW的时候有效 -->
<tx:method name="find*" timeout="500" />
<!-- 配置事务传播行为为不支持事务 -->
<tx:method name="*" propagation="NOT_SUPPORTED" />
</tx:attributes>
</tx:advice>
4.配置Aop
<!-- 第三步: 配置AOP -->
<aop:config>
<aop:pointcut id="allServiceMethod" expression="execution(* com.spring.aoptx..*.*(..))"/>
<!-- 这个advisor类似于我们手工配置的aop:aspect,它将切面、通知和切入点做了一个整合 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="allServiceMethod" />
</aop:config>
5.数据库操作对象
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
一个简单的例子
bean
@Setter
@Getter
public class SystemLog {
private Integer id;
private String operator;
private String action;
private Date createTime;
}
@Setter
@Getter
public class User {
private Integer id;
private String userName;
private Timestamp lastLoginTime;
}
dao层
public class SystemLogDaoImpl implements SystemLogDao {
private JdbcTemplate jdbcTemplate;
public int insertLog(SystemLog systemLog) {
String sql = "insert into system_log(action, operator, createtime) values(?,?,?)";
return jdbcTemplate.update(sql, new Object[]{systemLog.getAction(), systemLog.getOperator(), systemLog.getCreateTime()});
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public int updateUser(User user) {
String sql = "update user set last_login_time = ? where id = ?";
return jdbcTemplate.update(sql, new Object[]{user.getLastLoginTime(), user.getId()});
}
public User findUserById(Integer id) {
String sql = "delete from user where id = ?";
jdbcTemplate.update(sql, new Object[]{id});
return null;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}
测试
/**
* Spring声明式事务
*/
private static void testTransactionAOP() {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
LoginService loginService = ctx.getBean(LoginService.class);
User user = new User();
user.setId(1);
user.setUserName("旺仔");
loginService.login(user);
}
事务的七大传播行为和四大隔离级别:
七种传播行为
1. PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
2. PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
3. PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
4. PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
5. PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6. PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
7. PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
备注:常用的两个事务传播属性是1和4,即PROPAGATIONREQUIRED PROPAGATIONREQUIRES_NEW
五种事务隔离级别
隔离级别 | 说明 |
---|---|
ISOLATION_DEFAULT | 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. |
ISOLATIONREADUNCOMMITTED | 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。 |
ISOLATIONREADCOMMITTED | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 |
ISOLATIONREPEATABLEREAD | 这种事务隔离级别可以防止脏读、不可重复读。但是可能出现幻像读。 它保证了一个事务不能修改已经由另一个事务读取但还未提交的数据 |
ISOLATION_SERIALIZABLE | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。 |
关键词:
幻读(虚读)
事务1读取记录时事务2增加了记录并提交,事务1再次读取时可以看到事务2新增的记录;
通俗的说,幻读就是指在一个事务内读取了别的事务插入的数据,导致前后读取不一致(insert)
不可重复读取
事务1读取记录时,事务2更新了记录并提交,事务1再次读取时可以看到事务2修改后的记录;
在一个事务内读取表中的某一行数据,多次读取结果不同.一个事务读取到了另一个事务提交后的数据.
脏读
事务1更新了记录,但没有提交,事务2读取了更新后的行,然后事务T1回滚,现在T2读取无效。
通俗的说,脏读就是指一个事务读取了一个未提交事务的数据
注解方式的实现方式
1.首先是配置
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.lanou3g.spring")
@PropertySource("classpath:jdbc.properties")
public class MyConfiguration {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.user}")
private String user;
@Value("${jdbc.password}")
private String password;
/**
* 配置数据源
* @return
*/
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
/**
* 配置事务管理器
* @param dataSource
* @return
*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
2.bean
@Setter
@Getter
public class SystemLog {
private Integer id;
private String operator;
private String action;
private Date createTime;
}
@Setter
@Getter
public class User {
private Integer id;
private String userName;
private Timestamp lastLoginTime;
}
3.dao层
@Repository
public class SystemLogDaoImpl implements SystemLogDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int insertLog(SystemLog systemLog) {
String sql = "insert into system_log(action, operator, createtime) values(?,?,?)";
return jdbcTemplate.update(sql, new Object[]{systemLog.getAction(), systemLog.getOperator(), systemLog.getCreateTime()});
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int updateUser(User user) {
String sql = "update user set last_login_time = ? where id = ?";
return jdbcTemplate.update(sql, new Object[]{user.getLastLoginTime(), user.getId()});
}
public User findUserById(Integer id) {
String sql = "delete from user where id = ?";
jdbcTemplate.update(sql, new Object[]{id});
return null;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
}
4.service层
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private UserDao userDao;
@Autowired
private SystemLogDao systemLogDao;
// 凡是xml中支持的事务属性,在注解中都有对应的属性来实现,具体属性含义参见xml配置
@Transactional(
rollbackFor = Exception.class // 指定哪些异常可以触发事务回滚
//noRollbackFor = // 指定事务不回滚哪些异常
// isolation = // 指定事务隔离级别
// timeout = // 指定事务超时时间
// propagation = // 指定事务传播行为
// readOnly = // 指定只读事务
)
public void login(User user) {
// Service中只写业务操作代码,不需要关注事务管理
// 1 更新用户表用户最后登录时间
user.setLastLoginTime(new Timestamp(System.currentTimeMillis()));
userDao.updateUser(user);
int ret = 9 / 0; // 模拟操作异常
// 2 插入登录日志
SystemLog log = new SystemLog();
log.setAction("login");
log.setOperator(user.getUserName());
log.setCreateTime(new Date());
systemLogDao.insertLog(log);
}
}
4.测试
/**
* Spring声明式事务
*/
private static void testTransactionAOP(ApplicationContext ctx) {
LoginService loginService = ctx.getBean(LoginService.class);
User user = new User();
user.setId(1);
user.setUserName("六火");
loginService.login(user);
}