声明式事务
事务分为声明式和编程式两种: 声明式事务:声明式事务是指通过注解的形式对事务的各种特性进行控制和管理。 编程式事务:指的是通过编码的方式实现事务的声明。 |
编码方式实现事务:try{ // 获取数据库连接 Connection connection = JdbcUtils.getConnection(); // 设置手动管理事务 connection.setAutoCommit(false); …… 执行若干操作 …… // 提交事务 connection.commit(); } catch (Exception e) { // 回滚事务 connection.rollback() } finally { // 关闭数据库连接 connection.close(); } |
声明式事务环境搭建11.2.1、准备测试数据库##创建tx数据库 drop database if exists `tx`; CREATEdatabase `tx`; ##切换tx数据库 USE `tx`;
##删除用户表 DROPTABLE IF EXISTS `user`; ##创建用户表 CREATETABLE `user` ( `id` intprimarykey auto_increment, `username` varchar(50) NOTNULL, `money` int(11) DEFAULTNULL ); ##插入数据 insert into `user`(`username`,`money`) values ('张三',1000),('李四',1000);
##删除图书表 droptable if exists `book`; ##创建图书表 createtable `book`( `id` intprimarykey auto_increment, `name` varchar(500) notnull, `stock` int ); ##插入数据 insertinto book(`name`,`stock`) values('java编程思想',100),('C++编程思想',100);
##查看数据 select * from book; select * fromuser; 创建一个Java工程,导入Jar包Spring的core核心包 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 Spring切面包 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-aop-4.0.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar Spring数据持久层包与数据库 c3p0-0.9.1.2.jar mysql-connector-java-5.1.37-bin.jar spring-jdbc-4.0.0.RELEASE.jar spring-orm-4.0.0.RELEASE.jar spring-tx-4.0.0.RELEASE.jar Spring测试包 spring-test-4.0.0.RELEASE.jar 日记包 commons-logging-1.1.3.jar log4j-1.2.17.jar
在源码目录(src或其他)下创建一个log4j.properties属性 # Global logging configuration log4j.rootLogger=INFO,stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p[%t]-%m%n 测试Service的默认事务实验1:测试service服务层的默认事务 在源码目录(src或其他源码目录)下创建一个jdbc.properties属性 jdbc.username=root jdbc.password=root jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/tx
创建测试的类 publicclass Book { private Integer id; private String name; privateintstock;
publicclass User { private Integer id; private String username; privateintmoney;
编写两个Dao类 @Repository publicclass BookDao { @Autowired private JdbcTemplate jdbcTemplate;
public List<Book>queryAll() { String sql = "select id,name,stock from book"; returnjdbcTemplate.query(sql, new BeanPropertyRowMapper(Book.class)); }
public Book queryById(int id) { String sql = "select id,name,stock from book where id= ?"; returnjdbcTemplate.queryForObject(sql,newBeanPropertyRowMapper(Book.class), id); }
publicint update(Book book) { String sql = "update book set name=?,stock=? whereid=?"; returnjdbcTemplate.update(sql,book.getName(), book.getStock(), book.getId()); }
publicint deleteById(int id) { String sql = "delete from book where id=?"; returnjdbcTemplate.update(sql, id); } }
@Repository publicclass UserDao {
@Autowired private JdbcTemplate jdbcTemplate;
public List<User>queryAll() { String sql = "select id ,username,money fromuser"; returnjdbcTemplate.query(sql, newBeanPropertyRowMapper(User.class)); }
public User queryById(int id) { String sql = "select id ,username,money from userwhere id = ?"; returnjdbcTemplate.queryForObject(sql,newBeanPropertyRowMapper(User.class), id); }
publicint update(User user) { String sql = "update user set username=?,money=? whereid=?"; returnjdbcTemplate.update(sql,user.getUsername(), user.getMoney(), user.getId()); }
publicint deleteById(int id) { String sql = "delete from user where id = ?"; returnjdbcTemplate.update(sql, id); } }
一个Service测试 @Service publicclass TransactionService { @Autowired private BookDao bookDao; @Autowired private UserDao userDao;
publicvoid updateUserAndBook() { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); } }
在applicationContext.xml配置文件中配置内容 <!-- 包扫描 --> <context:component-scan base-package="com.xypuxing"></context:component-scan> <!-- 加载jdbc.properties --> <context:property-placeholder location="classpath:jdbc.properties"/> <!-- 数据库连接池 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="driverClass" value="${jdbc.driverClass}"/> </bean> <!-- 配置jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean>
测试的代码 @ContextConfiguration(locations = "classpath:applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) publicclass TransactionServiceTest {
@Autowired private TransactionService transactionService;
@Test publicvoid testUpdateUserAndBook() { transactionService.updateUserAndBook(); } }
运行结果: |
异常的演示可以在两个操作中添加一个产生异常的代码。 @Service publicclass TransactionService { @Autowired private BookDao bookDao; @Autowired private UserDao userDao;
publicvoid updateUserAndBook() { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); int 12 / 0; // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); } } |
这个时候,我们发现。其实前面的两个dao操作,其实是两个事务。
bookDao.update(new Book(2, "xxx", 2222)); 这是一个事务 int 12 / 0; 产生了错误,导致后面的语句没有执行 userDao.update(new User(2, "ssssssss", 2222));这是另一个事务
这个时候,我们需要使用声明式事务来控制多个操作,在一个业务中,要么都成功。要么都失败!
Spring事务引入的分析------PlatformTransactionManager类简单介绍
PlatformTransactionManager类是Spring提供的专门用于统一管理事务的一个接口。 那么不同的持久层技术都对其实现了自己的事务管理类。 使用Spring注解式事务管理,只需要分三个简单的步骤: 1、配置自己需要用的事务管理器类------->>>>>DatasourceTransactionManager(这里是数据库连接池版本) <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="c3p0DataSource"></property> </bean>
2、配置事务的自动代理 <tx:annotation-driven transaction-manager="transactionManager" />
3、在需要添加事务的目标方法上,添加@Transaction注解 使用Spring的注解声明事务管制实验2:测试Spring的声明式事务
需要使用@Transactional注解来标,此方法交给Spring来管理事务 @Transactional publicvoid updateUserAndBook() { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); int i = 12 / 0; // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); }
applicationContext.xml配置文件中的内容 <!-- 配置数据库连接池的事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 表示解析 @Transactional注解的对象,使用的事务管理器 --> <tx:annotation-driven transaction-manager="transactionManager"/> noRollbackFor和noRollbackForClassName测试不回滚的异常实验3:noRollbackFor和noRollbackForClassName测试不回滚的异常
默认情况下,@Transactional是回滚运行时异常。也就是RuntimeException及其子异常或 Error错误。
noRollbackFor属性表示不回滚的异常类型/** *Transaction默认回滚的是RuntimeException,运行时异常。 *noRollbackFor属性表示不回滚的异常类型<br/> * 算术异常和类型转换异常不回滚事务 */ @Transactional(noRollbackFor= { //表示算术异常和类型转换异常,不需要回滚事务 ArithmeticException.class,ClassCastException.class }) publicvoid updateUserAndBook() { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); Objecto = new Long(1); Strings = (String) o; // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); } noRollbackForClassName属性表示不回滚的异常全类名/** *Transaction默认回滚的是RuntimeException,运行时异常。 *noRollbackForClassName属性表示不回滚的异常全类名<br/> * 算术异常和类型转换异常不回滚事务 */ @Transactional(noRollbackForClassName= {"java.lang.ArithmeticException","java.lang.ClassCastException"}) publicvoid updateUserAndBook() { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); int i = 12 / 0; // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); } 自定义设置回滚异常实验5:rollbackFor和rollbackForClassName回滚的异常
设置回滚的异常类型rollbackFor/** *rollbackFor 属性设置回滚的异常类型<br/> */ @Transactional(rollbackFor={FileNotFoundException.class}) publicvoid updateUserAndBook() throws FileNotFoundException { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); FileInputStreamfis = new FileInputStream(new File("d:\\1.txt")); // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); }
11.6.2、设置回滚的异常全类名/** *rollbackFor 属性设置回滚的异常类型<br/> *rollbackForClassName 属性设置回滚的异常类型全类名 */ @Transactional( rollbackForClassName="java.io.FileNotFoundException") publicvoid updateUserAndBook() throws FileNotFoundException { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); FileInputStreamfis = new FileInputStream(new File("d:\\1.txt")); // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); } |
事务的只读属性
实验4:测试readOnly只读属性
readOnly 属性设置事务是否允许写操作。readOnly 默认值是false,表示可读,可写。如果设置为true 表示只支持读操作。
声明事务代码
/**
* readOnly表示事务只读,不支持写操作。
*/
@Transactional(readOnly=true)
publicvoid updateUserAndBook() {
// 更新图书
bookDao.update(new Book(2, "xxx", 2222));
// 更新用户
userDao.update(new User(2, "ssssssss", 2222));
}
在写操作的方法上标注为只读。就会报异常
事务超时属性timeout(秒为单位)
timeout是设置超时属性。以秒为单位。
/**
*timeout设置事务超时时间,以秒为单位
*/
@Transactional(timeout=2)//两秒超时
publicvoid updateUserAndBook() throws InterruptedException {
// 更新图书
bookDao.update(new Book(2, "xxx", 2222));
Thread.sleep(3000);
// 更新用户
userDao.update(new User(2, "ssssssss", 2222));
}
当两秒钟时间内不能完成所有操作,就会抛异常。
事务隔离级别
四种事务隔离级别:
一:读未提交 read uncommitted
二:读已提交 read committed
三:可重复读 repeatable read
四:串行事务 serializable
由事务隔离级别产生的几个常见问题:
读未提交,可导致----->>>> 脏读
读已提交,可导致----->>>> 不可重复读
重复读,可导致 ----->>>> 幻读
脏读:所谓脏读,指的是,在某一个事务中,读取到了其他事务中未提交的数据。称之为脏读。
不可重复读:指的是一个事务中,多次读取,由于其他事务进行修改操作。导致两次得到的内容不同。称之为不可重复读。
幻读:指的是在一个事务中,每次读取的数据都跟第一次的数据相同,不管数据本身是否已经被其他事务所修改。称之为幻读。
1.查看当前会话隔离级别
select@@tx_isolation;
2.查看系统当前隔离级别
select@@global.tx_isolation;
3.设置当前会话隔离级别
set session transaction isolation level read uncommitted;
set sessiontransaction isolation level read committed;
set sessiontransaction isolation level repeatable read;
set sessiontransaction isolation level serializable;
4.开启事务
start transaction;
5.提交事务
commit;
6.回滚事务
rollback;
脏读演示
读未提交演示:
幻读的演示
串行化事务的演示
事务的传播特性propagation什么是事务的传播行为: 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。 事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务的传播特性,有以下几种类型: |
注解演示事物传播特性创建UserService类 @Service publicclass UserService {
@Autowired private UserDao userDao;
@Transactional publicvoid updateUser() { userDao.update(new User(1, "第二个操作成功!用户名被修改", 0)); } }
创建BookService类 @Service publicclass BookService {
@Autowired BookDao bookDao;
@Transactional publicvoid updateBook() { bookDao.update(new Book(1, "第一个操作成功!图书被修改", 0)); } }
修改TransactionService类 @Service publicclass TransactionService { @Autowired private BookDao bookDao; @Autowired private UserDao userDao; @Autowired private BookService bookService; @Autowired private UserService userService;
事物传播测试方法 @Transactional publicvoid multiUpdate() { bookService.updateBook(); userService.updateUser(); }
/** * timeout设置事务超时时间,以秒为单位 */ @Transactional publicvoid updateUserAndBook() throws InterruptedException { // 更新图书 bookDao.update(new Book(2, "xxx", 2222)); // 更新用户 userDao.update(new User(2, "ssssssss", 2222)); } }
测试的代码 @Test publicvoid testMultiUpdate() { transactionService.multiUpdate(); }
实验1:大小事务传播特性都是REQUIRED@Transactional(propagation = Propagation.REQUIRED) publicvoid multiUpdate() {
@Transactional(propagation = Propagation.REQUIRED) publicvoid updateBook() {
@Transactional(propagation=Propagation.REQUIRED) publicvoid updateUser() { |
当所有的操作都是REQUIRED的时候,所有操作都在一个事务中。所有操作要么都成功。要么都失败。
实验2:大小事务传播特性都是REQUIRES_NEW@Transactional(propagation = Propagation.REQUIRES_NEW) publicvoid multiUpdate() @Transactional(propagation = Propagation.REQUIRES_NEW) publicvoid updateBook() @Transactional(propagation = Propagation.REQUIRES_NEW) publicvoid updateUser()
|
1、这个测试,在调用multiUpdate方法和updateUser方法的时候会开启事务。如果程序正常运行。每个事务都会正常提交。 2、如果程序中有异常出现。就会从异常出现的代码所在的事务开始,到后面所有代码操作都不会成功!
实验5:大事务是REQUIRED,小1REQUIRES_NEW,小2REQUIRED@Transactional(propagation = Propagation.REQUIRED) publicvoid multiUpdate() @Transactional(propagation = Propagation.REQUIRES_NEW) publicvoid updateBook() @Transactional(propagation = Propagation.REQUIRED) publicvoid updateUser()
1、如果程序都正常运行,所有事务都会正常提交。 2、如果程序出现异常。就会从出现异常的代码所在的事务开始,以后面所有代码所在的事务都不会成功! xml配置式事务声明去掉。所有@Transaction的注解。 在applicationContext.xml配置文件中进行如下配置: <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 事务管理器,需要数据库连接池对象 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 需要配置方法的事务特性 --> <tx:advice id="advice1" transaction-manager="transactionManager"> <tx:attributes> <!-- 表示update打头的方法,都使用新的事务 --> <tx:method name="update*" propagation="REQUIRES_NEW" /> <!-- 表示multiUpdate方法都使用新的事务 --> <tx:method name="multiUpdate" propagation="REQUIRES_NEW"/> </tx:attributes> </tx:advice> <!-- 配置切面的配置信息--> <aop:config> <!-- 配置切入点。切入点可以知道有哪些类,哪些方法进入切入管理。 --> <aop:pointcut expression="execution(* com.xypuxing.service.*Service.*(..))"id="pointcut1"/> <!-- 配置切入点中都使用哪些事务特性 --> <aop:advisor pointcut-ref="pointcut1" advice-ref="advice1"/> </aop:config> |
Spring整合Web
在web工程中添加Spring的jar包。
Spring的核心包
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.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
JDBC-ORM包
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
Spring的web整合包
spring-web-4.0.0.RELEASE.jar
测试包
spring-test-4.0.0.RELEASE.jar
整合Spring和Web容器分两个步骤:
1、导入spring-web-4.0.0.RELEASE.jar
2、在web.xml配置文件中配置org.springframework.web.context.ContextLoaderListener监听器监听ServletContext的初始化
3、在web.xml配置文件中配置contextConfigLocation上下文参数。配置Spring配置文件的位置,以用于初始化Spring容器
在web.xml中配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
获取WebApplicationContext上下文对象的方法如下:
方法一(推荐):
WebApplicationContextUtils.getWebApplicationContext(getServletContext())
方法二(不推荐):
WebApplicationContext webApplicationContext= (WebApplicationContext)getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);