• 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的文件名。

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

  • 相关阅读:
    python报以下错误:TypeError: 'int' object is not subscriptable
    C# Func与Action
    C#调用C++的DLL 尝试写入受保护的内存
    C#调用C++的dll EntryPointNotFoundException
    C# 拖拽事件
    C#操作Access数据库中遇到的问题(待续)
    Winform 中写代码布局中遇到的控件遮盖问题
    thinkphp6执行流程(一)
    xdebug调试过程中apache500超时
    禁用phpcookie以后如何使用Session
  • 原文地址:https://www.cnblogs.com/yangwn/p/1790734.html
Copyright © 2020-2023  润新知