Spring 06 声明式事务

xiaoxiao2021-02-28  80

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>
转载请注明原文地址: https://www.6miu.com/read-29244.html

最新回复(0)