大多数后台程序都有后台程序,显然也就有权限管理模块。今天在做项目时发现了一个重大问题(差点害死我)
先看一下权限模块-修改的效果图:
到这一步有什么问题吗?看着savaPermit()方法页很正常,而且还try-catch了,使用boolean result作为标记,标记是否修改成功。
但仔细想一想,这个方法存在问题:这个修改操作(对数据库一个delete,多个insert),应该是个原子操作,要么都成功,要么就需在在数据库中回滚。
我当时在debug这断代码,delete操作之后,可能是修改了源码,eclipse重新编译-部署了一次,删除操作之后,保存操作出现了异常。发现了这个问题。
(下面是debug重新部署的log:)
十一月 01, 2016 1:38:30 下午 org.apache.catalina.core.StandardContext reload
信息: Reloading Context with name [/supervise-copy] has started
十一月 01, 2016 1:38:30 下午 org.apache.catalina.core.StandardWrapper unload
信息: Waiting for 1 instance(s) to be deallocated for Servlet [springMVC]
十一月 01, 2016 1:38:31 下午 org.apache.catalina.core.StandardWrapper unload
信息: Waiting for 1 instance(s) to be deallocated for Servlet [springMVC]
十一月 01, 2016 1:38:32 下午 org.apache.catalina.core.StandardWrapper unload
信息: Waiting for 1 instance(s) to be deallocated for Servlet [springMVC]
十一月 01, 2016 1:38:32 下午 org.apache.catalina.core.ApplicationContext log
信息: Destroying Spring FrameworkServlet 'springMVC'
十一月 01, 2016 1:38:32 下午 org.apache.catalina.core.ApplicationContext log
信息: Closing Spring root WebApplicationContext
十一月 01, 2016 1:38:32 下午 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesJdbc
严重: The web application [/supervise-copy] registered the JDBC driver [oracle.jdbc.driver.OracleDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
十一月 01, 2016 1:38:32 下午 org.apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
严重: The web application [/supervise-copy] is still processing a request that has yet to finish. This is very likely to create a memory leak. You can control the time allowed for requests to finish by using the unloadDelay attribute of the standard Context implementation.
十一月 01, 2016 1:38:32 下午 org.apache.catalina.loader.WebappClassLoaderBase checkThreadLocalMapForLeaks
严重: The web application [/supervise-copy] created a ThreadLocal with key of type [org.springframework.core.NamedThreadLocal] (value [Request attributes]) and a value of type [org.springframework.web.context.request.ServletRequestAttributes] (value [org.apache.catalina.connector.RequestFacade@7b3f82eb]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
十一月 01, 2016 1:38:32 下午 org.apache.catalina.loader.WebappClassLoaderBase checkThreadLocalMapForLeaks
严重: The web application [/supervise-copy] created a ThreadLocal with key of type [org.springframework.core.NamedThreadLocal] (value [Locale context]) and a value of type [org.springframework.web.servlet.DispatcherServlet$1] (value [org.springframework.web.servlet.DispatcherServlet$1@c1fd6b2]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
十一月 01, 2016 1:38:36 下午 org.apache.catalina.startup.TldConfig execute
信息: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
十一月 01, 2016 1:38:36 下午 org.apache.catalina.core.ApplicationContext log
信息: No Spring WebApplicationInitializer types detected on classpath
十一月 01, 2016 1:38:36 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring root WebApplicationContext
十一月 01, 2016 1:38:38 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring FrameworkServlet 'springMVC'
十一月 01, 2016 1:38:40 下午 org.apache.catalina.core.StandardContext reload
信息: Reloading Context with name [/supervise-copy] is completed
(下面是报的异常信息:)
然后:
我累个擦,这个用户是tmd超级管理员啊。悲催了。。。。。
还好我在另外的数据库中有备份。用备份恢复之后,终于终于好了。
但是这个如何解决呢?
配置事务
Spring AOP异常捕获原理
被拦截的方法需显式抛出异常,并不能经过任何处理,这样aop代理才能捕获到方法存在异常,才能进行回滚。默认情况下aop只捕获RuntimeException的异常,但可以通过<tx:method name="upd*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>配置来捕获特定的异常并回滚
换句话说在service的方法中不使用try-catch或者在catch中最后加上throw new runtimeexcetpion();,这样此方法的异常时才能被aop捕获进而回滚。
解决方案:
方案1.例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException();语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端、view层、action层)要继续捕获这个异常并做相应处理。
方案2.在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法)
注意:
无论方案1还是方案2,都需要配置AOP切面:(一开始我就是没有配置AOP切面,就一直不能回滚。)
<!-- 事物配置 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"
rollback-for="java.lang.Exception" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="canyin"
expression="execution(* com.shark.service.impl.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="canyin" />
</aop:config>
如果使用方案2:Controller无需修改。
Service:(要在Service类上加上@Transactional(rollbackFor = Exception.class)注解)
@Override
public boolean savePermit(Integer roleId, String moduleId) {
boolean result = false;
try {
iSysPermitDAO.deleteByRoleId(roleId);
Map<String, Integer> map = new HashMap<String, Integer>();
String[] strArray = moduleId.split("\\|");
for (String str : strArray) {
int mid = Integer.parseInt(str);
map.put("roleId", roleId);
map.put("moduleId", mid);
map.put("functionIds", 1);
iSysPermitDAO.insert(map);
}
result = true;
} catch (Exception e) {
e.printStackTrace();
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
如果使用方案1:
Service:(要在Service类上加上@Transactional(rollbackFor = Exception.class)注解)
@Override
public boolean savePermit(Integer roleId, String moduleId) {
boolean result = false;
try {
iSysPermitDAO.deleteByRoleId(roleId);
Map<String, Integer> map = new HashMap<String, Integer>();
String[] strArray = moduleId.split("\\|");
for (String str : strArray) {
int mid = Integer.parseInt(str);
map.put("roleId", roleId);
map.put("moduleId", mid);
map.put("functionIds", 1);
iSysPermitDAO.insert(map);
}
result = true;
} catch (Exception e) {
e.printStackTrace();
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new RuntimeException();
}
return result;
}
Controller:
@ResponseBody
@RequestMapping("savePermit.do")
public boolean savePermit(Integer roleId, String moduleId) {
boolean f = false;
try {
f = sysPermitService.savePermit(roleId, moduleId);
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
参考:
spring 声明式事务配置,主动抛出异常不回滚。http://bnmnba.iteye.com/blog/1402071