声明式事务
注解配置方式
大致分三步
XML中开启:
<!-- 开启事务注解扫描 -->
<!-- 如果定义的事务管理器名称就叫transactionManager,则此属性可以省略 -->
<tx:annotation-driven transaction-manager="txManager" />
注解方式开启:
@Configuration
@EnableTransactionManagement
public class MyTransactionConf {
}
/**
* 通过注解的方式配置Spring声明式事务
* 也可以将事务管理器定义到xml中,注解事务会按类型自动找到xml中配置的事务管理器
*/
@Configuration
public class MyTransactionConf {
/**
* 定义TransactionManager bean
* @param dataSource
* @return
*/
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 给批量插入方法添加事务控制
@Transactional
@Override
public int[] batchInsertStudent(List<Student> studentList) {
// 具体操作....
}
// 给查询方法添加只读事务
@Transactional(readOnly = true)
public List<Student> queryStudents() {
// 具体操作
}
@Transactional属性说明
value: 指定特定的事务管理器,默认是transactionManager
其他属性和xml中的的属性类似
具体事例
package com.lanou.spring.transaction.annotation.bean;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Teacher {
private Integer id;
private String tname;
public Teacher(){}
public Teacher(Integer id,String tname){
this.id=id;
this.tname=tname;
}
public Teacher (String tname){
this.tname=tname;
}
}
package com.lanou.spring.transaction.annotation.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Component;
import java.util.Properties;
//配置数据源
@PropertySource("classpath:jdbc.properties")
@Component
public class MyDataSource extends DriverManagerDataSource {
public MyDataSource(@Value("${jdbc.driver}") String driver,
@Value("${jdbc.url}") String url,
@Value("${jdbc.user}") String userName,
@Value("${jdbc.password}") String password,
@Value("${jdbc.characterEncoding}") String characterEncoding){
//MyDataSource的最终父类是DataSource
super.setDriverClassName(driver);
super.setUrl(url);
super.setUsername(userName);
super.setPassword(password);
Properties properties=new Properties();
properties.setProperty("characterEncoding",characterEncoding);
super.setConnectionProperties(properties);
}
}
package com.lanou.spring.transaction.annotation.dao;
import com.lanou.spring.transaction.annotation.bean.Teacher;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Setter
@Getter
//此注解和@Component作用一样,只是含有特定的语义(一般用来标注dao层的类)
//@Repository
@Component
public class TeacherDaoImpl {
@Autowired
private JdbcTemplate jdbcTemplate;
//查询所有教师表的数据
//通过@Transactional开启事务,设置事务属性
@Transactional(readOnly = true)
public List<Teacher> queryAll(){
List<Teacher> teachers=jdbcTemplate.query("select * from teacher",
new BeanPropertyRowMapper<Teacher>(Teacher.class));
return teachers;
}
//插入教师,返回影响了多少行
@Transactional(rollbackFor =ArithmeticException.class )
public int insertTeacher(Teacher teacher){
/*
* 通过伪代码演示我们手写事务控制代码的套路
*
* */
//setAutoCommit(false);
// beginTransaction
// try{
int result = jdbcTemplate.update("insert into teacher (tname) values (?)", teacher.getTname());
// int retVal = 9 / 0;
//commit;
//} catch (Exception e) {
// rollback;
return result;
}
}
- 开启事务注解支持,定义JdbcTemplate对象,定义事务管理器
package com.lanou.spring;
import com.lanou.spring.transaction.annotation.bean.Teacher;
import com.lanou.spring.transaction.annotation.dao.TeacherDaoImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.List;
@Slf4j
@Configuration
@ComponentScan(basePackages ="com.lanou.spring.transaction.annotation")
//开启事务相关注解支持
@EnableTransactionManagement
public class AppByTransactionAnnotation
{
//定义JdbcTemplate对象,Spring给我们封装了所有的JDBC操作,这里的dataSoource是MyDataSource类中的MyDatasource
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
//定义事务管理器
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
public static void main(String[] args) {
ApplicationContext ctx=new AnnotationConfigApplicationContext(AppByTransactionAnnotation.class);
//testTransaction(ctx);
testQuery(ctx);
}
static void testQuery(ApplicationContext ctx){
TeacherDaoImpl teacherDao =ctx.getBean(TeacherDaoImpl.class);
List<Teacher> teacherList=teacherDao.queryAll();
for (Teacher teacher:teacherList) {
System.out.println("id:"+teacher.getId()+",tname:"+teacher.getTname());
}
}
static void testTransaction(ApplicationContext ctx){
TeacherDaoImpl teacherDao=ctx.getBean(TeacherDaoImpl.class);
int row=teacherDao.insertTeacher(new Teacher("哈哈哈"));
System.out.println("影响了"+row+"行");
}
}
XML配置方式
三步走
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
简单示例:
<!-- 示例演示给所有get、find、query开头的方法添加只读事务,其他方法是默认事务(可读可写) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="query*" read-only="true" />
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
< tx:advice / >默认配置如下:
传播行为(propagation)是REQUIRED
隔离级别(isolation level)是DEFAULT
事务默认可读写(read-write)
事务超时时间是数据库默认事务超时时间
unchecked异常RuntimeException异常触发事务回滚,checked异常Exception不会触发回滚
<!-- 切面,其实就是切入点和通知的结合 -->
<aop:config>
<aop:pointcut id="all_dao_method_pointcut" expression="execution(* com.john.spring.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="all_dao_method_pointcut" />
</aop:config>
示例(大致思路)
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.framework.version>5.1.5.RELEASE</spring.framework.version>
</properties>
<dependencies>
<!-- Spring IOC最小依赖是beans、context,我们引入context依赖,maven会自动将beans依赖一并引入 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.6</version>
</dependency>
<!-- 用于通过注解生成一些固定代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- 引入logback日志依赖 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--添加AOP的依赖包 如果不添加,tx_conf.xml的aop:pointcut id="all_dao_method"处出错-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
//下面这段插件是复制resources的,(如果你命名的资源包不叫resources,系统就不会给你编译,可以用下面这段代码解决问题)
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<!-- here the phase you need 如果定义resources时名称不是resource,那么compile不会给你编译,这个时候你可以copy-你命名的resources eg:我命名的是resource-->
<phase>compile</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<resources>
<resource>
<directory>src/main/resource</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
package com.lanou.spring.transaction.bean;
import com.mysql.jdbc.StringUtils;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Teacher {
private Integer id;
private String tname;
public Teacher(){
}
public Teacher(Integer id, String tname) {
this.id = id;
this.tname = tname;
}
public Teacher(String tname){
this.tname=tname;
}
}
package com.lanou.spring.transaction.dao;
import com.lanou.spring.transaction.bean.Teacher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
// 此注解和@Component作用一样, 只是含有特定的语义(一般用来标注dao层的类)
@Repository
public class TeacherDaoImpl {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 查询teacher表所有数据
* @return
*/
public List<Teacher> queryAll() {
List<Teacher> teachers = jdbcTemplate.query("select * from teacher", new BeanPropertyRowMapper<Teacher>(Teacher.class));
return teachers;
}
/**
* 插入teacher
* @param teacher
* @return 返回影响行数
*/
public int insertTeacher(Teacher teacher) {
// 通过伪代码演示如果我们手写事务控制代码的套路
// setAutoCommit(false);
// beginTransaction
// try{
int result = jdbcTemplate.update("insert into teacher (tname) values (?)", teacher.getTname());
// int retVal = 9 / 0;
// commit;
// } catch (Exception e) {
// rollback;
// }
//
return result;
}
}
jdbc.url=jdbc:mysql://localhost:3306/yafa5
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=root
jdbc.characterEncoding=utf8
?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="HOME_LOG" value="logs"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36}-[%M] - %msg%n
</Pattern>
</layout>
</appender>
<appender name="RollingFile"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>TRACE</level>
</filter>
<!-- 测试部署时使用如下配置 -->
<!-- 可让每天产生一个日志文件,最多 30 个,更早的删除 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${HOME_LOG}/log-%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger -
%msg%n
</pattern>
</encoder>
</appender>
<logger name="com.lanou.spring" level="DEBUG"/>
<logger name="org.springframework" level="ERROR"/>
<root level="debug">
<appender-ref ref="STDOUT"/>
<appender-ref ref="RollingFile" />
</root>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<!-- 通过xml配置声明式事务,我们需要添加tx命名空间 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 通过xml方式配置Spring声明式事务 配置start -->
<!-- 第一步:配置数据源 -->
<context:property-placeholder location="classpath: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}" />
<property name="connectionProperties">
<props>
<prop key="characterEncoding">${jdbc.characterEncoding}</prop>
</props>
</property>
</bean>
<!-- 第二步:初始化事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 第三步:配置事务AOP通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert*" rollback-for="ArithmeticException" />
<tx:method name="query*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 第四步:定义AOP配置(将上面的通知和表达式组装到一起) -->
<aop:config>
<aop:pointcut id="all_dao_method" expression="execution(* com.lanou.spring.transaction.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="all_dao_method" />
</aop:config>
<!-- 通过xml方式配置Spring声明式事务 配置结束 -->
</beans>
package com.lanou.spring;
import com.lanou.spring.transaction.bean.Teacher;
import com.lanou.spring.transaction.dao.TeacherDaoImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.List;
/**
* 纯XML方式入口类
*/
@Slf4j
@Configuration
@ImportResource(locations = "classpath:tx_conf.xml")
@ComponentScan(basePackages = "com.lanou.spring.transaction")
public class App {
/**
* 定义了一个JdbcTemplate类型的bean,是Spring提供给我们做jdbc CRUD操作的类
* @param dataSource 来自applicationContext.xml中,通过DI自动注入
* @return
*/
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
//testQuery(ctx);
testTransaction(ctx);
}
static void testQuery(ApplicationContext ctx) {
TeacherDaoImpl teacherDao = ctx.getBean(TeacherDaoImpl.class);
List<Teacher> teacherList = teacherDao.queryAll();
for(Teacher teacher : teacherList) {
System.out.println("id: " + teacher.getId()+", tname: " + teacher.getTname());
}
}
static void testTransaction(ApplicationContext ctx) {
TeacherDaoImpl teacherDao = ctx.getBean(TeacherDaoImpl.class);
int rowEffect = teacherDao.insertTeacher(new Teacher("托尼老师"));
System.out.println("影响了" + rowEffect + "行.");
}
}
Spring事务的隔离级别和传播行为
七种传播行为
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读取无效。
通俗的说,脏读就是指一个事务读取了一个未提交事务的数据
回滚事务
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>