Spring 06 声明式事务

1、事务的概念

将一组操作数据的SQL作为一个整体提交,要么都成功,要么都失败

使用的原因:
保证数据的完整性和一致性,对数据操作是要保证数据的安全

2、事务的特性【ACID】

⑴ 原子性
将多个SQL作为一个整体,不可分割

⑵ 一致性
数据在操作前是正确的,操作后也是正确的
操作的结果应该和业务逻辑保持一致

⑶ 隔离性
多个事务并发操作同一个数据时,保证事务间的数据是隔离开来的,相关不会受到干扰,保证数据的安全

⑷ 持久性
事务操作数据库的结果,应该永久地保存到持久化存储器中

3、事务的隔离级别

分类

未提交读

int TRANSACTION_READ_UNCOMMITTED = 1;
一个事务读取到了另一个事务还没有提交的数据

可能产生脏读,不可重复读,幻读问题。但是它解决了数据丢失问题

例如:

   T1正在读取数据,并对数据进行操作
   T2 修改了数据,但是还没有提交数据
   T1 再次读取数据,可能发生脏读问题。因为T2可能会回滚事务

已提交读

int TRANSACTION_READ_COMMITTED = 2;
一个事务读取到了另外一个事务已经提交过的数据

可能产生不可重复读,幻读问题。但是它解决了数据丢失和脏读问题

例如:

    T1 正在读取数据,并对数据进行操作
    T2 修改了数据,并提交了数据
    T1 再次读取数据,发现两次读取到的数据不一致。这就产生了不可重复读问题

重复读

int TRANSACTION_REPEATABLE_READ = 4;
一个事务只能重复的读取当前事务中的操作数据,而不能读取到另外的事务中未提交和已提交的数据

可能产生幻读。但是它解决了数据丢失,脏读和不可重复读问题

例如:

     T1 正在读取数据,并对数据进行操作
     T2 修改了数据,并且提交了数据,但是没有提交数据
     T1 再次读取数据,两次读取到的数据是一致的

序列化/不可并发

int TRANSACTION_SERIALIZABLE = 8;
可以解决所有的问题,一般配合数据库锁的机制控制数据的安全【统计工作】

  幻读问题:一个事务正在进行某种统计操作,其他事务又进行数据的增删改等操作,导致两次统计到的结果不一致

例如:

     T1 正在统计表的记录数,得到一个结果
     T2 向数据库中添加了一些数据
     T3 再次统计表的记录数,得到的结果就比T1统计到的多

Tips:

   SELECT * FROM ??? WHERE ??? FOR UPDATE; // 行级锁

数据库的默认隔离级别


MySQL 4(重复读)
Oracle 2(已提交读)

4、事务管理方式

编程式事务

即使用原生的JDBC API进行事务的管理

步骤:
⑴ 获取数据库连接对象【Connection】
⑵ 取消事务的自动提交
⑶ 执行SQL操作
⑷ 正常完成操作时,手动提交事务
⑸ 执行失败时,回滚事务
⑹ 关闭相关资源(释放连接等)

声明式事务

通过相关配置,给程序方法增加事务的操作

5、Spring的声明式事务

相关API

Spring框架提供了PlatformTransactionManager接口,用来管理底层的事务。并且提供了相关的实现类

⑴ DataSourceTransactionManager
Spring和JdbcTemplate或MyBatis框架集成时,提供的事务管理器

⑵ HibernateTransactionManager
Spring和Hibernate框架集成时,提供的事务管理器

使用步骤

⑴ 拷贝jar包
① 和IOC有关的:

   commons-logging-1.1.3.jar
   spring-beans-4.0.0.RELEASE.jar
   spring-context-4.0.0.RELEASE.jar
   spring-core-4.0.0.RELEASE.jar
   spring-expression-4.0.0.RELEASE.jar

② 和AOP有关的:

  spring-aop-4.0.0.RELEASE.jar
  spring-aspects-4.0.0.RELEASE.jar
  com.springsource.net.sf.cglib-2.2.0.jar
  com.springsource.org.aopalliance-1.0.0.jar
  com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

