【问题标题】:Using @Transactional: transaction get commited even if exception thrown使用@Transactional:即使抛出异常,事务也会被提交
【发布时间】:2013-03-14 08:29:10
【问题描述】:

我正在使用 struts2 - spring 3.2.2 和 mybatis。

首先我的要求是:

在其中制作一个事务管理实用程序

  1. 如果在当前事务的其他语句中抛出任何异常,事务必须回滚。 (其他 db 操作可以通过各种类执行,即不在同一个类中。)

按照要求,我创建了简单的程序。 我的 applicationContext.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: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.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd">

    <tx:annotation-driven transaction-manager="transactionManager"/>
        <!-- Initialization for data source -->
   <bean id="dataSource" 
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
      <property name="url" value="jdbc:sqlserver://localhost;database=master;integratedSecurity=true;"/>
      <property name="username" value="Jaydeep"/>
      <property name="password" value="Acty#System123"/>
   </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />

        <property name='mapperLocations' value='classpath*:test/xml/*.xml' />
    </bean>

    <bean class='org.mybatis.spring.mapper.MapperScannerConfigurer'>
      <property name='basePackage' value='test.dao' />
    </bean>

    <bean id='sqlSession' class='org.mybatis.spring.SqlSessionTemplate'>
      <constructor-arg index='0' ref='sqlSessionFactory' />
    </bean>

   <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

   <bean id="serviceProvider"
      class="DataServiceProvider">
      <property name="sqlSession"  ref="sqlSession" />    
   </bean>

    <bean id="updutil" class="MyUpdateUtil">
        <property name="serviceProvider" ref="serviceProvider"></property>
    </bean>

</beans>

//DataServiceProvider.java

import org.apache.ibatis.session.SqlSession;
import test.dao.DepartmentMapper;
import test.dao.EmployeeMapper;

public class DataServiceProvider {
    private SqlSession sqlSession;
    public DepartmentMapper getDeptMapper() {
        if(sqlSession != null)
            return sqlSession.getMapper(DepartmentMapper.class);
        else
            System.out.println("session null");
        return null;
    }
    public EmployeeMapper getEmpMapper() {
        if(sqlSession != null)
            return sqlSession.getMapper(EmployeeMapper.class);
        else
            System.out.println("session null");
        return null;
    }

    public SqlSession getSqlSession() {
        return sqlSession;
    }

    public void setSqlSession(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }
}

//接口:fooService.java

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import org.springframework.transaction.annotation.Transactional;
import test.model.Department;

@Transactional
public interface fooService {

    public void update(boolean isThrow) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException;

    public void insert(Department dept) throws IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException;

    public void select() throws IOException;
}

//将在其中执行所有db相关操作的实用程序类 //MyUpdateUtil.java

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import test.model.Department;
import test.model.Employee;
import test.model.EmployeeExample;

@Transactional
public class MyUpdateUtil implements fooService {

    public void update(boolean isThrow) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException  {
        System.out.println("Updating...................Transaction alive? ..." + StartTransAction.isActive());
        if(isThrow)
            throw new RuntimeException("simulate Error condition") ;
        Employee record = new Employee();
        EmployeeExample example = new EmployeeExample();
        example.createCriteria().andDeptidEqualTo(1L);

        record.setEmpid(1L);
        record.setDeptid(1L);
        record.setEmpname("jaydeep");
        record.setSalary(BigDecimal.valueOf(2000));

        getServiceProvider().getEmpMapper().updateByExampleWithBLOBs(record, example);
    }
    @Transactional
    public void insert(Department dept) throws IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        System.out.println("Inserting....................Transaction alive? .." + StartTransAction.isActive());
        getServiceProvider().getDeptMapper().insert(dept);
    }

    public void select() throws IOException {
        System.out.println("Dept Info");
        List<Department> deptList = getServiceProvider().getDeptMapper().selectByExampleWithBLOBs(null);
        for(Department d : deptList) {
            System.out.println("Dept ID: " + d.getDeptid());
            System.out.println("Dept Name: " + d.getDeptname());
        }
        System.out.println("Emp Info");
        List<Employee> empList = getServiceProvider().getEmpMapper().selectByExampleWithBLOBs(null);
        for(Employee e : empList) {
            System.out.println("Emp ID: " + e.getEmpid());
            System.out.println("Dept ID: " + e.getDeptid());
            System.out.println("Emp Name: " + e.getEmpname());
        }
    }   
    @Autowired(required=true)
    private DataServiceProvider serviceProvider;
    public DataServiceProvider getServiceProvider() {
        return serviceProvider;
    }

    public void setServiceProvider(DataServiceProvider serviceProvider) {
        this.serviceProvider = serviceProvider;
    }
}

//以及当我从 jsp 页面单击链接时执行的主要操作.... //StartTransAction.java

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import test.model.Department;

