Mybatis + SpringMVC事务管理

SpringMVC支持声明式和编程式事务管理,这里我讲的是声明式事务,也即通过注解@Transactional来使用事务。

这里是我在这个工程中所使用的jar包:http://download.csdn.net/detail/liujan511536/9050079

这里是我这个工程的源码:http://download.csdn.net/detail/liujan511536/9050093

这是一个dynamic web project。

首先看配置文件web.xml,这个文件在WebContent/WEB-INF/目录下:


  SpringMVC_Mybatis
  
    index.html
    index.htm
    index.jsp
    default.html
    default.htm
    default.jsp
  
  
  
  	
  		org.springframework.web.context.ContextLoaderListener
  	
  
  
  
  
	contextConfigLocation
	classpath:mybatis-context.xml
  

  
  	springmvc
  	org.springframework.web.servlet.DispatcherServlet
  	
  		
  		contextConfigLocation
  		
  			
  			classpath:application-context.xml
  		
  	
  	1
  
  
  
  	springmvc
  	*.html
  

mybatix-context.xml ( /WebContent/WEB-INF/classes/目录下):


   
    
 		
 			
 				classpath:db-config.properties
 			
 		
 	
	
	
		
		
		
	
	
	
        
    
    
	
	
	
		
		
	
	
	
		
	
	

application-context.xml (/WebContent/WEB-INF/classes/目录下):



        
	
	
	
	


 容器装配顺序造成的事务无法回滚
在加载这个web.xml这个文件时,Spring会去加载在这个文件中引入的其他文件,这里有mybatis-context.xml和application-context.xml这两个文件,两个文件都在/WebContent/WEB-INF/classes/目录下。
但是这里有个问题,就是这两个文件的加载是有先后顺序的。Spring会优先加载在context-param标签中引入的文件,也就是这里的mybatis-context.xml,然后再加载application-context.xml。在加载mybatis-context.xml时,Spring就会装配@Controlle和@Service的容器,并把他们放到Spring的容器中来;接着加载application-context.xml,这时Spring也会把@Service和@Controller注解的实例装配到Spring容器中,但是这时由于先前在加载mybatis-context.xml时已经装配过@Service和@Controller的容器,所以这时新装配的容器会覆盖掉先前的容器,所以Spring容器中就只剩下后来装配的容器了。
这种装配顺序就会引来一个问题,由于我的事务是在mybatis-context.xml文件中声明的,所以这个文件中的Service容器是带有事务能力的;但是这里装配的Service容器会被后来application-context.xml中的Service容器替换掉,而application-context.xml中的Service容器是没有事务能力的,所以最后造成在Spring容器中的Service是没有事务能力的。
这是很容易就陷入的一个陷阱;很多初学者在照着网上的教程配置好后,却发现无论如何都不能回滚或者其他事务的问题,很多时候就是由于这种情况造成的。
要解决这个问题,只需只在mybatis-context.xml中生成Service的带事务的容器,而在application-context.xml中就不生成Service容器了。又由于Service是Controller类中的一个属性,所以在装配Controller前要先装配好Service。
为了达到以上目的,由于是先加载myabtis-context.xml,后加载application-context.xml,所以我们只需在myabtis-context.xml装配Service,不装配Controller;然后在application-context.xml中装配Controller,不装配Service就可以了。这样就得修改mybatis-context.xml和application-context.xml这两个文件,修改后的文件 如下:
mybatis-context.xml:



    
    
    	
    
    
    
 		
 			
 				classpath:db-config.properties
 			
 		
 	
	
	
		
		
		
	
	
	
        
    
    
	
	
	
		
		
	
	
	
		
	
	

application-context.xml


    
	
		
		
	
	
	

web.xml内容不变。

2. 我们把事务控制在Service层,当一个函数(假设函数名为funA)前加上 @Transactional标签时,就表示这个函数具有事务能力。那么什么情况下事务会commit,什么情况下事务会rollback?
如果要事务回滚,则funA函数内必须得抛出一个uncheck异常(比如RuntimeException),而且这个异常不能在funA内被try...catch,比如,在UserService类里有一个函数叫saveUser,是用来往数据库中插入一条数据的:
@Transactional(propagation=Propagation.REQUIRED)
public void saveUser(User u) throws Exception{
	userMapper.insert(u);
	
	throw new RuntimeException("hehe");
}
当运行这个函数时,是先往数据库中插入数据u,然后再抛出RuntimeException异常,而且这个异常没有被saveUser函数捕捉,所以这个异常会被Spring检测到,这时事务就会回滚,数据插入失败;
但是,如果这个异常被saveUser捕捉了,那么Spring就不会检测到这个异常,事务就不会回滚,数据就会插入成功,如下:
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void saveUser(User u) throws Exception{
	userMapper.insert(u);
	try {
		throw new RuntimeException("hehe");
	} catch (Exception e) {
		// TODO: handle exception
	}
		
}

但是,这个异常如果被调用saveUser的函数catch的话,事务也会回滚的,比如在UserController中有一个方法 addUser,在这个方法里会调用userService的saveUser方法:
public void addUser(String name, int stuId) {
	User u = new User();
	u.setName(name);
	u.setStuid(stuId);
	try {
		userService.saveUser(u);
	} catch (Exception e) {
		// TODO: handle exception
		e.printStackTrace();
	}
		
}
这时事务也会回滚。
综上所述,只要异常不在异常抛出的地方被catch,那么即使该异常在后面的函数中被catch,事务也会回滚。

这里又有一个问题,就是抛出的异常只能是uncheck类的,如果是check类的事务就不会回滚,如下:
@Transactional(propagation=Propagation.REQUIRED)
public void saveUser(User u) throws Exception{
	userMapper.insert(u);
	
	throw new Exception("hehe");
		
}
这时事务是不会回滚的。
如果想在跑出check类异常时事务也会回滚,则可以在 @Transactional中声明当遇上check类异常时回滚,如下:
@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public void saveUser(User u) throws Exception{
	userMapper.insert(u);
		
	throw new Exception("hehe");
		
}


最后讲下
@Transactional标签中 propagation的含义:

REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。 

SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。 

MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。 

REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。 

NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 

NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。 

NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。







你可能感兴趣的:(springmvc,mybatis)