③ 和数据库有关的:

   spring-jdbc-4.0.0.RELEASE.jar
   spring-orm-4.0.0.RELEASE.jar
   spring-tx-4.0.0.RELEASE.jar

④ 数据库驱动和c3p0

   mysql-connector-java-5.1.7-bin.jar
   c3p0-0.9.1.2.jar

⑵ 创建c3p0的properties配置文件
⑶ 创建核心配置文件
添加context和tx名称空间

① 通过

    <context:component-scan base-package="要扫描的包及其子包" />

标签,来设置自动扫描包

② 通过

    <context:property-placeholder location="???.properties" />

标签,来引入外部配置文件

③ 通过bean 标签,声明c3p0即DataSource对象

④ 通过bean 标签,声明JdbcTemplate对象。注意需要给其dataSource属性赋值,引用DataSource(c3p0)对象

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>

⑤ 通过bean标签,声明TransactionManager对象。注意需要给其dataSource属性赋值,引用DataSource(c3p0)对象

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

以DataSourceTransactionManager为例

⑥ 通过

    <tx:annotation-driven transaction-manager="事务管理器" />

标签,来开启基于注解的声明式事务

⑷ 编写具体的DAO、Service,并在具体的业务方法上,加上@Transactional注解,即可给方法开启事务

6、Transactional【事务】的属性

事务传播行为

【propagation属性】
方法被调用时,事务的开启方式

一共有7个传播行为,常用的有2个:
⑴ Propagation.REQUIRED
表示一个方法被调用时,如果已经存在了一个事务,则加入其中;否则,会开启一个新的事务。【默认值】

⑵ Propagation.REQUIRES_NEW
表示一个方法被调用时,不管调用的方法是否存在事务,都会开启一个新的事务

事务隔离级别

【isolation属性】

Isolation.DEFAULT
表示与数据库的默认隔离级别一致
MySQL 4(重复读)

Isolation.READ_UNCOMMITTED 未提交读
Isolation.READ_COMMITTED 已提交读
Isolation.REPEATABLE_READ 重复读
Isolation.SERIALIZABLE 序列化/不可并发

事务回滚策略

【rollbackFor属性】
当发生什么样的异常(包括其子类)时,进行回滚

对于Spring框架,默认情况下,RuntimeException类型才会回滚;对于编译时异常和Error,是不会回滚的
所以,需要修改Spring的默认回滚策略

例如:rollbackFor = Exception.class

Tips:
⑴ RuntimeException:运行时异常
例如FileNotFoundException,这种可以遇见到的异常

⑵ Exception:编译期异常/检查异常/受控异常

与rollbackFor相对的是:
【noRollbackFor】属性
遇到指定的异常类型(包括其子类),不进行回滚

事务超时属性

【timeout】属性
timeout = 超时时间(秒)

如果一个事务的执行时间,超过了指定的时长(n秒),就被视为超时。当该事务执行完毕时,会抛出异常
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was ???

只读属性

【readOnly】属性
一般查询的时候,会将该属性设置为true,表明为只读。这样数据库底层会对查询进行优化处理,提高查询的效率

注意:当一个连接对象(Connection)设置了只读属性,则其再操作增删改时,会报错:
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

Transaction属性设置示例

【示例一】

// 事务传播行为为当有事务时就加入,没有就新建一个事务;
// 默认隔离级别;
// 事务回滚策略:Exception及其子异常
// 超时:3秒超时
@Transactional(propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,
        rollbackFor = Exception.class,
        timeout = 3)
public void testTransaction() { }

【示例二】

 // 查询业务操作,设置连接属性为只读,提高效率
    @Transactional(readOnly = true)
    public void testQuery() { }

代码示例

【SQL语句】

USE test;

CREATE TABLE books(
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(100) NOT NULL,
  price INT NOT NULL,
  stock INT NOT NULL
);

