一、基础配置
(1)配置文件:使用spring注解方式的配置方式
db.xml <bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
(2)PO 测试表结构:
School.java@Entity
@Table(name = "SCHOOL")
public class School {
@Id
@GenericGenerator(name = "generator", strategy = "increment")
@GeneratedValue(generator = "generator")
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@Column(name = "NAME")
private String name;
@Column(name = "ADDRESS")
private String address;
public School(String name, String address) {
this.name = name;
this.address = address;
}
..........
}
Student.java
@Entity
@Table(name = "STUDENT")
public class Student {
@Id
@GenericGenerator(name = "generator", strategy = "increment")
@GeneratedValue(generator = "generator")
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@Column(name = "NAME")
private String name;
@Column(name = "AGE")
private Integer age;
@ManyToOne(targetEntity = School.class)
@JoinColumn(name = "SCHOOL_ID")
private School school;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "LOGIN_DATE")
private Date loginDate;
public Student(String name, int age, School school) {
this.name = name;
this.age = age;
this.school = school;
}
......
}
Student 多<———>一关系
(3)dao:StudentDAO,SchoolDAO
(4)service:StudentSchoolService,向其中注入StudentDAO,SchoolDAO
二、事务传播机制 测试代码
PROPAGATION_REQUIRED | 支持当前事务(即合并为一个事务),如果当前没有事务,就新建一个事务。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。 |
(1)在service上生命@Transactional,该类的所有方法将都受到事务控制,默认是:Propagation.REQUIRED
org.springframework.transaction.annotation.Transactional
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* A qualifier value for the specified transaction.
* <p>May be used to determine the target transaction manager,
* matching the qualifier value (or the bean name) of a specific
* {@link org.springframework.transaction.PlatformTransactionManager}
* bean definition.
*/
String value() default "";
/**
* The transaction propagation type.
* Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
(2)@Transactional(propagation=Propagation.NEVER):以非事务方式执行,如果当前存在事务,则抛出异常。
@Transactional(propagation=Propagation.NEVER)
public void methodNever() {
System.out.println("============ BEGIN methodNever============");
this.schoolDAO.save(new School("11", "11"));
System.out.println("============ SPLIT TOW METHOD ============");
this.schoolDAO.save(new School("22", "22"));
System.out.println("============ END methodNever============");
}
输出:
============ BEGIN methodNever============
Hibernate: select max(id) from SCHOOL
============ SPLIT TOW METHOD ============
============ END methodNever============
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
<1>当在单方法上声明时,方法仍被事务控制,如上图输出。
@Transactional(propagation=Propagation.REQUIRED)
public void methodNeverInnerREQUIREDSameBean() {
methodNever();
}
<2>如果在service内部,在一个REQUIRED方法内包含一个NEVER方法,声明为NEVER的方法会正常在外部事务范围内执行。
<3>如果在serviceA的REQUIRED方法调用serviceB的NEVER方法,NEVER方法会抛出异常。对NEVER事务抛出异常的描述指的是在嵌套的情况下。
serviceB:public class SchoolService
@Transactional(propagation = Propagation.NEVER)
public void methodNever() {
this.schoolDAO.save(new School("11", "11"));
}
serviceA:public class StudentSchoolService
@Transactional(propagation=Propagation.REQUIRED)
public void methodNeverInnerREQUIREDDifBean() {
schoolService.methodNever();
}
执行serviceA.methodNeverInnerREQUIREDDifBean()方法,抛出异常:
Exception in thread "main" org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
如果在methodNeverInnerREQUIREDDifBean方法中调用NEVER方法之前有数据提交,也将被回滚,如:
@Transactional(propagation=Propagation.REQUIRED)
public void methodNeverInnerREQUIREDDifBean() {
this.schoolDAO.save(new School("11", "11")); // 会回滚
schoolService.methodNever();
}
(2)
PROPAGATION_NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
在serviceA的REQUIRED方法调用serviceB的NOT_SUPPORTED方法:
当执行到serviceB的NOT_SUPPORTED方法时,serviceA的REQUIRED方法事务将会被挂起,先进行NOT_SUPPORTED方法的执行
serviceB:public class SchoolService
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodNotSupported() {
this.schoolDAO.save(new School("11", "11"));
}
serviceA:public class StudentSchoolService
@Transactional(propagation=Propagation.REQUIRED)
public void methodNotSupportedInnerREQUIREDDifBean() {
System.out.println("============ BEGIN methodNotSupport ============");
this.schoolDAO.save(new School("11", "11"));
System.out.println("============ finish required save ============");
schoolService.methodNotSupported();
System.out.println("============ finish not supported save ============");
System.out.println("============ END methodNotSupport ============");
}
输出:
============ BEGIN methodNotSupport ============
Hibernate: select max(id) from SCHOOL
============ finish required save ============
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?) ------serviceB的NOT_SUPPORTED方法
============ finish not supported save ============
============ END methodNotSupport ============
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?) ------serviceA的required方法
(3)
PROPAGATION_SUPPORTS:
支持当前事务,如果当前没有事务,就以非事务方式执行
在serviceA的REQUIRED方法调用serviceB的SUPPORTS方法:supports方法中的操作也会包含在父方法的事务内
serviceB:public class SchoolService
@Transactional(propagation = Propagation.SUPPORTS)
public void methodSupports() {
this.schoolDAO.save(new School("11", "11"));
}
serviceA:public class StudentSchoolService
@Transactional(propagation=Propagation.REQUIRED)
public void methodSupportsInnerREQUIREDDifBean() {
System.out.println("============ BEGIN methodSupports ============");
this.schoolDAO.save(new School("11", "11"));
schoolService.methodSupports();
System.out.println("============ END methodSupports ============");
}
输出:
============ BEGIN methodSupports ============
Hibernate: select max(id) from SCHOOL
============ END methodSupports ============
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
如果将serviceA:public class StudentSchoolService中的REQUIRED方法去掉事务的配置
整提操作将都不存在事务,数据库操作按顺序执行,输出:
============ BEGIN methodSupports ============
Hibernate: select max(id) from SCHOOL
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
============ END methodSupports ============
(4)
PROPAGATION_MANDATORY:
支持当前事务,如果当前没有事务,就抛出异常
在serviceA的无事务配置方法调用serviceB的MANDATORY方法:执行到serviceB的MANDATORY方法时将抛出异常,在serviceA中调用serviceB.MANDATORY方法之前的数据库操作会正常提交(因为没在事务控制范围内)。
serviceB:public class SchoolService
@Transactional(propagation = Propagation.MANDATORY)
public void methodMandatory() {
this.schoolDAO.save(new School("11", "11"));
}
serviceA:public class StudentSchoolService
public void methodMandatoryInnerNoTransactionDifBean() {
System.out.println("============ BEGIN methodMandatory ============");
this.schoolDAO.save(new School("11", "11")); // 正常save
schoolService.methodMandatory(); // 抛出异常
System.out.println("============ END methodMandatory ============");
}
输出:
============ BEGIN methodMandatory ============
Hibernate: select max(id) from SCHOOL
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
Exception in thread "main" org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
(5)在事务内部新起线程执行的情况
例:
@Transactional(propagation = Propagation.REQUIRED)
public void methodRequiredNewThread() {
System.out.println("============ BEGIN methodRequiredNewThread ============");
this.schoolDAO.save(new School("11", "11")); // 在方法的事务内
new Thread(new Runnable() {
public void run() {
System.out.println("save in thread");
schoolDAO.save(new School("22", "22")); // 不在事务内
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("============ END methodRequiredNewThread ============");
}
输出:
============ BEGIN methodRequiredNewThread ============
Hibernate: select max(id) from SCHOOL
save in thread
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
============ END methodRequiredNewThread ============
Hibernate: insert into SCHOOL (ADDRESS, NAME, id) values (?, ?, ?)
配置了事务的方法内部,新起线程执行的数据库操作,不在外部的方法事务内。如果内部线程中使用配置了配置事务的方法,该方法使用新事务。
spring事务,根据每个线程分配一个session。所在内部起新线程,就另起了一个session,和新事务。
(6)PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW的嵌套
两个PROPAGATION_REQUIRED事务方法嵌套,由于两个方法合并成一个事务,所以不论外部或内部的方法抛出异常,都将回滚。
外部方法:PROPAGATION_REQUIRED,内部方法:PROPAGATION_REQUIRES_NEW:
<1>外部方法REQUIRED抛出异常(所有数据库操作方法之后,包括嵌套字方法):内部REQUIRES_NEW事务方法可正常提交
<2>内部方法REQUIRES_NEW抛出异常:所有操作都将回滚
PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED的区别
嵌套抛出异常的情况
三、@Transactional(readOnly = true)
public @interface Transactional:
/**
* {@code true} if the transaction is read-only.
* Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
*/
boolean readOnly() default false;
只读型事务
默认为false,该注解只是给事务系统一个提示,并不一定会导致写操作失败。看其他文章描述,声明为只读事务,spring会对性能方面进行优化。
例子:在声明为readOnly事务内执行数据库save操作,抛出异常
@Transactional(readOnly = true)
public void methodReadOnly() {
this.schoolDAO.save(new School("11", "11"));
}
输出:
Exception in thread "main" org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.