本教程主要基于本人Nutz框架的 Demo进行改造,目的是使原有Nutz demo代码可以正常运行,新增spring代码也可以正常运行,并且Nutz框架原有代码与spring新增代码可以相互引用,从而达到整合后的新代码与旧代码可以兼容,请配合项目源码进行操作。
Demo功能
登陆模块
查看用户列表
新增用户
通过自定义sql自动导出报表
https://github.com/Evan43789596/evanshare.git
某公司原有项目采用的是Nutz框架,无奈是Nutz框架整合其他主流框架会相对繁琐,加上Nutz对事务支持也不是很友好,而且也不便于团队成员使用和学习市面主流技术,于是我们想把Nutz与spring框架整合,在不影响原有项目的前提下,新代码均使用spring实现,也就意味着把nutz的东西都转移到spring去管理了。
目标
- 原有nutz的业务代码不需要改动能正常运行
- 新做的spring的代码可以正常运行
- nutz可以引用spring的代码
- spring可以引用nutz的代码
方案
- 保留两套MVC,Spring MVC与Nutz MVC共存,原有旧代码继续走Nutz MVC
- Nutz与Spring统一使用Spring IOC容器进行管理,均从Spring上下文查找bean
- 原有Nutz配置搬到Spring ,由Spring统一加载与管理,AOP与事务均使用Spring实现
思路
- Nutz Mvc 与 Spring Mvc相互独立存在,项目原有代码继续使用Nutz Mvc,新建Spring代码采用Spring Mvc,根据请求路径中的前缀/spring/进行切换。
- 原有Nutz项目的bean与新做的spring相关bean统一交给springIOC容器管理,覆盖NutzIOC注解(IocBean、Inject、InjectName)使这些注解标注的类实例交由Spring管理
- 创建SpringIocProvider实现Ioc、IocProvider,使原有Nutz代码与新代码上下文统一使用applicationContext
- 通过Spring AutowiredAnnotationBeanPostProcessor类中的setAutowiredAnnotationTypes方法,可使开发者扩展自定义注解交给spring去自动注入(如:Inject),从而实现spring的代码也能使用Inject注解
把原有nutz项目的所有配置统一交由spring去加载,这样后面的事情都按照spring的方式去做即可。文件变更
- 在com.evanshare包下,新增spring相关Controller与service,其功能与原Nutz部分相同
- 新增SpringIocProvider
- 修改MainModule原有IocBy的类型,改为SpringIocProvider
- 新增org.nutz包,用于复写Nutz Ioc相关注解
- 修改ResourceFilter,新增对/spring/过滤
- 修改web.xml,加入spring相关配置
- 在resource目录下新增application.xml、SpringMVC-servlet.xml以及其他相关.xml,把原有Nutz配置搬到 .xml
- 对com.evanshare.module原有nutz部分module引入spring相关service与注解
打开WEB-INF下的web.xl,在原有配置基础上加入如下配置
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
listener-class>
listener>
<servlet>
<servlet-name>SpringMVCservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath*:SpringMVC-servlet.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>SpringMVCservlet-name>
<url-pattern>/spring/*url-pattern>
servlet-mapping>
在resource目录下,创建applicationContext.xml,加入如下配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<import resource="conf/*.xml" />
beans>
在resource目录下,创建SpringMVC-servlet.xml,加入如下配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.evanshare" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/value>
property>
<property name="suffix">
<value>.jspvalue>
property>
bean>
beans>
在Resource下,分别创建jdbc.xml、nutDao.xml、property.xml、transation.xml、annotation.xml
jdbc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}" />
<property name="password" value="${db.password}"/>
<property name="testWhileIdle" value="true"/>
<property name="validationQuery" value="${db.validationQuery}"/>
<property name="maxActive" value="${db.maxActive}"/>
<property name="connectionProperties" value="druid.stat.slowSqlMillis=2000"/>
bean>
beans>
nutzDao.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="dao" class="org.nutz.dao.impl.NutDao">
<property name="dataSource" ref="dataSource"/>
<property name="runner" ref="springDaoRunner"/>
bean>
<bean id="springDaoRunner" class="org.nutz.integration.spring.SpringDaoRunner">
bean>
beans>
property.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:properties/db.propertiesvalue>
list>
property>
bean>
beans>
transation.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="dao" class="org.nutz.dao.impl.NutDao">
<property name="dataSource" ref="dataSource"/>
<property name="runner" ref="springDaoRunner"/>
bean>
<bean id="springDaoRunner" class="org.nutz.integration.spring.SpringDaoRunner">
bean>
beans>
annotation.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.evanshare">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
<context:annotation-config/>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor">
<property name="autowiredAnnotationTypes" >
<set >
<value >org.nutz.ioc.loader.annotation.Injectvalue>
set>
property>
bean>
beans>
注意:
以下annotation.xml中的这段配置,是为了让spring能识别我们的自定义注解,并且能为我们用自定义注解(Nutz的@Inject)标识的对象注入,我们如果想加多几个自定义注解,只需要像一下配置那样把全限定类名加入到属性中即可
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor">
<property name="autowiredAnnotationTypes" >
<set >
<value >org.nutz.ioc.loader.annotation.Injectvalue>
set>
property>
bean>
Tips
为什么我会知道?参考Spring AutowiredAnnotationBeanPostProcessor源码注释可以看到,其实它本身就是支持开发者自由扩展的
/**
* Set the 'autowired' annotation types, to be used on constructors, fields,
* setter methods and arbitrary config methods.
* The default autowired annotation type is the Spring-provided
* {@link Autowired} annotation, as well as {@link Value}.
*
This setter property exists so that developers can provide their own
* (non-Spring-specific) annotation types to indicate that a member is
* supposed to be autowired.
*/
public void setAutowiredAnnotationTypes(Set> autowiredAnnotationTypes) {
Assert.notEmpty(autowiredAnnotationTypes, "'autowiredAnnotationTypes' must not be empty");
this.autowiredAnnotationTypes.clear();
this.autowiredAnnotationTypes.addAll(autowiredAnnotationTypes);
}
具体SpringIocProvider代码请查看项目源码,主要看下面这段代码:
public class SpringIocProvider implements IocProvider, Ioc {
protected ApplicationContext applicationContext;
@Override
public Ioc create(NutConfig config, String[] args) {
if (config == null || Lang.length(args) > 0)
applicationContext = new ClassPathXmlApplicationContext(args);
else
applicationContext = (ApplicationContext) config.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
return this;
}
@SuppressWarnings("unchecked")
@Override
public T get(Class classZ) throws IocException {
InjectName injectName = classZ.getAnnotation(InjectName.class);
if (injectName != null && !Strings.isBlank(injectName.value()))
return (T) applicationContext.getBean(injectName.value());
return (T) applicationContext.getBean(applicationContext.getBeanNamesForType(classZ)[0]);
}
}
从以上代码可以看出,该create和get方法会返回一个applicationContext,也就是说,Nutz Ioc如果是使用该iocProvider去创建,那么Nutz 在查找bean的时候将会从spring的上下文中去查找了,从而达到了我们想要的spring管理bean的效果,可对比Nutz原生ComboIocProvider.
在主模块声明SpringIocProvider
在MainModule上加入@IocBy(type=SpringIocProvider.class,args={})
@ChainBy(args="conf/mvc/evanshare-mvc-chain.js")
@SetupBy(value=MainSetup.class)
//@IocBy(type=CustomComboIocProvider.class,args={"*js","conf/ioc/","*anno","com.evanshare","*tx"})
@IocBy(args = {} ,type = SpringIocProvider.class)
@Modules(scanPackage=true)
@Fail("jsp:jsp.500")
@Localization(value="msg/", defaultLocalizationKey="zh-CN")
public class MainModule {
}
意味着nutz的所有bean都将会从spring上下文中查找
为了使项目中原有nutz IOC注解标识的类能交给spring容器管理,我们需要做一些小的改动,就是在原注解上加入spring的注解@Component,这样的话,spring就会帮我们将这些类纳入到spring容器管理.需要重写的注解有:@Inject、@IocBean、@InjectName,以下取IocBean作为示例,其他同理。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Component
public @interface IocBean {
在com.evanshare包下,新建Spring相关Controller: SpUserController、SpSqlController,新建Spring相关Service:SpUserService、SpSqlService
作用
SpUserController:基于spring实现,用于用户操作相关模块,作用与Nutz demo中的UserModule相同,用于用户登录、添加用户、用户列表展示
SpSqlController:基于Spring实现,用于Sql查询相关模块,作用与Nutz demo中的SqlController中的SqlModule相同,用于根据sql导出报表
SpUserService:基于Spring实现,用于用户相关处理逻辑,
与Nutz demo中的UserService作用相同,可互相替换使用SpSqlService:基于Spring实现,用于sql相关处理逻辑,
与Nutz demo中的SqlService作用相同,可互相替换使用
SpUserController
/**
*
* 基于spring实现的Sql Controller
* Created by liangyh on 2017/1/24.
* Email:[email protected]
*/
@Controller
public class SpSqlController {
@Autowired
private SpSqlService spSqlService;
//注入nutz service
@Inject
private SqlService sqlService;
/**
* 跳转到sql查询页面
* @return
*/
@RequestMapping(value = "/sql/sql_query")
public String forwardToSqlQuery(){
return "sql/sql_query";
}
/**
* 根据自定义sql到处报表
* @param querySql
* @param resp
* @throws Exception
*/
@RequestMapping("/sql/exportData")
public void exportSqlData(@RequestParam(value = "querySql",required = false) String querySql, HttpServletResponse resp) throws Exception {
spSqlService.exportExcelBySql(querySql,resp);
//调用Nutz service去到处报表
// sqlService.exportExcelBySql(querySql,resp);
}
SpUserController
/**
* 基于spring实现的用户controller
* Created by liangyh on 2017/1/28.
* Email:[email protected]
*/
@RestController
public class SpUserController {
@Autowired
private SpUserService spUserService;
//注入nutz的service
@Inject
private UserService userService;
/**
* 用户登录
*
* @param name
* @param password
* @param session
* @return
*/
@RequestMapping("/user/login")
public String login(@RequestParam("username") String name, @RequestParam("password") String password, HttpSession session) {
User user = spUserService.findByNameAndPsw(name,password);
if (user == null) {
return "/page/error/500";
} else {
session.setAttribute("user", user);
return "jsp/common/index_menu";
}
}
/**
* 获取用户列表
*
* @param session
* @return
*/
@RequestMapping("/user/list")
public String list(@RequestParam(value = "pageNum",required = false) String pageNum,@RequestParam(value = "numPerPage",required = false) String numPerPage ,@RequestParam(value = "keyword",required = false) String keyword,HttpSession session,HttpServletRequest req) {
Integer page = StringUtils.isNotBlank(pageNum)?Integer.parseInt(pageNum):0;
Integer perPageNum = StringUtils.isNotBlank(numPerPage)?Integer.parseInt(numPerPage):0;
//调用Nutz service获取用户列表
// QueryResult result = userService.getUserList(page, perPageNum,keyword);
QueryResult result = spUserService.getUserList(page, perPageNum,keyword);
List users = result.getList(User.class);
PagerModel pager = new PagerModel();
pager.setCurrentPage(page);
pager.setNumPerPage(perPageNum);
pager.setPageNumShown(result.getPager().getPageSize());
pager.setTotalCount(result.getPager().getRecordCount());
pager.setPageSize(result.getPager().getPageSize());
req.setAttribute("users", users);
req.setAttribute("pager", pager);
return "user/user_list";
}
SpUserService
/**
* 基于spring 的service
* Email:[email protected]
*/
@Service
public class SpUserService {
@Autowired
private Dao dao;
/**
* 获取用户列表
* @param pageNumber
* @param pageSize
* @param keyword
* @return
*/
public QueryResult getUserList(int pageNumber,int pageSize,String keyword){
if(StringUtils.isEmpty(keyword)){
keyword = "";
}
Pager pager = dao.createPager(pageNumber, pageSize);
List userList =dao.query(User.class, Cnd.where("name","like","%"+keyword+"%"), pager);
return new QueryResult(userList,pager);
}
/**
* 根据姓名和密码查找用户
* @param name
* @param password
* @return
*/
public User findByNameAndPsw(String name,String password){
return dao.fetch(User.class, Cnd.where("name", "=", name).and("password", "=", password));
}
public void print(){
System.out.println("I AM NUTZ SERVICE");
}
/**
* 获取用户总数
* @return
*/
public Integer getTotalUserCount(){
return dao.count(User.class);
}
/**
* 添加用户
* @param user
* @return
*/
public User addUser(User user) {
user.setCreateTime(new Date());
user.setUpdateTime(new Date());
return dao.insert(user);
}
}
SpSqlService
/**
* Created by liangyh on 2017/1/28.
* Email:[email protected]
*/
@Service
public class SpSqlService {
private final Logger logger = Logger.getLogger(SqlModule.class);
@Autowired
private Dao dao;
/**
* 根据自定义sql导出报表
* @param insql 自定义sql
* @param resp 响应实体
* @throws SQLException
* @throws IOException
* @throws FileNotFoundException
* @throws JSQLParserException
*/
public void exportExcelBySql(String insql,HttpServletResponse resp)
throws Exception {
// 11:创建一个excel
SXSSFWorkbook book = new SXSSFWorkbook(1000);
java.sql.ResultSet rs = null;
List tables = getTables(insql);
DataSource datasource = Mvcs.getIoc().get(DruidDataSource.class, "dataSource");
Connection conn = null;
Statement st =null;
try {
conn = datasource.getConnection();
// 3:声明st
double maxRowNum;
int sheets;
ResultSetMetaData rsmd;
int cols;
long startTime;
long endTime;
st = conn.createStatement();
//结果集总行数
double totalcount = 137713;
/* excel单表最大行数是65535 */
maxRowNum = 60000;
sheets = (int) Math.ceil(totalcount / maxRowNum);
rs = st.executeQuery(insql);
// 3:rsmd
rsmd = (ResultSetMetaData) rs.getMetaData();
// 4:分析一共多少列
cols = rsmd.getColumnCount();
startTime = System.currentTimeMillis();
//写入excel文件数据信息
for (int i = 0; i < sheets; i++) {
logger.info("<=======正在导出报表=========>");
// 12:创建sheet
SXSSFSheet sheet = book.createSheet(" " + (i + 1) + " ");
// 13:创建表头
SXSSFRow row = sheet.createRow(0);
for (int j = 0; j < cols; j++) {
String cname = rsmd.getColumnName(j + 1);
SXSSFCell cell = row.createCell(j);
cell.setCellValue(cname);
}
// 显示数据
for (int t = 0; t < maxRowNum; t++) {
if (!rs.next()) {
break;
}
// 14:保存数据
row = sheet.createRow(sheet.getLastRowNum() + 1);
for (int j = 0; j < cols; j++) {
SXSSFCell cell = row.createCell(j);
cell.setCellValue(rs.getString(j + 1));
}
}
}
resp.reset();
resp.setContentType("multipart/form-data"); //自动识别
resp.setHeader("Content-Disposition", "attachment;filename=data.xls");
book.write(resp.getOutputStream());
endTime = System.currentTimeMillis();
logger.info("导出报表所用时间为:" + (endTime - startTime));
}catch (Exception ex){
logger.info(String.format("导出报表异常,Sql=【%s】,异常信息{}",insql),ex);
}finally {
if(book!=null){
book.close();
}
if(rs!=null){
rs.close();
}
if(st!=null){
st.close();
}
if(conn!=null){
conn.close();
}
}
}
/**
* 获取对应的表
* @param insql 自定义sql
* @return
* @throws JSQLParserException
*/
private List getTables(String insql) throws JSQLParserException{
net.sf.jsqlparser.statement.Statement statement = CCJSqlParserUtil.parse(insql);
Select selectStatement = (Select)statement;
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List result = tablesNamesFinder.getTableList(selectStatement);
return result;
}
猜想
基于Spring实现的SpUserService与基于Nutz实现的UserService相互替换使用,Spring实现的Controller既然调用spring的service,也能调用nutz的service(SpSqlService与SqlService同理)
在org.nutz下创建SpringDaoRunner,SpringDaoRunner继承NutDaoRunner,重写了_run方法,通过DataSourceUtils去获取connection,使得事务可以统一交给Spring去管理
public class SpringDaoRunner extends NutDaoRunner {
@Override
public void _run(DataSource dataSource, ConnCallback callback) {
Connection con = DataSourceUtils.getConnection(dataSource);
try {
callback.invoke(con);
} catch (Exception e) {
if (e instanceof RuntimeException)
throw (RuntimeException) e;
else
throw new RuntimeException(e);
} finally {
DataSourceUtils.releaseConnection(con, dataSource);
}
}
}
回顾之前的nutDao.xml配置,该配置作用是使用spring管理事务
<bean id="dao" class="org.nutz.dao.impl.NutDao">
<property name="dataSource" ref="dataSource"/>
<property name="runner" ref="springDaoRunner"/>
bean>
<bean id="springDaoRunner" class="org.nutz.integration.spring.SpringDaoRunner">
bean>
在ResourceFilter中的init方法中,新增对请求路径中/spring/过滤
/**
* 对资源文件进行过滤
* @author lyh
*
*/
public class ResourceFilter extends NutFilter {
protected Set prefixs = new HashSet();
@Override
public void init(FilterConfig conf) throws ServletException {
super.init(conf);
prefixs.add(conf.getServletContext().getContextPath() + "/druid/");
//对请求路径带有/spring/过滤
prefixs.add(conf.getServletContext().getContextPath() + "/spring/");
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
if (req instanceof HttpServletRequest) {
String uri = ((HttpServletRequest) req).getRequestURI();
for (String prefix : prefixs) {
if (uri.startsWith(prefix)) {
chain.doFilter(req, resp);
return;
}
}
}
super.doFilter(req, resp, chain);
}
}
为什么要过滤?
为了应对基于spring controller请求可以走Spring Mvc,所以在ResourceFilter那进行了过滤,避免被NutFilter进行拦截和调用到Nutz MVC的处理方法,这样请求可以顺利进入DispatcherServlet
在原Nutz demo菜单基础上,新增两个指向Spring处理类的菜单,只想Spring处理类的Url前缀都加上/spring/,用于区分请求是走Spring MVC还是Nutz Mvc
<ul class="tree treeFolder">
<li><a href="<%=request.getContextPath() %>/user/list" target="navTab" rel="user_list">用户列表a>li>
ul>
li>
ul>
<ul class="tree treeFolder">
<li><a href="<%=request.getContextPath() %>/sql/sql_query" target="navTab" rel="sql_query">SQL查询a>li>
ul>
li>
ul>
<ul class="tree treeFolder">
<li><a href="<%=request.getContextPath() %>/spring/user/list" target="navTab" rel="spring_user_list">基于Srping实现用户列表a>li>
ul>
li>
ul>
<ul class="tree treeFolder">
<li><a href="<%=request.getContextPath() %>/spring/sql/sql_query" target="navTab" rel="springsql_query">基于Spring实现SQL查询a>li>
ul>
li>
ul>
测试案例
- 浏览器输入:localhost:8080/evanshare,跳转到登陆页面,输入:admin 123456
- 点击资管管理-用户列表
- 点击资管管理-Sql查询
- 点击资管管理-基于Springle实现用户列表
- 点击资管管理-基于Springle实现Sql查询
预期结果
- 能正常登陆,进入管理后台
- 能正常打开用户列表
- 输入select * from tb_at_user ,能正常导出报表
- 能正常打开用户列表
- 输入select * from tb_at_user ,能正常导出报表
在com.evanshare.spring.service包下,新建NutzTransTestService
package com.evanshare.spring.service;
import org.nutz.dao.Dao;
import org.springframework.transaction.annotation.Transactional;
/**
* 该类用于事务测试用途
* Created by liangyh on 2017/1/28.
* Email:[email protected]
*/
public class NutzTransTestService {
protected Dao dao;
public void setDao(Dao dao) {
this.dao = dao;
}
@Transactional(rollbackFor=Exception.class)
public void doUserClear() {
dao.clear("tb_at_user");
throw new RuntimeException();
}
}
在Test目录下com.evanshare.spring.service包新建NutzTransTestServiceTest
package com.evanshare.spring.service;
import org.junit.Test;
import org.nutz.dao.Dao;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.util.Assert;
import static org.junit.Assert.*;
/**
* NUtz事务测试
* Created by liangyh on 2017/1/28.
* Email:[email protected]
*/
public class NutzTransTestServiceTest extends Assert {
@Test
public void doUserClear() throws Exception {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
NutzTransTestService nutzTest = ctx.getBean("transTest", NutzTransTestService.class);
Dao dao = ctx.getBean("dao", Dao.class);
int count = dao.count("tb_at_user");
assertTrue(count > 0);
try {
nutzTest.doUserClear();
} catch (Exception e) {
// 里面主动抛出异常
}
assertEquals(count, dao.count("tb_at_user"));
ctx.close();
}
}
测试案例
- 执行NutzTransTestServiceTest下的doUserClear方法
预期结果
- NutzTransTestService有一个spring事务注解的方法,里面清除了t_user表,然后抛出了异常
- 日志中显示, spring的事务进行了rollback, 可以看到rollback等字眼
- 执行doUserClear前后的tb_at_user表记录数相同,所以事务真的是回滚了
执行日志如下:
22:17:31.483 [main] DEBUG org.nutz.dao.impl.sql.run.NutDaoExecutor - DELETE FROM tb_at_user
22:17:31.503 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback
22:17:31.503 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@2228db21]
22:17:31.511 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@2228db21] after transaction
22:17:31.511 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource
22:17:35.556 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource
22:17:35.556 [main] DEBUG org.nutz.dao.impl.sql.run.NutDaoExecutor - SELECT COUNT(*) FROM tb_at_user
22:17:35.558 [main] DEBUG org.springframework.jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource