数据库事务 及 Spring 对 Hibernate 事务管理

数据库事务概念: 

    百度百科:数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完整地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。

数据库事务是作用:

    它确保了数据库的数据一致性和安全性,尤其在对数据执行增删时,如果发生异常和错误它就会触发事务回滚,从而确保了我们数据的一致性和安全性。


数据库事务相关资料:
    http://www.cnblogs.com/rush/archive/2011/12/11/2284262.html
Spring控制事务:

    网上很多Spring关于事务配置的,我的配置是:

applicationContext.xml文件中

<!-- 数据库配置文件位置 -->
    <context:property-placeholder location="classpath:profile/db_config.properties" />
    <!-- 配置dbcp数据源 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <!--这些数据库的连接参数(name值)与原来的不大一样 -->
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${jdbc.maxActive}" />
        <property name="minIdle" value="${jdbc.minIdle}" />
        <property name="validationQuery" value="${jdbc.validationQuery}" />
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
    </bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource">
            <ref bean="dataSource" />
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                <prop key="hibernate.cache.provider_class">${hibernate.cache.provider_class}</prop>
                <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
                <!--Hibernate4.0 的查询翻译器-->
                <prop key="hibernate.query.factory_class">${hibernate.query.factory_class}</prop>
                <!-- <prop key="current_session_context_class">thread</prop> --> 
            </props>
        </property>
        <!-- 可用mappingLocations 或者 mappingResources配置扫描-->
        <property name="mappingLocations">
            <list>
                <value>
                    classpath:com/gooxia/main/model/mapping/*.hbm.xml
                </value>
            </list>
        </property>
    </bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    <!-- 方式(1)使用annotation注解方式配置事务 -->
    <!-- <tx:annotation-driven transaction-manager="transactionManager" /> -->
    <!-- 方式(2)声明式事务,不用在每个类的方法上面写注解,以一个全局的规则匹配 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="find*" propagation="REQUIRED"/>
            <tx:method name="load*" propagation="REQUIRED"/>
            <tx:method name="get*"  propagation="REQUIRED"/>
            <tx:method name="*" rollback-for="Throwable" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <aop:aspectj-autoproxy/>

    <!-- 切入点 -->
    <aop:config>
        <!-- 对main业务逻辑层控制事务 -->
        <aop:pointcut id="mainServiceMethodsPointcut"
            expression="execution(* com.<span style="font-family: Arial, Helvetica, sans-serif;">gooxia</span><span style="font-family: Arial, Helvetica, sans-serif;">.main.service.*.*(..))"/></span>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="mainServiceMethodsPointcut"/>
    </aop:config>
其中db_config.properties文件配置为:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc\:mysql\://127.0.0.1\:3306/gooxia?useUnicode\=true&characterEncoding\=UTF-8


jdbc.username=root
jdbc.password=
jdbc.maxActive=100
jdbc.minIdle=5
jdbc.validationQuery=SELECT 1 from dual
jdbc.testOnBorrow=true


hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
hibernate.cache.provider_class=org.hibernate.cache.NoCacheProvider
hibernate.query.factory_class=org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory
hibernate.cache.use_second_level_cache=true
这些是Spring和Hibernate整合配置文件中的主要配置。

这些都不是重点。重点在于下面的过程:

第一、简单介绍一下我的测试环境:

Mysql数据库中两张表gooxia_admins和gooxia_users,一张是MyISAM引擎,一张是InnoDB引擎,如图:


每个表都只有id和name两个字段。

第二、项目采用Spring3.2.0 +Hibernate4.2.8

第三、项目中两个实体类Admin 和Users

由于我们的事务是控制在service层的方法上。我们可以简单测试一下自己的配置是否有错误。

Spring 管理事务时session 是绑定到ThreadLocal的来确保事务的执行,根据这个特点就可以测试。

BaseDAO类

public class BaseDAO<T> implements DAO<T> {
    @Autowired
    protected SessionFactory sessionFactory;
    @Autowired
    private MysqlJdbcTemplate jdbcTemplate;
    /**
     * T泛型类
     */
    protected Class<?> entityClass;
    /**
     * T泛型类字段
     */
    protected Field[] fields;
    
    public BaseDAO(){
        this.entityClass = (Class<?>)((ParameterizedType)getClass().getGenericSuperclass())
                .getActualTypeArguments()[0];
        this.fields = this.entityClass.getDeclaredFields();
    }
    public Session getCurrentSession(){
        System.out.println(entityClass.getName()+" session hashCode:"+sessionFactory.getCurrentSession().hashCode());
        return sessionFactory.getCurrentSession();
    }
    /**
     * 保存实体对象
     */
    public void save(T t){
        this.getCurrentSession().save(t);
    }
}
在每次获取到绑定在ThreadLocal的session的时候,输出获取得到session的hashCode();

UserServiceImpl实现类

public class UserServiceImpl extends BaseServiceImpl<User,UserDAO> implements UserService{
    @Autowired
    private AdminDAO adminDAO;
    public void testTransactionManager() throws Throwable{
        Admin admin = new Admin();
        admin.setName("test");
        this.adminDAO.save(admin);
        User user = new User();
        user.setName("test");
        this.dao.save(user);
//        throw new Error("测试事务");
    }
}
Service类中两次调用DAO,就会两次获取session
IndexController类

@Controller
public class IndexController {
    @Autowired
    private UserService userService;
    /**
     * 匹配控制器匹配不了的路径
     * 显示404页面
     */
    @RequestMapping(value="/**/*")
    public String notFound(){
        return "404";
    }
    @RequestMapping(value="/test")
    public @ResponseBody String test(){
        try {
            this.userService.testTransactionManager();
        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "test";
    }
}
测试结果:

可以看到两次获取到的hashCode是一样的。说明service中两次数据库操作是在同一个session中。

此时两张表中各一条记录。

测试事务:

清空表数据。将Service中testTransactionManager 的

throw new Error("测试事务");

注释去掉,使得在方法执行的最后抛出错误,看看事务是否有回滚。

最后抛出的异常。

再看看数据库


发现事务回滚失败了。因为service的这个方法一半操作数据库成功了,而另一半操作失败了。这并不是数据库事务控制想要的结果。

再来一次,我们在程序运行的时候取断点,看看怎么回事。

到这里保存了admin,此时数据库gooxia_admins表情况:


发现admin已经插入到数据库中了。

再往下

此时保存user,看数据库gooxia_user表情况:


发现user信息并没有插入。
抛出异常,全部运行完成后。就出现了以上的情况,一张表插入成功,一张表插入失败(回滚了)。

下面介绍如何真正正确的控制事务。

将gooxia_admins表引擎也改成InnoDB,同时清空表。


再次测试

//        throw new Error("测试事务");
注释错误抛出,运行结果。

插入正常。

现在抛出错误运行结果:


发现两个表中数据都没有插入。说明事务控制成功。

大家都知道Mysql数据库MyISAM和InnoDB是最常用的表引擎。MyISAM不支持事务,而InnoDB支持事务,所以出现这样的结果。

总结说明:

这说明要Spring控制事务,首先就必须数据库支持事务。Spring利用AOP在配置的service方法前后加上事务控制,从表面上完全不用写事务控制,这就是AOP的魅力。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值