马士兵Spring-声明式事务管理-annotation

1.事务加在DAO层还是service层?
service中可能多涉及多种DAO的操作,比如存了一个User之后,需要保存一条日志信息;如果在DAO中分别设置事务的话,一个DAO下面方法抛出异常了,但是不会影响到另一个DAO下面的方法,这是两个事务;因此事务要加在Service层;
 
2.需求:存一个user的同时,记录一个日志,说这个user被存了;
 
3.代码实现:--这边是spring管理hibernate下的transaction;
1)DAO实现:UserDAOImpl.java  --保存User 
package com.cy.dao.impl;

import javax.annotation.Resource;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Component;

import com.cy.dao.UserDAO;
import com.cy.model.User;

@Component
public class UserDAOImpl implements UserDAO {
    
    @Resource
    private SessionFactory sessionFactory;
    
    //保存User
    public void save(User user) {
        Session s = sessionFactory.getCurrentSession();
        s.save(user);
    }

}
View Code

2)DAO实现:LogDAOImpl.java  --保存Log

package com.cy.dao.impl;

import javax.annotation.Resource;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Component;

import com.cy.dao.LogDAO;
import com.cy.model.Log;

@Component("logDAO") 
public class LogDAOImpl implements LogDAO {
    
    @Resource
    private SessionFactory sessionFactory;
    
    //保存日志信息
    public void save(Log log) {
        Session s = sessionFactory.getCurrentSession();
        s.save(log);
        throw new RuntimeException("error!");
    }

}
View Code

3)User实体 和 Log实体:

User.java:

package com.cy.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;


@Entity
public class User {
    private int id;
    private String username;
    private String password;
    
    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    
}
View Code

Log.java:

package com.cy.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 
 * @author chengyu
 * 表名和我们的类名不一样,使用@Table(name="xxx");
 */
@Entity
@Table(name="t_log")
public class Log {
    private int id;
    private String msg;
    
    @Id
    @GeneratedValue
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
}
View Code

4)UserService中保存User后,插入一条日志记录:

package com.cy.service;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.cy.dao.LogDAO;
import com.cy.dao.UserDAO;
import com.cy.model.Log;
import com.cy.model.User;

@Component("userService")
public class UserService {
    
    @Resource
    private UserDAO userDAO;  
    
    @Resource
    private LogDAO logDAO;
    
    public void init() {
        System.out.println("init");
    }
    
    /**
     * 插入一个用户的同时,记录一条日志
     * @Transactional  默认情况:如果catch到任何的RuntimeException,自动回滚
     */
    @Transactional
    public void add(User user) {
        userDAO.save(user);
        Log log = new Log();
        log.setMsg("a user saved!");
        logDAO.save(log);
    }
    
    public UserDAO getUserDAO() {
        return userDAO;
    }
    
    public void setUserDAO( UserDAO userDAO) {
        this.userDAO = userDAO;
    }
    
    public void destroy() {
        System.out.println("destroy");
    }
}

5)beans.xml中的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
           
    <context:annotation-config />
    <context:component-scan base-package="com.cy"/>
    
    <!-- 如果下面使用到了占位符的方式,请在locations下面去找 -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <value>classpath:jdbc.properties</value>
        </property>
    </bean>

    <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean> 
    
    <!-- 初始化SessionFactory 
        annotatedClasses :  接收对那些类做了注解
    -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="annotatedClasses">
            <list>
                <value>com.cy.model.User</value>
                <value>com.cy.model.Log</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
            </props>
        </property>
    </bean>
    
    <bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
          <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <tx:annotation-driven transaction-manager="txManager"/>
    
</beans>

6)测试代码:

package com.cy.service;

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

import com.cy.model.User;

public class UserServiceTest {
    
    @Test
    public void testAdd() throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        UserService service = (UserService)ctx.getBean("userService");
        System.out.println(service.getClass());
        service.add(new User());
        ctx.destroy();
    }
}
View Code

测试结果:

因为在LogDAOImpl中插入日志时,throw new RuntimeException("error!");

User和Log都插入失败,User插入后回滚了;

 

 

4. @Transactional settings的配置:

