Spring笔记本(4)

声明式事务

事务分为声明式和编程式两种:

声明式事务:声明式事务是指通过注解的形式对事务的各种特性进行控制和管理。

编程式事务:指的是通过编码的方式实现事务的声明。

编码方式实现事务:

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();

   }

}

 

运行结果:

Spring笔记本(4)
 

异常的演示

可以在两个操作中添加一个产生异常的代码。

@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));

   }

}

 

Spring笔记本(4)

Spring笔记本(4)

 

这个时候,我们发现。其实前面的两个dao操作,其实是两个事务。

 

      bookDao.update(new Book(2, "xxx", 2222));      这是一个事务

int 12 / 0;                                   产生了错误,导致后面的语句没有执行

      userDao.update(new User(2, "ssssssss", 2222));这是另一个事务

 

 

这个时候,我们需要使用声明式事务来控制多个操作,在一个业务中,要么都成功。要么都失败!

 

 

Spring事务引入的分析------PlatformTransactionManager类简单介绍

 

Spring笔记本(4)

PlatformTransactionManager类是Spring提供的专门用于统一管理事务的一个接口。

那么不同的持久层技术都对其实现了自己的事务管理类。

Spring笔记本(4)

使用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 表示只支持读操作。

Spring笔记本(4)

声明事务代码

   /**

    * readOnly表示事务只读,不支持写操作。

    */

   @Transactional(readOnly=true)

   publicvoid updateUserAndBook() {

      // 更新图书

      bookDao.update(new Book(2, "xxx", 2222));

      // 更新用户

      userDao.update(new User(2, "ssssssss", 2222));

   }

 

在写操作的方法上标注为只读。就会报异常


Spring笔记本(4)

 

事务超时属性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));

   }

 

 

当两秒钟时间内不能完成所有操作,就会抛异常。


Spring笔记本(4)

 

事务隔离级别

四种事务隔离级别:

一:读未提交         read uncommitted

二:读已提交         read committed

三:可重复读         repeatable read

四:串行事务         serializable

 

由事务隔离级别产生的几个常见问题:

读未提交,可导致----->>>> 脏读

读已提交,可导致----->>>> 不可重复读

重复读,可导致     ----->>>> 幻读

 

脏读:所谓脏读,指的是,在某一个事务中,读取到了其他事务中未提交的数据。称之为脏读。

 

不可重复读:指的是一个事务中,多次读取,由于其他事务进行修改操作。导致两次得到的内容不同。称之为不可重复读。

 

幻读:指的是在一个事务中,每次读取的数据都跟第一次的数据相同,不管数据本身是否已经被其他事务所修改。称之为幻读。

 

 

1.查看当前会话隔离级别

[email protected]@tx_isolation;

 

2.查看系统当前隔离级别

[email protected]@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;


脏读演示

 

Spring笔记本(4) 

读未提交演示:

Spring笔记本(4)

幻读的演示

Spring笔记本(4)
 

串行化事务的演示

Spring笔记本(4)

事务的传播特性propagation

什么是事务的传播行为:

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

 

事务的传播特性,有以下几种类型:

Spring笔记本(4)
 

注解演示事物传播特性

创建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() {

Spring笔记本(4)
 

当所有的操作都是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()

 

Spring笔记本(4)
 

Spring笔记本(4)

Spring笔记本(4)

Spring笔记本(4)

 

Spring笔记本(4)

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()

 

Spring笔记本(4)

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);