INSERT INTO books(name, price, stock)
VALUES('小王子', 34, 100),
('圣经', 48, 100),
('福尔摩斯探案集', 84, 100);

【Book类(JavaBean)】

属性:Integer id, String name, Integer price, Integer stock;提供get和set,有参无参构造,重写toString方法

【核心配置文件】

<context:component-scan base-package="com.test.tx" />

<context:property-placeholder location="classpath:/jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driverClass}" />
    <property name="jdbcUrl" value="${jdbc.url}" />
    <property name="user" value="${jdbc.user}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<!-- 声明JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 声明事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 开启基于注解的声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />

【BookDao接口】

package com.test.tx.dao;

import java.util.List;

import com.test.tx.bean.Book;

public interface BookDao {

    int updateBookPrice(String name, Integer price);

    int updateBookStock(String name);

    Book getBook(String name);

    List<Book> queryBooks();

}

【BookDaoImpl实现类】

package com.test.tx.dao;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.test.tx.bean.Book;

@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int updateBookPrice(String name, Integer price) {
        String sql = "UPDATE test.books SET price = ? WHERE name = ?";
        return jdbcTemplate.update(sql, price, name);
    }

    @Override
    public int updateBookStock(String name) {
        String sql = "UPDATE test.books SET stock = stock - 1 WHERE name = ?";
        return jdbcTemplate.update(sql, name);
    }

    @Override
    public Book getBook(String name) {
        String sql = "SELECT id, name, price, stock FROM test.books WHERE name = ?";
        RowMapper<Book> rowMapper = new BeanPropertyRowMapper<Book>(Book.class);
        return jdbcTemplate.queryForObject(sql, rowMapper, name);
    }

    @Override
    public List<Book> queryBooks() {
        String sql = "SELECT id, name, price, stock FROM test.books";
        RowMapper<Book> rowMapper = new BeanPropertyRowMapper<Book>(Book.class);
        return jdbcTemplate.query(sql, rowMapper);
    }

}

【BookService接口】

package com.test.tx.service;

import java.util.List;

import com.test.tx.bean.Book;

public interface BookService {

    int updateBookPrice(String name, Integer price) throws Exception;

    int updateBookStock(String name) throws Exception;

    Book getBook(String name);

    List<Book> queryBooks();

}

【BookServiceImpl实现类】

package com.test.tx.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.test.tx.bean.Book;
import com.test.tx.dao.BookDao;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    /*
     * 测试超时
     * 
     * org.springframework.transaction.TransactionTimedOutException: Transaction
     * timed out: deadline was ???
     * 
     * @Transactional(timeout = 3)
     */
    // ---------------------------------------------------------------------------------
    /*
     * 测试更新时,设置连接为只读
     * 
     * Caused by: java.sql.SQLException: Connection is read-only. Queries
     * leading to data modification are not allowed
     * 
     * @Transactional(readOnly = true)
     */
    // ---------------------------------------------------------------------------------

    // 这里设置事务的传播行为是:不管有没有事务,都会开启一个新的事务
    // 是为了测试MulService的testTx方法【因为和下面的更新图书库存操作是两个事务,所以这个事务出错了,但是不会回滚。最终效果是价格变了,但库存没有变】
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int updateBookPrice(String name, Integer price) throws Exception {
        // 测试超时【让线程休眠】
        // Thread.sleep(4000);

        return bookDao.updateBookPrice(name, price);
    }

    /*
     * 如果不设置回滚策略,则会造成执行MulService的testTx方法时,图书的价格变了,但是库存没有变,事务并没有回滚
     * 
     * @Transactional(rollbackFor = Exception.class)
     */
    // ---------------------------------------------------------------------------------

    // 这里设置事务的传播行为是:不管有没有事务,都会开启一个新的事务
    // 是为了测试MulService的testTx方法【因为和上面的更新图书价格操作是两个事务,所以这个事务出错了,但是不会回滚。最终效果是价格变了,但库存没有变】
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int updateBookStock(String name) throws Exception {
        // 这里抛出一个编译时异常【Spring默认是不回滚的】
        // FileInputStream fis = new FileInputStream("a/a/a/a");

        // 这里有一个算数异常
        int i = 1 / 0;
        return bookDao.updateBookStock(name);
    }

    // 设置为只读的,提高效率
    @Transactional(readOnly = true)
    public Book getBook(String name) {
        return bookDao.getBook(name);
    }

    // 设置为只读的,提高效率
    @Transactional(readOnly = true)
    public List<Book> queryBooks() {
        return bookDao.queryBooks();
    }

}

