大家知道, 在spring mvc中, 在applicationContext.xml 和 dispatch-servlet.xml中都可以进行spring 的配置, 那么他们有什么区别呢:
我们先看一下Spring 官方文档:
Spring lets you define multiple contexts in a parent-child hierarchy.
The applicationContext.xml defines the beans for the "root webapp context", i.e. the context associated with the webapp.
The spring-servlet.xml (or whatever else you call it) defines the beans for one servlet's app context. There can be many of these in a webapp, one per Spring servlet (e.g. spring1-servlet.xml for servlet spring1, spring2-servlet.xml for servlet spring2).
Beans in spring-servlet.xml can reference beans in applicationContext.xml, but not vice versa.
All Spring MVC controllers must go in the spring-servlet.xml context.
In most simple cases, the applicationContext.xml context is unnecessary. It is generally used to contain beans that are shared between all servlets in a webapp. If you only have one servlet, then there's not really much point, unless you have a specific use for it.
可见, applicationContext.xml 和 dispatch-servlet.xml形成了两个父子关系的上下文,经过测试, 发现:
1) 一个bean如果在两个文件中都被定义了(比如两个文件中都定义了component scan扫描相同的package), spring会在application context和 servlet context中都生成一个实例,他们处于不同的上下文空间中,他们的行为方式是有可能不一样的(见下面描述的问题)。
2) 如果在application context和 servlet context中都存在同一个 @Service 的实例, controller(在servlet context中) 通过 @Resource引用时, 会优先选择servlet context中的实例。
在Spring 里配置Mybatis 事务时出现的问题:
按照网上的说明, 我在applicationContext.xml文件中增加了如下的配置:
1. applicationContext.xml
<!--这是原先的sqlSession和dao的配置, 增加事务时候配置保持不变-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis.xml"></property>
<property name="mapperLocations" value="classpath:com/sjl/dao/*-mapper.xml"></property>
</bean>
<bean id="idao" class="com.sjl.dao.IdaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
<!--增加的mybatis 事务控制 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
2. 然后在dao中使用 @Transactional 注解声明事务
package com.sjl.base;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
import com.sjl.common.EntityClassUtil;
import com.sjl.common.Pager;
import com.sjl.dao.Idao;
public class AbstractBaseDao<T, PK extends Serializable> implements BaseDao <T, PK> {
@Resource
private Idao<T, PK> idao;
private Class entityClass = EntityClassUtil.getEntityClass(getClass());
<strong><span style="color:#ff0000;">@Transactional</span></strong>
public void save(T entity) {
idao.save(entity);
throw new RuntimeException();//抛出异常, 事务回滚
}
public void delete(PK pk) {
idao.delete(entityClass, pk);
}
public Pager<T> findByPage(int pageOffset, int pageSize) {
HashMap<String, Integer> map = new HashMap();
map.put("pageOffset", pageOffset);
map.put("pageSize", pageSize);
Pager<T> pager = idao.findByPage(entityClass, map);
return pager;
}
}
其中的idao定义如下:
<pre name="code" class="java">package com.sjl.dao;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.stereotype.Repository;
import com.sjl.common.Pager;
@Repository("idao")
public class IdaoImpl<T, PK extends Serializable> extends SqlSessionDaoSupport implements Idao<T, PK> {
public void save(T entity) {
this.getSqlSession().insert(entity.getClass().getName()+".add", entity);
}
public void delete(Class<T> entityClass, PK pk) {
this.getSqlSession().insert(entityClass.getName()+".delete", pk);
}
public Pager<T> findByPage(Class<T> entityClass, Map param){
Pager<T> pager = new Pager();
List<T> pageContent = this.getSqlSession().selectList(entityClass.getName()+".findPageContent", param);
pager.setPageContent(pageContent);
int count = this.getSqlSession().selectOne(entityClass.getName()+".findTotal");
pager.setTotalCount(count);
return pager;
}
}
经过以上配置,按照道理应该没有问题了, 但是程序运行后发现事务始终没有启动,连transaction-manager都没有进入, 找了很久没有发现问题出在哪里。后来看到网上同样的配置能运行成功, 比较后发现他是在普通Java Application方式下运行成功的,我把自己的程序也在普通的Java Application方式下运行, 发现transaction起作用了,由此感觉应该是Spring MVC环境下出现的问题,而且应该是transaction 的配置没有起作用, 偶然记起在自己的配置中,dispatch-servlet.xml和applicationContext.xml中 配置的component-scan都扫描了相同的包,这样的话, 虽然在application Context中定义了transaction-manager, 但是这个transaction-manager的配置却是作用在application context中定义的bean上面的。根据前面讲到的application context和dispatcher-servlet context的关系, dispatcher-servlet context 和application context中都存在着相同类型的service bean 实例, 那么controller将优先调用dispatcher-servlet context 中的service bean,而dispatcher-servlet context 中由于没有配置transaction manager, 所以生成的service bean实例没有事务的植入(aop), 当然就无法启动事务了。
解决办法:
1. 在applicationContext.xml 中修改component-scan配置如下:
<context:component-scan base-package="com.sjl">
<span style="white-space:pre"> </span><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> //不扫描标记@Controller的类
</context:component-scan>
2. 在dispatcher-servlet.xml中修改component-scan配置:
<context:component-scan base-package="com.sjl.controller"></context:component-scan> .//只扫描controller package
教训:
在application context和dispatcher-servlet定义的bean最好不要重复, dispatcher-servlet最好只是定义controller类型的bean。