Spring MVC @Transactional注解方式事务失效的解决办法

本文详细描述了在使用SpringMVC + SpringJdbcTemplate框架时遇到事务无法正常回滚的问题,通过调整Spring容器和SpringMVC配置解决了该问题。包括增加组件扫描排除和服务过滤,确保事务控制的有效性。

前文提到,最新换了框架,新项目用SpringMVC + Spring JdbcTemplate。搭框架时,发现了一个事务无法正常回滚的问题,记录如下:

首先展示问题:

Spring applicationContext.xml配置:

        
	<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
		<property name="jndiName">
			<value>java:comp/env/jdbc/will</value>
		</property>
	</bean> 
		
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="txManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>
	
	<!-- 事务控制   -->
	<tx:annotation-driven transaction-manager="txManager" />

Spring mvc.dispatcher.xml配置:

	
    <!-- 自动扫描的包名 -->  
    <context:component-scan base-package="com.will" > 
    </context:component-scan>
	 
    <!-- 默认的注解映射的支持 -->
    <mvc:annotation-driven />
	
    <!-- 对静态资源文件的访问  -->  
    <mvc:default-servlet-handler/>  
      
        
    <!-- 拦截器  
    <mvc:interceptors>  
    	<bean class="com.will.mvc.MyInteceptor" />  
    </mvc:interceptors> 
    --> 
    
    <!-- 视图解释类 --> 
    <bean id="viewResolver"  
        class="org.springframework.web.servlet.view.UrlBasedViewResolver">  
        <property name="viewClass"  value="org.springframework.web.servlet.view.JstlView" />  
        <property name="prefix" value="/WEB-INF/pages/" />  
        <property name="suffix" value=".jsp" />  
    </bean>  	

然后在Service层模拟了一个事务回滚的method case:

    @Transactional
    public boolean save(Person person)
    {
       for(int id: new int[]{2,3})
        {
            personDao.del(id);
            int j = 1/0;
        }                
       
        return false;
    }

本以为大功告成,在运行save方法时,由于1/0 抛出 java.lang.ArithmeticException: / by zero  RuntimeException,导致事务回归。However,no way! So crazy~

查了下,发现Spring MVC对于事务配置比较讲究,需要额外的配置。解决办法如下:

需要在 applicationContext.xml增加:

<context:component-scan base-package="com.will"> 
  	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 
</context:component-scan>

在 Spring mvc.dispatcher.xml增加:

<context:component-scan base-package="com.will" > 
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> 
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />  
</context:component-scan>


由于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>
<servlet>
	<servlet-name>dispatcher</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
	     <param-name>contextConfigLocation</param-name>
	     <param-value>classpath*:/mvc_dispatcher_servlet.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>dispatcher</servlet-name>
	<url-pattern>*.do</url-pattern>
</servlet-mapping>


Spring容器优先加载由ServletContextListener(对应applicationContext.xml)产生的父容器,而SpringMVC(对应mvc_dispatcher_servlet.xml)产生的是子容器。子容器Controller进行扫描装配时装配的@Service注解的实例是没有经过事务加强处理,即没有事务处理能力的Service,而父容器进行初始化的Service是保证事务的增强处理能力的。如果不在子容器中将Service exclude掉,此时得到的将是原样的无事务处理能力的Service,因为在多上下文的情况下,如果同一个bean被定义两次,后面一个优先


( update 2014.05.27  今天看见一种说法:key word =双亲上下文不使用ContextLoaderListener监听器来加载spring的配置,改用DispatcherServlet来加载spring的配置,不要双亲上下文,只使用一个DispatcherServlet就不会出现上述问题。笔者这里未测过这个办法,因为我自己的业务需要一个extends ContextLoaderListener的selfListener,有兴趣的朋友可以自己测试下这个说法,并欢迎把测试的结果与我交流 :) 