事务的传播特性:在当前的执行环境里头,到底有没有事务,另外根据这个事务的配置情况执行到我这个当前方法的时候,当前这个方法怎么样来处理这个事务;
1.propagation:  transaction的产生过程;事务的传播方式;
REQUIRED:@Transactional注解的默认值;   如果有就使用原来的,如果没有就创建一个新的;@Transactional(propagation=Propagation.REQUIRED)
MANDATORY: 强制的,必须的;当前这个方法要执行,必须得有一个Transaction;如果没有transaction会抛异常;
        比如UserService中的add方法上面@Transactional(propagation=Propagation.MANDATORY),那么测试方法中调用此add方法之前,就必须得有一个transaction;
NESTED:在一个内嵌的transaction里面执行;
      原来有一个transaction了,相当于在这个里面内嵌了一个transaction,不在原来的transaction里面执行了,而是在内嵌的transaction里面执行, 内部的transaction回滚的时候不会影响外面的
        transaction;
NEVER: 我这个方法要执行,必须不能有事务;要是有事务,抛异常;
NOT_SUPPORTED: 这个方法要想执行必须不能有事务;要是有事务把这个事务挂起,执行完我这个方法,原来的事务再继续;
REQUIRES_NEW:创建一新的transaction,如果当前有transaction,将此transaction挂起;
SUPPORTS:  支持当前的transaction,要是有我就有,要是没有我也就没有,就不在事务里执行; 

2.isolation: 隔离级别;
3.readOnly: 如果设置为true,在transaction里面没有update。insert、delete这方面操作;比如上面的例子,add方法中要设置为@Transactional(readOnly=true)的话,就会报错:
             Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed;
             如果确认没有写的操作,可以设置为true,这时候spring会使用只读的Connection,readOnly的connection比读写的Connection执行效率要高;可以提高性能;
             再者,如果这个方法里面规定只能读,那么可以设置为true,防止别人进行更改操作;
4.timeout:  一个事务如果时间太长的话,就把它终止掉;
5.rollbackFor: 默认情况下,RuntimeException会导致回滚;HibernateException本身就是一种运行期异常;
                可以在rollbackFor里面写清楚,什么情况下会回滚,比如写自己定义的MyException,如果抛出MyException,就会回滚;
6.noRollbackFor:  在某些异常下不会回滚,其他的会回滚;    
 
传播行为 说明(通俗版) 企业实战场景
REQUIRED(默认) 如果当前有事务,就加入当前事务;如果当前没有事务,就新建一个事务。 最常用场景:下单、支付、新增数据等,确保方法在一个事务中执行。
REQUIRES_NEW 无论当前是否有事务,都新建一个独立的事务;原有事务会被挂起,新事务执行完成后,再恢复原有事务。 日志记录、消息发送等:即使主事务回滚,日志也必须保存(比如下单失败,也要记录失败日志)。
NESTED 如果当前有事务,就在当前事务中嵌套一个子事务;子事务回滚不会影响主事务,但主事务回滚会带动子事务回滚。 复杂业务拆分:比如下单时,先扣库存(子事务),再生成订单(主事务),扣库存失败回滚,不影响主事务后续操作。
MANDATORY 必须在一个已存在的事务中执行,否则抛出异常(NoTransactionException)。 依赖主事务的子方法:比如订单支付后,修改订单状态,必须在支付事务中执行,不能单独执行。
NEVER 必须在没有事务的环境中执行,否则抛出异常。 无需事务的方法:比如查询数据、获取字典信息,不允许有事务(避免占用数据库连接)。
 
重点提醒:默认传播行为是 REQUIRED,很多事务失效问题,都是因为传播行为配置不当(比如子方法用了 REQUIRES_NEW,主事务回滚后,子事务没有回滚)。
 
 
 
1)required图解:

在执行了method1,method1里面调用了method2,如果说在method1里面已经有transaction了,那么method2里面就不需要再创建新的transaction了;

 

2)requires_new图解:

image

PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, always uses an independent physical transaction for each affected transaction scope, never participating in an existing transaction for an outer scope. In such an arrangement, the underlying resource transactions are different and, hence, can commit or roll back independently, with an outer transaction not affected by an inner transaction’s rollback status and with an inner transaction’s locks released immediately after its completion. Such an independent inner transaction can also declare its own isolation level, timeout, and read-only settings and not inherit an outer transaction’s characteristics.

 

 

 

---------------------

posted on 2017-05-22 23:14  有点懒惰的大青年  阅读(310)  评论(0)    收藏  举报