【MulService类(用于调用BookServiceImpl的两个业务方法)】

package com.test.tx.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class MulService {

    @Autowired
    private BookService bookService;

    @Transactional
    public void testTx(String name, Integer price) throws Exception {
        bookService.updateBookPrice(name, price);

        bookService.updateBookStock(name);
    }

}

【测试类】

package junit.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.test.tx.bean.Book;
import com.test.tx.service.BookService;
import com.test.tx.service.MulService;

public class TestTx {
    private ApplicationContext ioc = new ClassPathXmlApplicationContext("???.xml");

    @Test
    // 测试执行更新图书价格操作
    public void test1() throws Exception {
        BookService bookService = ioc.getBean(BookService.class);
        bookService.updateBookPrice("小王子", 100);
    }

    @Test
    // 测试更新图书的库存和价格【两个Dao操作】
    public void test2() throws Exception {
        MulService mulService = ioc.getBean(MulService.class);
        String name = "小王子";
        Integer price = 200;
        mulService.testTx(name, price);
    }

    @Test
    public void test3() {
        BookService bookService = ioc.getBean(BookService.class);
        Book book = bookService.getBook("小王子");
        System.out.println(book);
    }

}

7、基于XML的声明式事务

相关标签

 <tx:advice id="通知的id" transaction-manager="事务管理器" ></tx:advice>

用于声明通知的事务标签。它有tx:attributes子标签

<tx:attributes></tx:attributes>
 tx:advice的子标签,在其里面 声明开启事务的方法标签

<tx:method
        name="" 
        isolation="DEFAULT"
        no-rollback-for=""
        propagation="REQUIRED"
        read-only="false"
        rollback-for=""
        timeout="-1" />

 name:需要开启事务的方法的名字
 isolation:事务隔离级别
 no-rollback-for:事务不回滚策略
 propagation:事务传播行为
 read-only:只读属性
 rollback:事务回滚策略
 timeout:超时【默认值为-1,即不超时】

Tips:name属性可以使用* 来代替多个字符
例如:getUser方法名,可以用get* 来代替

设置开启事务方法的Transaction的属性

<aop:config></aop:config>

在其里面声明切入点表达式和组织者

<aop:pointcut expression="切入点表达式" id="表达式的id" />

用于声明切入点表达式

<aop:advisor advice-ref="所引用的事务标签的组织者的id" pointcut-ref="所引用的切入点表达式的id" />

用于声明组织者,需要引用事务的通知的id(tx:advice)和切入点表达式(aop:pointcut)
该标签用于匹配需要开启事务的业务逻辑方法

示例

<tx:advice id="adviceId" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 设置开启事务方法的Transaction的属性 -->
        <tx:method name="test*" propagation="REQUIRED" />
        <!-- 设置超时为3秒;出现异常就回滚的策略 -->
        <tx:method name="update*" timeout="3" rollback-for="java.lang.Exception" />

        <!-- 查询业务方法,设置连接为只读,提高效率 -->
        <tx:method name="query*"  read-only="true" />
        <!-- 事务隔离级别:重复读 -->
        <tx:method name="get*" read-only="true" isolation="REPEATABLE_READ" />
    </tx:attributes>
</tx:advice>

<aop:config>
    <!-- 声明切入点表达式 -->
    <aop:pointcut expression="execution(* com.test..service..*(..))" id="pointcutId" />
    <!-- 声明组织者 -->
    <aop:advisor advice-ref="adviceId" pointcut-ref="pointcutId" />
</aop:config>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值