- 浏览: 2469697 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (574)
- Book (62)
- Architecture (6)
- Java (39)
- Taobao (41)
- Distributed (4)
- Life (72)
- Database (7)
- Spring (16)
- Photography (15)
- Bicycle (41)
- Test (20)
- jBPM (8)
- Business (12)
- Movie (3)
- Ajax (15)
- Code (7)
- Eclipse (96)
- VIM (2)
- Music (6)
- Groovy (10)
- AutoHotKey (3)
- Dorado (10)
- Maven (7)
- Scrum (5)
- English (20)
- Financial (12)
- OSGi (3)
- Other (4)
- Tool (6)
- Browser (1)
- PPT (1)
- Project Management (4)
- Agile (6)
- Nosql (1)
- Search engine (6)
- Shell (2)
- Open Source (4)
- Storm (10)
- Guava (3)
- Baby (1)
- netty (1)
- Algorithm (1)
- Linux (1)
- Python (2)
最新评论
-
roy2011a:
https://github.com/ebottabi/sto ...
storm的序列化问题及与spring的结合方式 -
roy2011a:
能抗能打 写道哥们儿,你好!能共享下那个storm与sprin ...
storm的序列化问题及与spring的结合方式 -
Alick1:
兄弟,你之前是不是在深圳的正阳公司呆过啊?
storm的ack和fail -
liuleixwd:
先点个赞,写的非常好!有个问题请教下,如果我再bolt里不用e ...
storm的ack和fail -
yao-dd:
solr的facet查询
最近对ibatis2.1.0.256这个版本(大部分应用都是用该版本)的源代码以及spring的SqlMapClientTemplate相关的代码看了下. 这个版本的ibatis有一个问题, 就是如果执行sql出错的话, 出错信息非常不友好, 连最基本的sql和paramter都没有, 给查找问题带来不小的麻烦, 本想看看代码能进行一下定制, 由于版本太老, 扩展性太差了, 基本没法扩展, 最后被迫放弃.
题外话, 现在ibatis都已经出到3.0了(为什么我们还不升级?)
发现spring和ibatis的代码风格相差还是很大的. 两相比较就可以看出差距来, 同时也对di有了进一步的理解.
在spring中几乎将所有的类都当一个个bean来看待, 因此所有的依赖要么是通过构造函数注入, 要么通过setter方式注入. 而ibatis则明显没有这样的概念, 很多地方对其他类的依赖都是直接在构造函数中new出来的, 这样给定制带来了麻烦, 可以说, 基本上是没法对ibatis进行扩展. 因此我们可以认为ibatis是一个非常封闭的dao框架(当然后续版本的ibatis有所改善).
基于ibatis这样的架构, 导致spring对ibatis的扩展也非常有限.其中最重要的类在我看来无非两个:SqlMapClientFactoryBean, SqlMapClientTemplate, 前者是一个典型的FactoryBean, 用来将ibatis纳入spring的ioc容器中管理. 这里我们可以借鉴一下spring自己的FactoryBean的做法. 一般的FactoryBean都要实现FactoryBean接口, 在getObject()方法中, 我们可以去具体实现到底要怎么创建所需要的bean, 当然简单的可以直接new, 一般情况下还需要借助其他的接口来获取所需要的bean, 比如这里还实现了InitializingBean接口, 即在bean的定义以及相关的依赖设置完毕之后, 调用了afterPropertiesSet()方法,此时便开始根据配置文件来构造SqlMapClient了, 在根据配置构造SqlMapClient的部分spring调用了ibatis的SqlMapClientBuilder这个构造器来处理的, 从这里开始, ibatis便关闭了我们灵活处理构造SqlMapClient的大门. 对内部的构造过程我们几乎完全无法控制, 如果希望有所改善, 必须复写大量的类.
SqlMapClientTemplate用来对ibatis的SqlMapClient进行包装, 以便在调用ibatis访问数据库的时候, 做一些手脚, 不过这里这里主要是为了将iBatis抛出的SqlException转换成spring统一的DataAccessException异常, 处理异常的CRUD统一入口代码如下:
因此如果你不喜欢spring的exception处理方式, 也可以不用它. 直接用SqlMapClient即可. spring之所以能将所有的操作归结到一个入口, 这个与SqlMapClientCallback接口的使用有很大的关系. 这里我们可以见到spring里面典型的接口->匿名类的用法.spring对匿名类用的那可是相当的多, 这个也是我们值得借鉴的一个地方, 不过匿名类不可滥用, 一般来说匿名类都是很简单的调用(10行左右), 如果有大段的代码调用, 那么匿名类就不合适了, 这里基本都很简单, 都是一个简单的转发, 即将对SqlMapClientTemplate的调用转发给SqlMapClient, 比如下面的代码:
另外这里需要说一下的是, SqlMapClient是ibatis用来访问数据库的一个入口, 即一个SqlMapClient表示一个数据库. 所有的CRUD操作都要经过这个接口.
下面我们就来历数ibatis的种种让人崩溃的做法.
ibatis主要做了两件事, 一个就是解析sqlmap配置文件, 就是我们配置的一个sql语句和javabean与表字段之间的映射关系, 这里我们不做多展开.另外一个就是调用jdbc驱动, 执行数据库操作.
这里我们先从解析开始.ibatis的解析入口是SqlMapClientBuilder类. 这个builder类将解析的过程封装到了SqlMapConfigParser中.代码如下:
万恶的new出现了, 这里我们已经没法对parser进行控制了.
接着我们来看看SqlMapConfigParser构造器:
又是大量的new操作, 再一次无语了.
而且只有get方法, 没有set方法, 再一次关闭了注入依赖的可能.
在SqlMapConfigParser中有两个类需要注意, 即SqlMapExecutorDelegate和SqlMapClientImpl, SqlMapExecutorDelegate这个封装了从配置文件解析的一些东东, SqlMapClientImpl所需要的一些配置就是从SqlMapExecutorDelegate获取的, 因此SqlMapClientImpl注入了SqlMapExecutorDelegate, 并在需要的时候转调SqlMapExecutorDelegate, SqlMapClientImpl因为是操作数据库的入口, 还需要负责管理事务, session等, 里面有一个很重要的东东:SqlExecutor, 这个也是在SqlMapExecutorDelegate中实现的.如同名字一样, 它是用来执行sql语句的.这个似乎是我们用来控制底层数据库操作的一个入口, 如果能进行配置将是一件幸福的事儿.它是在SqlMapExecutorDelegate中创建的, 很遗憾, 也是在构造函数中new出来的:
SqlExecutor是一个具体类, 而且没有setter方法, 再一次崩溃.
看到这里, 我们几乎完全失去了对ibatis的底层操作进行控制的可能, 我们继续看SqlExecutor是如何被使用的.
所有的sql语句的执行都是封装在ibatis的一个个MappedStatement中, 大部分的逻辑都是放在GeneralStatement中, 这里我们拿出查询的处理代码:
在这段代码中, 有sql语句, 参数的校验, 另外就是调用sqlExecutor执行了, 基本上很简单, 这里不得不暴露一个ibatis对异常处理非常糟糕的做法, 就是隐藏信息中,没有包含出错的sql语句和对应的参数, 我们仅仅只能知道哪个sqlmap文件配置的哪个sql有问题, 但是不知道最终的sql是个什么样子. 而我们实际开发过程中很大一部分都是因为bad sql导致的. 不能呈现bad sql 自然给解决问题带来不小的难度, 我曾经想了不少问题都没法解决.
另外, 对于在日志中输入正常的sql, 参数, 执行结果的做法, 这里ibatis用到了动态代理.比如这个PreparedStatementLogProxy类:
它的log很特殊是跟java.sql的一些类相关的:
因此很多人在问如何打印日志, 实际上就是要将java.sql这个package下相关类的DEBUG开关打开即可.
题外话, 现在ibatis都已经出到3.0了(为什么我们还不升级?)
发现spring和ibatis的代码风格相差还是很大的. 两相比较就可以看出差距来, 同时也对di有了进一步的理解.
在spring中几乎将所有的类都当一个个bean来看待, 因此所有的依赖要么是通过构造函数注入, 要么通过setter方式注入. 而ibatis则明显没有这样的概念, 很多地方对其他类的依赖都是直接在构造函数中new出来的, 这样给定制带来了麻烦, 可以说, 基本上是没法对ibatis进行扩展. 因此我们可以认为ibatis是一个非常封闭的dao框架(当然后续版本的ibatis有所改善).
基于ibatis这样的架构, 导致spring对ibatis的扩展也非常有限.其中最重要的类在我看来无非两个:SqlMapClientFactoryBean, SqlMapClientTemplate, 前者是一个典型的FactoryBean, 用来将ibatis纳入spring的ioc容器中管理. 这里我们可以借鉴一下spring自己的FactoryBean的做法. 一般的FactoryBean都要实现FactoryBean接口, 在getObject()方法中, 我们可以去具体实现到底要怎么创建所需要的bean, 当然简单的可以直接new, 一般情况下还需要借助其他的接口来获取所需要的bean, 比如这里还实现了InitializingBean接口, 即在bean的定义以及相关的依赖设置完毕之后, 调用了afterPropertiesSet()方法,此时便开始根据配置文件来构造SqlMapClient了, 在根据配置构造SqlMapClient的部分spring调用了ibatis的SqlMapClientBuilder这个构造器来处理的, 从这里开始, ibatis便关闭了我们灵活处理构造SqlMapClient的大门. 对内部的构造过程我们几乎完全无法控制, 如果希望有所改善, 必须复写大量的类.
SqlMapClientTemplate用来对ibatis的SqlMapClient进行包装, 以便在调用ibatis访问数据库的时候, 做一些手脚, 不过这里这里主要是为了将iBatis抛出的SqlException转换成spring统一的DataAccessException异常, 处理异常的CRUD统一入口代码如下:
public Object execute(SqlMapClientCallback action) throws DataAccessException { SqlMapSession session = this.sqlMapClient.openSession(); Connection ibatisCon = null; try { Connection springCon = null; try { .... return action.doInSqlMapClient(session); } catch (SQLException ex) { throw getExceptionTranslator().translate("SqlMapClient operation", null, ex); } finally { DataSourceUtils.releaseConnection(springCon, getDataSource()); } } finally { if (ibatisCon == null) { session.close(); } } }
因此如果你不喜欢spring的exception处理方式, 也可以不用它. 直接用SqlMapClient即可. spring之所以能将所有的操作归结到一个入口, 这个与SqlMapClientCallback接口的使用有很大的关系. 这里我们可以见到spring里面典型的接口->匿名类的用法.spring对匿名类用的那可是相当的多, 这个也是我们值得借鉴的一个地方, 不过匿名类不可滥用, 一般来说匿名类都是很简单的调用(10行左右), 如果有大段的代码调用, 那么匿名类就不合适了, 这里基本都很简单, 都是一个简单的转发, 即将对SqlMapClientTemplate的调用转发给SqlMapClient, 比如下面的代码:
public Object queryForObject(final String statementName, final Object parameterObject) throws DataAccessException { return execute(new SqlMapClientCallback() { public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException { return executor.queryForObject(statementName, parameterObject); } }); }
另外这里需要说一下的是, SqlMapClient是ibatis用来访问数据库的一个入口, 即一个SqlMapClient表示一个数据库. 所有的CRUD操作都要经过这个接口.
下面我们就来历数ibatis的种种让人崩溃的做法.
ibatis主要做了两件事, 一个就是解析sqlmap配置文件, 就是我们配置的一个sql语句和javabean与表字段之间的映射关系, 这里我们不做多展开.另外一个就是调用jdbc驱动, 执行数据库操作.
这里我们先从解析开始.ibatis的解析入口是SqlMapClientBuilder类. 这个builder类将解析的过程封装到了SqlMapConfigParser中.代码如下:
public static SqlMapClient buildSqlMapClient(Reader reader) { // return new XmlSqlMapClientBuilder().buildSqlMap(reader); return new SqlMapConfigParser().parse(reader); }
万恶的new出现了, 这里我们已经没法对parser进行控制了.
接着我们来看看SqlMapConfigParser构造器:
public SqlMapConfigParser() { this(null, null); } public SqlMapConfigParser(XmlConverter sqlMapConfigConv, XmlConverter sqlMapConv) { super(new Variables()); parser.setValidation(true); parser.setEntityResolver(new SqlMapClasspathEntityResolver()); vars.sqlMapConfigConv = sqlMapConfigConv; vars.sqlMapConv = sqlMapConv; vars.delegate = new SqlMapExecutorDelegate(); vars.typeHandlerFactory = vars.delegate.getTypeHandlerFactory(); vars.client = new SqlMapClientImpl(vars.delegate); ... }
又是大量的new操作, 再一次无语了.
而且只有get方法, 没有set方法, 再一次关闭了注入依赖的可能.
在SqlMapConfigParser中有两个类需要注意, 即SqlMapExecutorDelegate和SqlMapClientImpl, SqlMapExecutorDelegate这个封装了从配置文件解析的一些东东, SqlMapClientImpl所需要的一些配置就是从SqlMapExecutorDelegate获取的, 因此SqlMapClientImpl注入了SqlMapExecutorDelegate, 并在需要的时候转调SqlMapExecutorDelegate, SqlMapClientImpl因为是操作数据库的入口, 还需要负责管理事务, session等, 里面有一个很重要的东东:SqlExecutor, 这个也是在SqlMapExecutorDelegate中实现的.如同名字一样, 它是用来执行sql语句的.这个似乎是我们用来控制底层数据库操作的一个入口, 如果能进行配置将是一件幸福的事儿.它是在SqlMapExecutorDelegate中创建的, 很遗憾, 也是在构造函数中new出来的:
public SqlMapExecutorDelegate() { mappedStatements = new HashMap(); cacheModels = new HashMap(); resultMaps = new HashMap(); parameterMaps = new HashMap(); requestPool = new ThrottledPool(RequestScope.class, DEFAULT_MAX_REQUESTS); sessionPool = new ThrottledPool(SessionScope.class, DEFAULT_MAX_SESSIONS); sqlExecutor = new SqlExecutor(); typeHandlerFactory = new TypeHandlerFactory(); dataExchangeFactory = new DataExchangeFactory(typeHandlerFactory); }
SqlExecutor是一个具体类, 而且没有setter方法, 再一次崩溃.
看到这里, 我们几乎完全失去了对ibatis的底层操作进行控制的可能, 我们继续看SqlExecutor是如何被使用的.
所有的sql语句的执行都是封装在ibatis的一个个MappedStatement中, 大部分的逻辑都是放在GeneralStatement中, 这里我们拿出查询的处理代码:
protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults) throws SQLException { ErrorContext errorContext = request.getErrorContext(); errorContext.setActivity("preparing the mapped statement for execution"); errorContext.setObjectId(this.getId()); errorContext.setResource(this.getResource()); try { parameterObject = validateParameter(parameterObject); Sql sql = getSql(); errorContext.setMoreInfo("Check the parameter map."); ParameterMap parameterMap = sql.getParameterMap(request, parameterObject); errorContext.setMoreInfo("Check the result map."); ResultMap resultMap = sql.getResultMap(request, parameterObject); request.setResultMap(resultMap); request.setParameterMap(parameterMap); errorContext.setMoreInfo("Check the parameter map."); Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject); errorContext.setMoreInfo("Check the SQL statement."); String sqlString = sql.getSql(request, parameterObject); errorContext.setActivity("executing mapped statement"); errorContext.setMoreInfo("Check the SQL statement or the result map."); RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler); sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback); errorContext.setMoreInfo("Check the output parameters."); if (parameterObject != null) { postProcessParameterObject(request, parameterObject, parameters); } errorContext.reset(); sql.cleanup(request); notifyListeners(); } catch (SQLException e) { errorContext.setCause(e); throw new NestedSQLException(errorContext.toString(), e.getSQLState(), e.getErrorCode(), e); } catch (Exception e) { errorContext.setCause(e); throw new NestedSQLException(errorContext.toString(), e); } } protected void sqlExecuteQuery(RequestScope request, Connection conn, String sqlString, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException { getSqlExecutor().executeQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback); }
在这段代码中, 有sql语句, 参数的校验, 另外就是调用sqlExecutor执行了, 基本上很简单, 这里不得不暴露一个ibatis对异常处理非常糟糕的做法, 就是隐藏信息中,没有包含出错的sql语句和对应的参数, 我们仅仅只能知道哪个sqlmap文件配置的哪个sql有问题, 但是不知道最终的sql是个什么样子. 而我们实际开发过程中很大一部分都是因为bad sql导致的. 不能呈现bad sql 自然给解决问题带来不小的难度, 我曾经想了不少问题都没法解决.
另外, 对于在日志中输入正常的sql, 参数, 执行结果的做法, 这里ibatis用到了动态代理.比如这个PreparedStatementLogProxy类:
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { try { if (EXECUTE_METHODS.contains(method.getName())) { if (log.isDebugEnabled()) { log.debug("{pstm-" + id + "} PreparedStatement: " + removeBreakingWhitespace(sql)); log.debug("{pstm-" + id + "} Parameters: " + getValueString()); log.debug("{pstm-" + id + "} Types: " + getTypeString()); } clearColumnInfo(); if ("executeQuery".equals(method.getName())) { ResultSet rs = (ResultSet) method.invoke(statement, params); return ResultSetLogProxy.newInstance(rs); } else { return method.invoke(statement, params); } } else if (SET_METHODS.contains(method.getName())) { if ("setNull".equals(method.getName())) { setColumn(params[0], null); } else { setColumn(params[0], params[1]); } return method.invoke(statement, params); } else if ("getResultSet".equals(method.getName())) { ResultSet rs = (ResultSet) method.invoke(statement, params); return ResultSetLogProxy.newInstance(rs); } else { return method.invoke(statement, params); } } catch (Throwable t) { throw ClassInfo.unwrapThrowable(t); } }
它的log很特殊是跟java.sql的一些类相关的:
private static final Log log = LogFactory.getLog(PreparedStatement.class);
因此很多人在问如何打印日志, 实际上就是要将java.sql这个package下相关类的DEBUG开关打开即可.
发表评论
-
spring中map的定义, 包括value为class的定义
2013-03-11 22:26 37<bean id="fieldM ... -
spring的xml string applicationcontext实现
2013-03-06 07:26 2362这里有两种实现方式: import org.springf ... -
对多数据源进行aop声明式事务管理
2009-11-04 18:57 4979当在对数据库表进行横向切分(将一个表的数据拆分为到多个数据库中 ... -
编程方式实现sping bean延迟初始化
2009-10-09 10:12 2757在实际开发中, 碰到如下需求场景: 在线上需要spring容 ... -
使用spring aop 简化mock设计实现
2009-09-28 20:42 2840有时候为了去掉对外部系统的依赖, 我们需要针对外部依赖的接口创 ... -
自定义logger注解, 简化log4j的配置
2009-09-28 20:20 6043上次在参加支付宝架构培训的时候, 看到他们框架中有一个不错的对 ... -
使用spring aop碰到的几个问题及解决办法
2009-09-28 20:09 3939这里的问题只是针对spring 2.0.7 至于其他版本, ... -
本人常用的两种spring aop实现方式
2009-09-28 10:53 3886备忘一下, 基本的用法包括在配置文件中配置pointcut, ... -
spring aspectj的返回值
2009-05-31 08:40 4303最近使用到了基于aspectj的spring aop, aop ... -
spring map bean定义
2009-05-21 12:20 43473一般我们知道在property属性里面定义一个无id的map是 ... -
spring知识总结
2009-05-09 22:11 3253bean属性及构造器参数:直接量(基本类型、Strings类型 ... -
spring @Configration扩展使用一例
2009-02-05 22:03 5795最近对注解了解的比较多, 也在实际项目中实战了一把, 同时对s ... -
spring 注解学习笔记
2009-02-05 21:20 9811spring 注解讲的最详细的文章: http://www.i ... -
spring加载配置文件
2008-12-11 09:27 2349ClassPathXmlApplicationContext ... -
Spring动态数据源路由实现
2008-09-24 09:32 16823简单的翻译, 也算是一篇笔记. 原文:http://blog. ... -
spring aop学习笔记
2008-02-19 14:08 15586理解spring aop的路径:最初级的做法是通过使用代理将业 ...
相关推荐
很好的spring+ibatis事务的配置文档.
"Struts2+Spring+Ibatis+MySQL" 是一个经典的Java Web开发框架组合,用于构建高效、可扩展的企业级应用程序。这个组合集成了强大的MVC(Model-View-Controller)框架Struts2、依赖注入与面向切面编程的Spring框架、...
Struts2+Spring+Hibernate和Struts2+Spring+Ibatis是两种常见的Java Web应用程序集成框架,它们分别基于ORM框架Hibernate和轻量级数据访问框架Ibatis。这两种框架结合Spring,旨在提供一个强大的、可扩展的、易于...
在IT行业中,构建高效、可扩展的Web应用是至关重要的,而"Maven搭建SpringMVC+Spring+Ibatis"的组合则提供了一种强大的解决方案。本文将深入探讨这些技术及其集成,帮助你理解和掌握如何利用它们来构建现代化的Java ...
这个“struts2+spring+iBatis框架包”集成了这三个框架,使得开发者能够快速构建基于MVC(Model-View-Controller)模式的Web应用。 Struts2作为MVC框架,负责处理应用程序的控制逻辑。它通过Action类和配置文件定义...
总的来说,"spring+ibatis+oracle分页缓存源码"项目展示了如何在Spring管理的环境中,利用iBatis和Oracle数据库实现高效的数据分页和缓存策略。通过理解和实践这些技术,开发者可以构建出更加健壮、响应快速的Web...
在Struts+Spring+iBATIS的架构中,iBATIS负责与数据库交互,通过SQL映射文件(sqlmap.xml)定义SQL查询、插入、更新和删除操作。它与Spring整合后,可以在Spring的事务管理下执行数据库操作,确保数据的一致性。 在...
JSF+Spring+Ibatis示例,对学习JAVA企业应用开发有巨大的帮助!
maven3+struts2+spring+ibatis,本来是用maven3+struts2+spring+hibernate但考虑到hibernate在多表级联查询的时候执行效率不高,所以改用性能更好不过sql比较麻烦的的ibatis,本项目只有登录和插入数据,仅供参考: ...
在Java Web开发中,Spring、Struts2和iBatis是三个非常重要的框架,它们各自在不同的层面上提供了强大的功能。Spring是一个全面的后端应用框架,提供了依赖注入(DI)、面向切面编程(AOP)、事务管理等功能;Struts...
这个"struts+spring+ibatis的Demo"压缩包文件提供了这三个框架集成使用的示例代码,旨在帮助开发者理解和学习如何将它们有效地结合在一起。 **Struts 2框架** Struts 2是一个基于MVC设计模式的Web应用框架,它继承...
标题中的"spring+struts+ibatis"是指一种经典的Java Web开发架构,也被称为SSM框架。这个架构结合了Spring框架、Struts2框架和iBatis(现在称为MyBatis)来构建高效且可维护的Web应用。下面将详细阐述这三个框架以及...
"webwork+spring+ibatis" 的实例通常会展示如何将这三个框架集成到一个完整的Web项目中。这个实例可能包含以下部分: 1. **环境配置**:安装和配置Java开发环境,如JDK,以及相关的开发工具,如IDEA或Eclipse。 2. ...
Struts+Spring+Ibatis环境配置(一) - zwjxf的专栏 - 博
struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例 struts2 + spring + ibatis 实例
一个简单的spring+struts+ibatis整合的实例,实现了用户登录,用户登录成功则显示欢迎信息,失败则显示用户名或密码错误,该实例非常简单基础,特别适合新人学习,工程包含了必要的资源包,部署到服务器中及可运行,...
在"struts+spring+ibatis"的整合应用中,Spring通常作为核心,管理Struts的Action以及iBatis的数据访问对象(DAO)。Struts处理HTTP请求,将请求转发给Spring管理的Action,Action再通过Spring的依赖注入获取到...
各种系统架构图及其简介(Spring+IBatis+Struts1+Struts2+Hibernat)
这个"Struts+Spring+Ibatis示例"提供了一个基础的整合应用,帮助开发者理解这三者如何协同工作。 **Struts** 是一个基于 Model-View-Controller (MVC) 设计模式的Java web框架,主要负责处理用户请求,控制应用程序...
Struts2+Spring+Ibatis整合的简单人事管理系统 没分了,转载过来的,有需要的看看吧,我觉得不错~~