Spring3.0.2 使用 Annotation 与 @Transactional 冲突问题解决方案

项目中使用Spring最新的全Annotation方式,从Controller, Service, 到 DAO全部使用Annotation方式进行开发。

在使用@Transactional 事务处理时,遇到了问题:

配置如下:

web.xml

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext/db-config.xml</param-value>
</context-param>

<servlet>
<servlet-name>SillServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext/mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>SillServlet</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>

mvc-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />
<!-- 对项目中的所有文件进行扫描-->
<context:component-scan base-package="com.*.*.*.*" />
</beans>

 db-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<context:property-placeholder location="/WEB-INF/applicationContext/jdbc.properties" />
<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="/WEB-INF/sqlmap/sqlMapping.xml" />
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven />
</beans>

Controller类如下:

@Controller
@RequestMapping(
"/check")
public class CheckController {
private Logger logger = LoggerFactory.getLogger(WelcomeController.class);
@Autowired
private PrepareService prepareService;

@RequestMapping(method
= RequestMethod.GET)
public void check(HttpServletRequest request, HttpServletResponse response) throws Exception {
String id
= ServletRequestUtils.getStringParameter(request, "id");

Map
<String, Object> mapdata = new HashMap<String, Object>();
mapdata.put(
"userId", userId);
mapdata.put(
"script", script);
prepareService.addUserInputScript(mapdata);
}

@RequestMapping(
"/show")
public ModelAndView show(HttpServletRequest request, Map<String, Object> model) throws Exception {
String number
= ServletRequestUtils.getStringParameter(request, "id");
model.put(
"account", number);
return new ModelAndView("welcome", model);
}
}

ServiceHandler:

@Service
public class PrepareServiceHandler implements PrepareService {

@Autowired
private ScriptDAO scriptDAO;

@Autowired
private ConditionDAO conditionDAO;

/**
* 需要事务支持的Function
*/
@Transactional(readOnly
= false, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void addUserInputScript(...) throws Exception {
scriptDAO.addScript(...);
conditionDAO.addCondition(...);
}
}

DAO:(只提供一个例子)

@Repository
public class ScriptDAOImpl implements ScriptDAO {

private @Resource(name = "sqlMapClientTemplate") SqlMapClientTemplate sqlMapClientTemplate;

private static final String SQL_MAPPING_TABLE = "SCRIPTS.SQL_MAPPING_TABLE";

@Override
public Integer addScript(ScriptDO scriptDO) {
return (Integer) sqlMapClientTemplate.insert(SQL_MAPPING_TABLE, scriptDO);
}
}


OK,如果上述Bean,并不是通过Annotation定义,而是通过<bean id=".." class=".."> 声明式写明在配置文件 Transactional 是好用的。

但是通过Annotation来定义,就不是那么回事了。

原因就是

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

出现在spring mvc的配置文件中时,web 容器在配置的路径中扫描包含@Controller, @Service, @Repository或@Components等的类并且也会包含扫描@Transaction,但是此时@Transaction并未完成初始化,导致事务未被注册。

怎么解决呢,搜了2天也没有搜到比较好的解决办法:有的是通过context:component-scan 中的 context:exclude-filter, 把带@Transaction的类排除,之后再声明式的写到配置文件中,很不爽,感觉就是一种兑付的态度。

如果是使用两个扫描器怎么样,将原来的配置分成两部分,将扫描其他目录的context:component放入context-param 的 contextConfigLocation文件,该文件为org.springframework.web.context.ContextLoaderListener的加载文件。

controller还是由servlet来装载。

代码如下:

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext/db-config.xml</param-value>
</context-param>

<servlet>
<servlet-name>SillServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext/mvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>SillServlet</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>

</web-app>

mvc-config:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

<!-- 只针对action包中的controller进行扫描 -->
<context:component-scan base-package="com.*.*.*.*.action" />

</beans>

db-config:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
<context:property-placeholder location="/WEB-INF/applicationContext/jdbc.properties" />

<bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
<property name="sqlMapClient" ref="sqlMapClient" />
</bean>

<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="/WEB-INF/sqlmap/sqlMapping.xml" />
<property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method
="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>

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

<tx:annotation-driven   />

<!--Spring 扫描除controller以外的Bean -->
<context:component-scan base-package="com.*.*.*.*">
<context:exclude-filter type="regex" expression=".*Controller$" />
</context:component-scan>

</beans>

再RunTest,OK~!

重点回顾:

1.spring初始化时优先component-scan bean,注入Web Controller 的Service直接拿了上下文中的@Service("someService"),这个时候@Transactional 还没有被处理.所以Web Controller 的Service是SomeServiceImpl而不是AOP的$ProxyNO.

2.必须分开以不同的方式进行加载,Controller 由 Servlet-init进行扫描,Service与DAO由context扫描,这样做就需要区分路径与controller的文件名。

以上观点,瑾我个人观点,如果有更简捷实用的方法希望大家赐教~

原文地址:https://www.cnblogs.com/yangwn/p/1790734.html