import com.opensymphony.xwork2.ActionSupport;


public class StartTransAction extends ActionSupport {
    private static final long serialVersionUID = 1L;

    public String execute() throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException {      
        Department dept = new Department();
        dept.setDeptid(Long.valueOf(String.valueOf(new Random().nextInt(500))));
        dept.setDeptname("esb");
        System.out.println("before Insert..................Transaction alive? ...." + isActive());
        try {
            updutil.insert(dept);

            System.out.println("After Insert..................Transaction alive? ...." + isActive());
            updutil.select();
            updutil.update(true);
        } catch (Exception e) {
            System.out.println(e.toString());
        }finally{
            System.out.println("After Update.................Transaction alive? ....." + isActive());
            updutil.select();
        }
        return SUCCESS;
    }
    @Autowired
    fooService updutil;

    public fooService getUpdutil() {
        return new MyUpdateUtil();
    }
    public void setUpdutil(fooService updutil) {
        this.updutil = updutil;
    }
    private DataSourceTransactionManager transactionManager;

    public DataSourceTransactionManager getTransactionManager() {
        return transactionManager;
    }
    public void setTransactionManager(
            DataSourceTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }   

    public static boolean isActive() throws IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException  {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
         Class tsmClass = contextClassLoader.loadClass("org.springframework.transaction.support.TransactionSynchronizationManager");
         Boolean isActive = (Boolean) tsmClass.getMethod("isActualTransactionActive", null).invoke(null, null);
         return isActive;
    }
}

现在当我运行程序...输出是这样的:

在插入之前........事务还活着吗? .....假
插入...........事务还活着? ....真
插入后......事务还活着? ......错误
部门信息
部门编号:1
部门名称:si
部门编号:251
部门名称:esb
部门 ID:293
部门名称:esb

企业信息
员工 ID:1
部门编号:1
企业名称:s
更新中........事务还活着? .....真的
java.lang.RuntimeException:模拟错误情况
更新后........事务还活着? .....假
部门信息
部门编号:1
部门名称:si
部门编号:251
部门名称:esb
部门 ID:293
部门名称:esb

企业信息
员工 ID:1
部门编号:1
企业名称:s


较粗的部分是插入的新记录。

抛出运行时异常后,我需要回滚插入的记录。 但是如输出所示,即使抛出异常,记录也会被提交。 如我们所见,事务也在 update() 方法中继续进行。

请帮助我实现这一目标。我尝试了很多,但没有工作。 如果可能的话,给我上述问题的工作代码....


编辑: 我完全替换了上面的代码。

现在我有以下文件:

//applicationContext.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: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.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd">

    <tx:annotation-driven transaction-manager="transactionManager"/>
       <!-- bean id="serviceProvider" class="DataServiceProvider"></bean-->
        <!-- Initialization for data source -->
   <bean id="dataSource" 
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
      <property name="url" value="jdbc:sqlserver://localhost;database=master;integratedSecurity=true;"/>
      <property name="username" value="Jaydeep"/>
      <property name="password" value="Acty#System123"/>

   </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name='mapperLocations' value='classpath*:test/xml/*.xml' />
    </bean>

    <bean class='org.mybatis.spring.mapper.MapperScannerConfigurer'>
      <property name='basePackage' value='test.dao' />
      <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>


    <bean id='sqlSession' class='org.mybatis.spring.SqlSessionTemplate'>
      <constructor-arg index='0' ref='sqlSessionFactory' />
    </bean>

   <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
        <property name="nestedTransactionAllowed" value="true" />
        <property name="validateExistingTransaction" value="true" />
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index='0' ref='sqlSessionFactory' />
    </bean>

    <bean id="myService" class="service.MyService">
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate" />
    </bean>
</beans>

//StartTransAction.java

package com.acty;
import java.lang.reflect.InvocationTargetException;
import service.MyService;

import com.opensymphony.xwork2.ActionSupport;

public class StartTransAction extends ActionSupport {
    private static final long serialVersionUID = 1L;

    public String execute(){
        myService.startOperations();
        return SUCCESS;
    }

