代码改变世界

JavaEE学习之Spring声明式事务

2014-08-30 23:51  ps_zw  阅读(1970)  评论(0编辑  收藏  举报

一、引言

上一篇文章,学习了AOP相关知识,并做了一个简单的Hello world。本文在上篇文章的基础上,进一步学习下Spring的声明式事务。

二、相关概念

1. 事务(Transaction)——它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位

2. 事务的几大特性(A、C、I、D):

  A——Atomicity(原子性)。数据库中的事务执行是作为原子。即不可再分,整个语句要么执行,要么不执行。

  C——Consistency(一致性)。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  I——Isolation(隔离性)。事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。

  D——Durability(持久性)。事务完成后,对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

3.spring中事务有以下5中属性:

  (1)传播行为。即事务的传递,例如:

     PROPAGATION_MANDATORY:该方法必须运行在一个事务中。如果当前事务不存在则抛出异常。

     PROPAGATION_NEVER:当前方法不应该运行在一个事务中。如果当前存在一个事务,则抛出异常.

     PROPAGATION_REQUIRED:该方法必须运行在一个事务中。如果一个事务正在运行,该方法将运行在这个事务中。否则,就开始一个新的事务。

     ...

  (2)隔离级别。多个事务并发执行,会产生脏读,不可重复读,幻读等问题,为避免这些问题,常常会设置隔离级别

  (3)只读。主要用于优化

  (4)超时。释放资源

  (5)回滚规则。

4.多个事务并发运行,产生的问题:

  (1)脏读(Dirty read)。当事务读取还未被提交的数据时。(读未提交)

  (2)不可重复读(Nonrepeatable read)。同一个事务中两次读取同一数据,每次得到的数据都不一样。(读不回去,其他事务是更新操作)

  (3)幻读(Phantom read)。一个事务在进行相同条件的两次或两次以上查询,结果在稍后的相同条件的查询中读取的结果不一样,会发现原来没有的记录。(读多了,其他事务是插入操作)

5.隔离级别:

  (1)读未提交(Read Uncommitted,ansi sql值:1 ).允许脏读取,但不允许更新丢失。

  (2)读已提交(Read Committed,ansi sql值:2 ).允许不可重复度,但不允许脏读取。

  (3)可以重复读(Repeatable Read,ansi sql值:4 ).禁止不可重复读和脏读取,但是有时可能出现幻影数据。

  (4)串行化(Serializable,ansi sql值:8 ).提供严格的事务隔离。它要求事务序列化执行,不支持并发。

6.spring的事务机制主要有两种:编程式事务和声明式事务。

  编程式事务:所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理。

  声明式事务:在Spring中,主要是通过AOP来完成声明式的事务管理。

三、实例

1.建库建表。使用MySQL新建TEST库,然后新建CUSTOMER表。脚本如下:

1 CREATE TABLE `customer` (
2   `RECID` int(10) NOT NULL AUTO_INCREMENT,
3   `NAME` varchar(100) NOT NULL,
4   `AGE` int(3) NOT NULL,
5   PRIMARY KEY (`RECID`)
6 )

 

2.新建项目,并引入相关jar包

项目结构:

pom.xml:

  1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3     <modelVersion>4.0.0</modelVersion>
  4     <groupId>com.wzhang</groupId>
  5     <artifactId>spring-demo</artifactId>
  6     <packaging>war</packaging>
  7     <version>0.0.1-SNAPSHOT</version>
  8     <name>spring-demo Maven Webapp</name>
  9     <url>http://maven.apache.org</url>
 10     <properties>
 11         <junit.version>3.8.1</junit.version>
 12         <log4j.version>1.2.17</log4j.version>
 13         <javax.servlet.version>2.5</javax.servlet.version>
 14         <org.hibernate.version>4.1.7.Final</org.hibernate.version>
 15         <org.springframework.version>3.2.3.RELEASE</org.springframework.version>
 16         <org.apache.struts.version>2.3.16.3</org.apache.struts.version>
 17     </properties>
 18     <dependencies>
 19         <dependency>
 20             <groupId>junit</groupId>
 21             <artifactId>junit</artifactId>
 22             <version>${junit.version}</version>
 23             <scope>test</scope>
 24         </dependency>
 25         <dependency>
 26             <groupId>javax.servlet</groupId>
 27             <artifactId>servlet-api</artifactId>
 28             <version>${javax.servlet.version}</version>
 29         </dependency>
 30         <!-- Spring -->
 31         <dependency>
 32             <groupId>org.springframework</groupId>
 33             <artifactId>spring-beans</artifactId>
 34             <version>${org.springframework.version}</version>
 35         </dependency>
 36         <dependency>
 37             <groupId>org.springframework</groupId>
 38             <artifactId>spring-context</artifactId>
 39             <version>${org.springframework.version}</version>
 40         </dependency>
 41         <dependency>
 42             <groupId>org.springframework</groupId>
 43             <artifactId>spring-core</artifactId>
 44             <version>${org.springframework.version}</version>
 45         </dependency>
 46 
 47         <dependency>
 48             <groupId>org.springframework</groupId>
 49             <artifactId>spring-web</artifactId>
 50             <version>${org.springframework.version}</version>
 51         </dependency>
 52         <dependency>
 53             <groupId>org.springframework</groupId>
 54             <artifactId>spring-expression</artifactId>
 55             <version>${org.springframework.version}</version>
 56         </dependency>
 57         <dependency>
 58             <groupId>org.springframework</groupId>
 59             <artifactId>spring-aop</artifactId>
 60             <version>${org.springframework.version}</version>
 61         </dependency>
 62         <dependency>
 63             <groupId>org.springframework</groupId>
 64             <artifactId>spring-orm</artifactId>
 65             <version>${org.springframework.version}</version>
 66         </dependency>        
 67         <dependency>
 68             <groupId>org.springframework</groupId>
 69             <artifactId>spring-tx</artifactId>
 70             <version>${org.springframework.version}</version>
 71         </dependency>
 72         
 73         <!-- Hibernate -->
 74         <dependency>
 75             <groupId>org.hibernate</groupId>
 76             <artifactId>hibernate-core</artifactId>
 77             <version>${org.hibernate.version}</version>
 78         </dependency>
 79         
 80         <!-- Log4j -->
 81         <dependency>
 82             <groupId>log4j</groupId>
 83             <artifactId>log4j</artifactId>
 84             <version>${log4j.version}</version>
 85         </dependency>
 86         
 87         <!-- MySQL -->
 88         <dependency>
 89             <groupId>mysql</groupId>
 90             <artifactId>mysql-connector-java</artifactId>
 91             <version>5.1.25</version>
 92         </dependency>
 93         
 94         <!-- AOP -->
 95         <dependency>
 96             <groupId>aopalliance</groupId>
 97             <artifactId>aopalliance</artifactId>
 98             <version>1.0</version>
 99         </dependency>
100         <dependency>
101             <groupId>org.aspectj</groupId>
102             <artifactId>aspectjweaver</artifactId>
103             <version>1.7.2</version>
104         </dependency>
105         
106         <!-- annotation -->
107         <dependency>
108             <groupId>javax.annotation</groupId>
109             <artifactId>jsr250-api</artifactId>
110             <version>1.0</version>
111         </dependency>
112     </dependencies>
113     <build>
114         <finalName>spring-demo</finalName>
115     </build>
116 </project>
Pom.xml

 

2.编写DAO接口以及实现类

(1)一般我们都会给我们的dao建一个基类,以封装一些通用的方法和sessionFactory。我这里也在网上找了一个别人写好的基类,代码如下:

  1 /**************IGenericDao<T>接口********************/
  2 package com.wzhang.springdemo.dao;
  3 
  4 import java.util.List;
  5 
  6 public interface IGenericDao<T> {
  7     
  8     void insert(T t);
  9 
 10     void delete(T t);
 11 
 12     void update(T t);
 13 
 14     T queryById(String id);
 15 
 16     List<T> queryAll();
 17 }
 18 
 19 /******************实现类**********************/
 20 
 21 package com.wzhang.springdemo.dao;
 22 
 23 import java.util.List;
 24 
 25 import org.hibernate.Query;
 26 import org.hibernate.SessionFactory;
 27 import org.springframework.beans.factory.annotation.Autowired;
 28 
 29 public abstract class GenericDao<T> implements IGenericDao<T> {
 30 
 31     private Class<T> entityClass;
 32 
 33     public GenericDao(Class<T> clazz) {
 34         this.entityClass = clazz;
 35     }
 36 
 37     /**
 38      * 使用注解方式,注入SessionFactory
 39      */
 40     @Autowired
 41     private SessionFactory sessionFactory;
 42 
 43     public void insert(T t) {
 44         sessionFactory.getCurrentSession().save(t);
 45     }
 46 
 47     public void delete(T t) {
 48         sessionFactory.getCurrentSession().delete(t);
 49     }
 50 
 51     public void update(T t) {
 52         sessionFactory.getCurrentSession().update(t);
 53     }
 54 
 55     
 56     @SuppressWarnings("unchecked")
 57     public T queryById(String id) {
 58         return (T) sessionFactory.getCurrentSession().get(entityClass, id);
 59     }
 60 
 61     public List<T> queryAll() {
 62         String hql = "from " + entityClass.getSimpleName();
 63         return queryForList(hql, null);
 64     }
 65 
 66     @SuppressWarnings("unchecked")
 67     protected T queryForObject(String hql, Object[] params) {
 68         Query query = sessionFactory.getCurrentSession().createQuery(hql);
 69         setQueryParams(query, params);
 70         return (T) query.uniqueResult();
 71     }
 72 
 73     @SuppressWarnings("unchecked")
 74     protected T queryForTopObject(String hql, Object[] params) {
 75         Query query = sessionFactory.getCurrentSession().createQuery(hql);
 76         setQueryParams(query, params);
 77         return (T) query.setFirstResult(0).setMaxResults(1).uniqueResult();
 78     }
 79 
 80     @SuppressWarnings("unchecked")
 81     protected List<T> queryForList(String hql, Object[] params) {
 82         Query query = sessionFactory.getCurrentSession().createQuery(hql);
 83         setQueryParams(query, params);
 84         return query.list();
 85     }
 86 
 87     /**
 88      * 
 89      * @param hql
 90      * @param params
 91      * @param recordNum
 92      * @return
 93      */
 94     @SuppressWarnings("unchecked")
 95     protected List<T> queryForList(final String hql, final Object[] params,
 96             final int recordNum) {
 97         Query query = sessionFactory.getCurrentSession().createQuery(hql);
 98         setQueryParams(query, params);
 99         return query.setFirstResult(0).setMaxResults(recordNum).list();
100     }
101 
102     /**
103      * 
104      * @param query
105      * @param params
106      */
107     private void setQueryParams(Query query, Object[] params) {
108         if (null == params) {
109             return;
110         }
111         for (int i = 0; i < params.length; i++) {
112             query.setParameter(i, params[i]);
113         }
114     }
115 
116 }
IGenericDao

(2)完成CustomerDao和CustomerDaoImpl.代码如下:

 1 /****************接口*******************/
 2 
 3 package com.wzhang.springdemo.dao;
 4 
 5 import com.wzhang.springdemo.domain.Customer;
 6 
 7 /**
 8  * CustomerDao
 9  * @author wzhang
10  *
11  */
12 public interface CustomerDao extends IGenericDao<Customer> {
13     /**
14      * 保存客户
15      * @param cus
16      * @return 成功返回0,失败返回-1
17      */
18     public int saveCustomer(Customer cus);
19 }
20 
21 
22 /***************实现类****************/
23 
24 package com.wzhang.springdemo.dao;
25 
26 import org.springframework.stereotype.Service;
27 
28 import com.wzhang.springdemo.domain.Customer;
29 
30 /**
31  * CustomerDao实现类
32  * @author wzhang
33  *
34  */
35 @Service("customerDao")
36 public class CustomerDaoImpl extends GenericDao<Customer> implements
37         CustomerDao {
38 
39     public CustomerDaoImpl() {
40         super(Customer.class);
41     }
42 
43     public int saveCustomer(Customer cus) {
44         try {
45             this.insert(cus);
46             return 0;
47         } catch (Exception e) {
48             throw new RuntimeException(e);
49         }
50     }
51 
52 }

 

3.按一般项目惯例,我们这里也搞一个service层,同时事务管理也在service层。

 1 /***************CustomerService接口****************/
 2 
 3 package com.wzhang.springdemo.service;
 4 
 5 import java.util.List;
 6 
 7 import com.wzhang.springdemo.domain.Customer;
 8 
 9 /**
10  * CustomerService
11  * @author wzhang
12  *
13  */
14 public interface CustomerService {
15     /**
16      * 批量保存
17      * @param customers
18      * @return
19      */
20     int saveCustomers(List<Customer> customers);
21 }
22 
23 /******************实现类***********************/
24 
25 package com.wzhang.springdemo.service.impl;
26 
27 import java.util.List;
28 
29 import javax.annotation.Resource;
30 
31 import org.springframework.stereotype.Service;
32 
33 import com.wzhang.springdemo.dao.CustomerDao;
34 import com.wzhang.springdemo.domain.Customer;
35 import com.wzhang.springdemo.service.CustomerService;
36 
37 @Service("customerService")
38 public class CustomerServiceImpl implements CustomerService {
39 
40     /**
41      * 注入dao
42      */
43     @Resource(name = "customerDao")
44     private CustomerDao dao;
45 
46     public int saveCustomers(List<Customer> customers) {
47         try {
48             for (Customer cus : customers) {
49                 dao.saveCustomer(cus);
50             }
51         } catch (Exception e) {
52             throw new RuntimeException(e);
53         }
54         return 0;
55     }
56 }

 

4.配置applicationContext.xml

 Spring声明式事务的配置主要有以下几部分内容:

(1)配置数据源dataSource.配置数据源时通常会使用分散配置,将具体的数据库驱动,url等信息配置在一个独立的properties文件中。

   在spring配置文件(applicationContext.xml)中使用<context:property-placeholder location="..." />引入properties资源。

db.properties(路径:/resource/properties/db.properties)

1 jdbc.driverclass=com.mysql.jdbc.Driver
2 jdbc.url=jdbc:mysql://192.168.1.107:3306/test
3 jdbc.username=wzhang
4 jdbc.password=wzhang
5 
6 hibernate.dialect=org.hibernate.dialect.HSQLDialect
7 hibernate.show_sql=true
8 hibernate.hbm2ddl.auto=none

applicationContex中dataSource配置:  

 1     <!-- 配置DataSource -->
 2     <bean id="dataSource"
 3         class="org.springframework.jdbc.datasource.DriverManagerDataSource">
 4         <property name="driverClassName">
 5             <value>${jdbc.driverclass}</value>
 6         </property>
 7         <property name="url">
 8             <value>${jdbc.url}</value>
 9         </property>
10         <property name="username">
11             <value>${jdbc.username}</value>
12         </property>
13         <property name="password">
14             <value>${jdbc.password}</value>
15         </property>
16     </bean>

 

(2)配置会话工厂SessoryFactory.需要注入dataSource.

    <!-- 配置SessoryFactory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
            </props>
        </property>
        <!-- 基于Hibernate注解,扫描给定包 -->
        <property name="packagesToScan" value="com.wzhang.springdemo.domain"></property>
        
        
        <!-- 基于*.hbm.xml映射文件  -->
        <!--     
        <property name="mappingResources">
        <list>
        <value>com/wzhang/springdemo/domain/customer.hbm.xml</value>
        </list>
        </property> 
        -->
    </bean>

  

(3)配置事务管理器TransactionManager.需要注入sessionFactory.

1     <!-- TransactionManager 配置事务管理器 -->
2     <bean id="transactionManager"
3         class="org.springframework.orm.hibernate4.HibernateTransactionManager">
4         <property name="sessionFactory" ref="sessionFactory" />
5     </bean>

 

(4)配置事务通知(传播行为,隔离级别,只读,超时,回滚规则等).节点<tx:advice></tx:advice>,需要配置spring-tx-3.x相关的scheme。

 1     <!-- 配置事务属性,传播特性,隔离级别 -->
 2     <tx:advice id="txAdvice" transaction-manager="transactionManager">
 3         <!-- 指定具体需要拦截的方法 -->
 4         <tx:attributes>
 5             <!-- 拦截do开头,save开头的方法,事务传播行为是required 隔离级别为default -->
 6             <tx:method name="do*" propagation="REQUIRED" isolation="DEFAULT" />
 7             <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" />
 8             <!-- 除上述定义之外的方法,事务属性为只读  -->
 9             <tx:method name="*" read-only="true" />
10         </tx:attributes>
11     </tx:advice>

 

(5)配置切面(切入点和切入点通知).节点<aop:config></aop:config>.需要配置spring-aop相关的scheme。

1     <!-- 配置切入点 -->
2     <aop:config>
3         <!-- 切入点,拦截*service接口及其实现类 -->
4         <aop:pointcut expression="execution(* com.wzhang.springdemo.service.*Service.*(..))"
5             id="serviceMethod" />
6         <!-- 切入点通知 -->
7         <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
8     </aop:config>

applicationContext.xml:(路径:/resource/spring/applicationContext.xml)

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
 4     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
 5     xsi:schemaLocation="http://www.springframework.org/schema/beans 
 6     http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
 7     http://www.springframework.org/schema/context     
 8     http://www.springframework.org/schema/context/spring-context-3.2.xsd
 9     http://www.springframework.org/schema/tx 
10     http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
11     http://www.springframework.org/schema/aop 
12     http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
13 
14     <!-- 分散配置 -->
15     <context:property-placeholder location="classpath:properties/db.properties" />
16 
17     <!-- 扫描包 -->
18     <context:component-scan base-package="com.wzhang" />
19 
20     <!-- 激活注解 -->
21     <context:annotation-config />
22     
23     <!-- 这是基于注解的方式配置事务 -->
24     <tx:annotation-driven transaction-manager="transactionManager" />
25 
26     <!-- 配置DataSource -->
27     <bean id="dataSource"
28         class="org.springframework.jdbc.datasource.DriverManagerDataSource">
29         <property name="driverClassName">
30             <value>${jdbc.driverclass}</value>
31         </property>
32         <property name="url">
33             <value>${jdbc.url}</value>
34         </property>
35         <property name="username">
36             <value>${jdbc.username}</value>
37         </property>
38         <property name="password">
39             <value>${jdbc.password}</value>
40         </property>
41     </bean>
42 
43     <!-- 配置SessoryFactory -->
44     <bean id="sessionFactory"
45         class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
46         <property name="dataSource" ref="dataSource" />
47         <property name="hibernateProperties">
48             <props>
49                 <prop key="hibernate.dialect">${hibernate.dialect}</prop>
50                 <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
51                 <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
52             </props>
53         </property>
54         <!-- 基于Hibernate注解,扫描给定包 -->
55         <property name="packagesToScan" value="com.wzhang.springdemo.domain"></property>
56         
57         
58         <!-- 基于*.hbm.xml映射文件  -->
59         <!--     
60         <property name="mappingResources">
61         <list>
62         <value>com/wzhang/springdemo/domain/customer.hbm.xml</value>
63         </list>
64         </property> 
65         -->
66     </bean>
67 
68     <!-- TransactionManager 配置事务管理器 -->
69     <bean id="transactionManager"
70         class="org.springframework.orm.hibernate4.HibernateTransactionManager">
71         <property name="sessionFactory" ref="sessionFactory" />
72     </bean>
73 
74     <!-- 配置事务属性,传播特性,隔离级别 -->
75     <tx:advice id="txAdvice" transaction-manager="transactionManager">
76         <!-- 指定具体需要拦截的方法 -->
77         <tx:attributes>
78             <!-- 拦截do开头,save开头的方法,事务传播行为是required 隔离级别为default -->
79             <tx:method name="do*" propagation="REQUIRED" isolation="DEFAULT" />
80             <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" />
81             <!-- 除上述定义之外的方法,事务属性为只读  -->
82             <tx:method name="*" read-only="true" />
83         </tx:attributes>
84     </tx:advice>
85 
86     <!-- 配置切入点 -->
87     <aop:config>
88         <!-- 切入点,拦截*service接口及其实现类 -->
89         <aop:pointcut expression="execution(* com.wzhang.springdemo.service.*Service.*(..))"
90             id="serviceMethod" />
91         <!-- 切入点通知 -->
92         <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
93     </aop:config>
94 </beans>
applicationContext.xml

四、测试

新建测试类HibernateTest.java:

1.测试正常保存情况

测试方法:

 1     @SuppressWarnings("resource")
 2     @Test
 3     public void bathSaveTest() {
 4         ApplicationContext ctx = new ClassPathXmlApplicationContext(
 5                 "classpath:spring/applicationContext.xml");
 6 
 7         CustomerService service = (CustomerService) ctx
 8                 .getBean("customerService");
 9         List<Customer> list = new ArrayList<Customer>();
10         for (int i = 0; i < 3; i++) {
11             Customer c = new Customer();
12             c.setAge(10 + i * 10);
13             c.setName("张三");
14             list.add(c);
15         }
16         service.saveCustomers(list);
17     }
bathSaveTest

在mysql中执行:select * from customer,查看结果如下:

说明批量保存没有问题。

2.修改Customer中的name属性,设置name不能为空(nullable=false)。新增测试方法transactionTest(),测试事务是否回滚。

测试方法:

 1     @SuppressWarnings("resource")
 2     @Test
 3     public void transationTest() {
 4         ApplicationContext ctx = new ClassPathXmlApplicationContext(
 5                 "classpath:spring/applicationContext.xml");
 6 
 7         CustomerService service = (CustomerService) ctx
 8                 .getBean("customerService");
 9         List<Customer> list = new ArrayList<Customer>();
10         for (int i = 0; i < 3; i++) {
11             Customer c = new Customer();
12             if (i == 2) {//i=2时设置name为空,测试事务是否回滚
13                 c.setAge(10 + i * 10);
14                 c.setName(null);
15             } else {
16                 c.setAge(10 + i * 10);
17                 c.setName("张三");
18             }
19             list.add(c);
20         }
21         service.saveCustomers(list);
22     }
transationTest方法

测试结果:junit测试失败,抛出异常。

在mysql中查看表customer中数据:

可以看出数据库数据并没有插入成功,全部回滚了。

其实我们还需要在service中提供一个单一保存customer的方法,然后在测试类中循环调用,看是否只有name为null的没有保存成功。我这里就不测了。。。^_^

五、总结

本文简单介绍了下spring的声明式事务的使用。

示例代码:spring-transaction.zip