何为数据库事务?
将多步数据操作(增删改)组成一个整体,执行时要嘛整体成功,要嘛整体失败。
四个特性:原子性,一致性,隔离性,持久性【概念问题,直接百度】
数据库并发问题
一个事务包含了多步数据库操作,当多个客户端同时执行多个事务时如果没有采取必要的隔离措施可能会发生并发问题,常见的并发问题包括脏读,不可重复读,幻象读,第一类丢失更新,第二类丢失更新【概念,百度】
数据库如何解决并发问题
数据库通过锁机制的方式来解决并发问题,但是锁的DML操作过于繁琐,所以数据库为用户提供了自动的锁机制,就是事务的隔离级别,只要用户指定了合适的隔离级别,数据库就会分析事务中的SQL语句并自动添加合适的锁
常见的隔离级别如下:
spring对事务管理的支持
spring对事务管理都是通过调用数据库的提交回滚,设置数据库的隔离级别来达到解决问题的,这不是spring的能力,只是spring对jdbc的封装。除了spring提供的传播机制。
spring事务管理主要是通过三个接口来实现:PlatformTransactionManager,TransactionDefinition,TransactionStatus
PlatformTransactionManager
事务的管理器,spring为各种持久层框架提供了不同的事务管理器实现类。用的时候根据不同的持久层采用不同的事务管理器。例如使用Mybatis,则使用org.springframework.jdbc.datasource.DataSourceTransactionManager
TransactionDefinition
定义了事务的事务隔离级别、事务传播、事务超时、是否只读
TransactionStatus
定义了事务的具体运行状态
事务的配置(以mybatis为例)
XML事务配置
1.配置数据源
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
<!-- 初始化连接大小 -->
<property name="initialSize" value="${initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${maxWait}"></property>
</bean>
2.配置事务管理器
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3.采用aop的方式为service层注入事务
<apo:config>
<!--定义切面,哪些方法需要注入事务-->
<aop:pointcut id="serviceMethod" expression="execution(* com.xzy.service.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"></aop:advisor>
</apo:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED" timeout="-1"/> <!--是否只读,事务传播机制,事务超时间,还可以配置其他选项自己百度-->
</tx:attributes>
</tx:advice>
注解方式
1.设置数据源(同xml方式)
2.配置事务管理器(同xml方式)
3.添加注解事务支持
<tx:annotation-driven transaction-manager="txManager"/>
4.在service实现类上加入注解@Transactional
@Transactional可以加在接口定义、接口方法、实现类、实现类方法上,但一般建议定义在实现类和实现类方法上
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class BookService {
// do someing...
}
spring事务在发生异常时的回滚
准备Service类
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class StudentService {
@Autowired
private StudentDao studentDao;
public List<Student> list(){
List<Student> list = studentDao.getAllStudent();
System.out.println(list);
return list;
}
public void update(){
Student student = new Student();
student.setId(1);
student.setName("test_" + System.currentTimeMillis());
studentDao.update(student);
}
}
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class BookService {
@Autowired
private BookDAO bookDAO;
@Autowired
private StudentService studentService;
public Book initBook(){
Book book = new Book();
book.setPrice(122);
book.setPublishDate(new Date());
book.setTitle("书");
return book;
}
public Book getBookById1(){
Book book = new Book();
book.setId(1);
book.setTitle("Java编程思想_" + System.currentTimeMillis());
book.setPrice(199.9);
book.setPublishDate(new Date());
return book;
}
public void add(){
bookDAO.add(initBook());
}
public void update(){
bookDAO.update(getBookById1());
}
public void test1(){
// 1 / 0 发生异常 这里不做处理 aop会自动回滚
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ; // 1/0会发生报错
}
public void test2(){
//发生异常时手动catch后事务不能回滚
try{
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ;
}catch (Exception e){
System.out.println("手动处理异常");
}
}
public void test3(){
//捕获异常后但是又抛出这样事务能回滚
try{
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ;
}catch (Exception e){
System.out.println("手动处理异常");
throw new RuntimeException("抛出"); //默认情况下对Error和RuntimeException及其子类进行回滚
}
}
public void test4(){
//手动回滚事务,这样即使做了异常处理我们手动可以进行回滚
try{
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ;
}catch (Exception e){
System.out.println("手动处理异常");
System.out.println("手动回滚事务");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //手动回滚事务
}
}
出现异常且不catch
当service抛出异常时自动回滚
public void test1(){
// 1 / 0 发生异常 这里不做处理 aop会自动回滚
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ; // 1/0会发生报错
}
出现异常并catch
如果手动捕获异常处理,则spring不能回滚
public void test2(){
//发生异常时手动catch后事务不能回滚
try{
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ;
}catch (Exception e){
System.out.println("手动处理异常");
}
}
出现异常catch且throw
出现异常时手动处理但抛出Error和RuntimeException及其子类进行回滚
public void test3(){
//捕获异常后但是又抛出这样事务能回滚
try{
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ;
}catch (Exception e){
System.out.println("手动处理异常");
throw new RuntimeException("抛出"); //默认情况下对Error和RuntimeException及其子类进行回滚
}
}
手动回滚
可以使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()进行手动回滚
public void test4(){
//手动回滚事务,这样即使做了异常处理我们手动可以进行回滚
try{
studentService.update();
bookDAO.add(initBook());
int i = 1 / 0 ;
}catch (Exception e){
System.out.println("手动处理异常");
System.out.println("手动回滚事务");
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //手动回滚事务
}
}
spring的事务传播行为
什么是spring的事务传播行为?
spring事务传播机制是用来控制当多个事务嵌套的时候的情况如何处理。例如上例子在bookService中方法使用了StudentService的方法,本身StudentService的方法也有自己的事务,当两个事务在一个方法里面时如何处理.
如何在service中方法中启用了线程调用其他service方法时事务如何嵌套?
public void test5(){
bookDAO.add(initBook());
Runnable runnable = new Runnable() {
@Override
public void run() {
studentService.update();
}
};
new Thread(runnable).start();
}
这种情况下,在子线程中会独立启动一个事务,与主线程无关