    public static boolean isActive() {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
         Class tsmClass = null;
        try {
            tsmClass = contextClassLoader.loadClass("org.springframework.transaction.support.TransactionSynchronizationManager");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         Boolean isActive = null;
        try {
            isActive = (Boolean) tsmClass.getMethod("isActualTransactionActive", null).invoke(null, null);
        } catch (IllegalAccessException | IllegalArgumentException
                | InvocationTargetException | NoSuchMethodException
                | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         return isActive;
    }

    private MyService myService;

    public void setMyService(MyService myService) {
        this.myService = myService;
    }
}

和 //MyService.java

package service;

import java.util.List;
import java.util.Scanner;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import test.dao.DepartmentMapper;
import test.dao.EmployeeMapper;
import test.model.Department;
import test.model.Employee;
import test.model.EmployeeExample;

@Service
@EnableTransactionManagement
@Transactional(propagation=Propagation.REQUIRED)
public class MyService {
    @Autowired
    private SqlSessionTemplate sqlSessionTemplate;

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }
    @Transactional(propagation=Propagation.REQUIRED)
    public void startOperations() {
        DepartmentMapper deptMapper = sqlSessionTemplate.getMapper(DepartmentMapper.class);
        EmployeeMapper empMapper = sqlSessionTemplate.getMapper(EmployeeMapper.class);

        System.out.println("Before Insert Dept" + com.acty.StartTransAction.isActive());

        this.select(deptMapper, empMapper);

        Department dept = new Department();
        //insert new dept
        Scanner sc = new Scanner(System.in);
        System.out.println("Enter dept id ");
        dept.setDeptid(sc.nextLong());
        System.out.println("Enter dept Name ");
        dept.setDeptname(sc.next());

        deptMapper.insert(dept);

        System.out.println("After Insert Dept" + com.acty.StartTransAction.isActive());

        this.select(deptMapper, empMapper);

        this.select(deptMapper, empMapper);
        //now update employee

        EmployeeExample example = new EmployeeExample();
        example.createCriteria().andEmpidEqualTo(1L);

        Employee emp = new Employee();
        emp.setEmpname("jjj");

        try {
        //empMapper.updateByExampleSelective(emp, example);
        empMapper.updateByExampleWithBLOBs(emp, example);
        }catch(Exception e) {
            e.printStackTrace();
        }
        System.out.println("After Update Emp");

        this.select(deptMapper, empMapper);
    }
    public void select(DepartmentMapper deptMapper, EmployeeMapper empMapper) {
        System.out.println("\nDeptartment\n");
        List<Department> deptList= deptMapper.selectByExampleWithBLOBs(null);
        for(Department de : deptList) {
            System.out.println(" Dept Id : " + de.getDeptid());
            System.out.println(" Dept Name : " + de.getDeptname());
        }
        System.out.println("\nEmployee\n");
        List<Employee> empList= empMapper.selectByExampleWithBLOBs(null);
        for(Employee emp : empList) {
            System.out.println(" Emp Id : " + emp.getEmpid());
            System.out.println(" Emp Name : " + emp.getEmpname());
        }
    }
}

//现在我在服务层做事。 所有 DAO 都在其他包中,我是从 spring 注入的。

那么这个技巧也不起作用。 查看输出:
插入部门前 true

部门

部门编号:1
部门名称:si
部门编号:2
部门名称:esb
部门编号:3

员工

员工编号:1
企业名称 : kkkkk
输入部门编号
4
输入部门名称
asdwe
插入部门后

部门

部门编号:1
部门名称:si
部门编号:2
部门名称:esb
部门编号:3
部门名称:esb
部门编号:4
部门名称:asdwe

-- 这里发生异常
org.springframework.dao.DataIntegrityViolationException:

更新数据库时出错。原因:com.microsoft.sqlserver.jdbc.SQLServerException: Ca

.....

更新后 Emp 部门

部门编号:1
部门名称:si
部门编号:2
部门名称:esb
部门编号:3
部门名称:esb
部门编号:4
部门名称:asdwe

... 看这里,之前插入的部门,这里没有回滚..... (这里我们可以对sqlSessionTemplate使用回滚的方法,但是那么spring自动事务管理有什么用呢?这样做是没有意义的,我相信!) 什么问题,我真的没明白...

请提供一些可行的解决方案...

【问题讨论】:

    标签: spring struts2 mybatis


    【解决方案1】:

    当您在更新中抛出异常时,插入行的第一个事务早已结束并提交。您可能想要实现的是将 StartTransAction.execute 中的 try/catch 中的整个块作为单个事务运行。

    一般来说,不建议在 dao 级别定义事务 - 而且您的 MyUpdateUtil 看起来像带有 select/insert/update 方法的 dao 对象。您应该在服务层管理事务。

    首先,将这些行移至MyUpdateUtil 中的新方法:

    @Transactional
    public void insertAndUpdate(Department dept) {
      this.insert(dept);
      this.select();
      this.update(true);
    }
    

    然后从execute try/catch 块调用它。这将为您提供进一步完善代码的工作起点。

    【讨论】:

    • 其实我是春天的新手。我真的不知道如何制作服务层。正如您所说,MyUpdateUtil 看起来像 dao,但我已经有了 Department 和 Employee 表的 dao。您正确地提到我想在 try/catch 中将整个块作为单个事务运行。请给我代码 sn-p 描述如何在服务层执行此操作....
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-08
    • 1970-01-01
    • 1970-01-01
    • 2018-12-13
    • 2020-07-20
    相关资源
    最近更新 更多