( update 2015.03.20  对于因为双亲上下文引起初始化二次,关键字:springmvc 初始化两次  

经过以上分析,故可以优化上述配置

在 applicationContext.xml增加:

<context:component-scan base-package="com.will"> 
</context:component-scan>

在 Spring mvc.dispatcher.xml增加:

<context:component-scan base-package="com.will" >    
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service" />  
</context:component-scan>

经过如上配置,可以发现事务控制部分的日志如下:

2013-09-25 09:53:13,031 [http-8080-2] DEBUG [org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] - Adding transactional method 'save' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2013-09-25 09:53:13,037 [http-8080-2] DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory] - Returning cached instance of singleton bean 'txManager'
2013-09-25 09:53:13,050 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Creating new transaction with name [com.will.service.impl.PersonServiceImpl.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2013-09-25 09:53:13,313 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Acquired Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction
2013-09-25 09:53:13,323 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Switching JDBC Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver] to manual commit
2013-09-25 09:53:13,327 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - Executing prepared SQL update
2013-09-25 09:53:13,328 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - Executing prepared SQL statement [delete from person where id=?]
2013-09-25 09:53:13,348 [http-8080-2] DEBUG [org.springframework.jdbc.core.JdbcTemplate] - SQL update affected 1 rows
2013-09-25 09:53:13,363 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Initiating transaction rollback
2013-09-25 09:53:13,364 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver]
2013-09-25 09:53:13,377 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceTransactionManager] - Releasing JDBC Connection [jdbc:mysql://localhost:3306/mvc?useUnicode=true&characterEncoding=UTF-8, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
2013-09-25 09:53:13,378 [http-8080-2] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource

在2013-09-25 09:53:13,363处进行了rollback。


PS:习惯了Structs,对事务处理有点思维定式,这次花费不少时间来解决这个问题。颇为尴尬!

<think>好的,我现在需要回答用户关于Spring MVC中@Repository注解的使用说明、示例和功能的问题。首先,我应该回顾一下用户提供的引用资料,看看里面有没有相关的信息。 用户提供的引用有5个,其中引用[1]提到@Component、@Repository、@Service、@Controller的关系,指出@Component是通用的注解,而@Repository用于持久层。引用[4]也提到这四个注解位于同一个包下,说明它们都是用来标注不同层次的组件。引用[5]中有具体的代码示例,展示@Service和@Repository的使用,可能可以从中提取示例。 接下来,我需要明确@Repository在Spring MVC中的作用。根据Spring框架的设计,@Repository通常用于数据访问层(DAO层),用来标记类作为数据访问对象。它的主要功能包括组件扫描和异常转换。Spring会自动扫描带有@Repository的类,并将其注册为Bean。此外,@Repository还能将特定于持久层的异常(如JDBC异常)转换为Spring的统一DataAccessException,方便异常处理。 在示例部分,引用[5]中的代码展示了@Service的使用,但用户需要的是@Repository的示例。我可以构造一个简单的DAO实现类,使用@Repository注解,并注入JdbcTemplate来操作数据库。例如: ```java @Repository public class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate; public User getUserById(Long id) { String sql = "SELECT * FROM users WHERE id = ?"; return jdbcTemplate.queryForObject(sql, new Object[]{id}, User.class); } } ``` 这样既展示了注解的使用,也说明了如何与数据库交互。同时需要提到,为了确保组件扫描生效,需要在Spring配置中添加包扫描路径,例如使用<context:component-scan>或者在Java配置类中使用@ComponentScan。 关于功能部分,需要强调@Repository除了作为组件注册外,还提供的异常转换特性。这点可能用户不太清楚,需要详细说明,比如在数据访问过程中抛出的原生异常会被Spring的异常转换机制处理,避免代码中捕获特定异常,提高可维护性。 用户可能还会混淆@Repository与其他注解的区别,比如@Service和@Controller,所以可以简要提到它们的适用场景:@Service用于服务层,@Controller用于控制层,而@Repository专用于数据访问层。不过根据用户的问题,这部分可能不需要展开,但可以作为相关问题提出。 最后,按照用户要求生成相关问题,需要围绕@Repository的用途、与其他注解的区别、配置方式以及异常处理机制来设计问题。例如: 1. @Repository和其他注解如@Service的主要区别是什么? 2. 如何配置Spring以正确扫描@Repository注解的类? 3. @Repository如何处理数据访问层的异常? 需要确保回答结构清晰,分点说明,并正确引用提供的资料中的信息,比如引用[1]和[4]提到的注解关系,引用[5]中的代码结构作为示例参考。同时,注意使用中文回答,数学公式部分这里可能用不到,但需要遵循系统指令的其他要求,如LaTeX格式等,但用户的问题不涉及数学内容,所以可能不需要处理这部分。</think>### Spring MVC中@Repository注解使用说明 #### 一、功能概述 1. **组件注册** 被`@Repository`注解的类会被Spring自动扫描并注册为Bean,通常用于**数据访问层(DAO层)**[^1][^4]。 2. **异常转换** 自动将数据访问层抛出的原生异常(如JDBC异常)转换为Spring的统一异常`DataAccessException`,简化异常处理[^4]。 --- #### 二、使用示例 ```java // 定义DAO接口 public interface UserDao { User getUserById(Long id); } // 实现类使用@Repository注解 @Repository public class UserDaoImpl implements UserDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public User getUserById(Long id) { String sql = "SELECT * FROM users WHERE id = ?"; return jdbcTemplate.queryForObject(sql, new Object[]{id}, User.class); } } ``` ##### 关键点: 1. **配置组件扫描** 在Spring配置文件中启用包扫描: ```xml <context:component-scan base-package="com.example.dao" /> ``` 或通过Java配置类: ```java @Configuration @ComponentScan("com.example.dao") public class AppConfig {} ``` 2. **依赖注入** 在其他组件(如`@Service`类)中通过`@Autowired`注入DAO层Bean: ```java @Service public class UserService { @Autowired private UserDao userDao; } ``` --- #### 三、与其他注解的区别 | 注解 | 适用层 | 主要功能 | |--------------|-------------|-----------------------------------| | `@Repository`| 数据访问层 | 数据操作、异常转换 | | `@Service` | 业务逻辑层 | 定义服务类(如事务管理)[^2][^5] | | `@Controller`| 控制层 | 处理HTTP请求[^2] | | `@Component` | 通用组件 | 非特定层时使用[^4] | --- #### 四、典型应用场景 - 数据库操作(如JdbcTemplate、MyBatis Mapper) - NoSQL数据访问(如MongoDB、Redis) - 事务管理(结合`@Transactional`注解) ---
评论 19
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值