项目中使用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的文件名。
以上观点,瑾我个人观点,如果有更简捷实用的方法希望大家赐教~