`
huangcongweng
  • 浏览: 3422 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

spring jdbc 和 iBatis 操作数据库性能分析(一)

阅读更多
基本上每一个APP都会和DB打交道,执行CURD操作,对于用java编写的APP来说CURD的实现方案有很多,但基本上分为两种,ORM和非ORM,。Hiberante为业界所推荐的ORM解决方案,还有很多,自己度娘一下,今天的主题是NO-ORM,不对ORM做讨论。
现在的问题是,如果在项目中不用ORM来做CURD操作就意味着要自己在项目中封装CURD操作。这种情况下,我们会做出如下几种选择:
A 开发人员直接使用Connection,PreparedStatement,Statement,ResultSet等jdbc核心接口和DB交互,当然,如果你的项目很小,扩张的可能性小,用户的交互性不强的情况下,以上做法其实是很方便的 。然而...如果项目很大,需求变更大,扩展的可能性大,用户交互性强,以上方法会非常的让开发与维护人员蛋疼,都懂得!怎么办,请接着看。
B 由对JDBC接口精通的开发人员自己封装工具类,客户程序仅仅传一个connection或者datasource 对象还有一条sql语句和相应参数给工具类就OK了,和DB交互的那套丑陋又必不可少的样板代码交给工具类,这样做也行,但就看工具类的开发人员水平有多高 ,隐患的BUG会让客户程序员蛋疼!项目也会随之崩溃!如果项目里没有N人,怎么办,接着看。
C 我们所遇到的问题,国外的N人们早就深有体会,所以spring jdbc,ibatis,dbutils......就出现了,这些都是开源的。以上开源框架的出现就是为了解决A,B中出现的问题。OK,今天的主题是性能分析,不罗嗦了,直奔主题吧。

场景1 通过给定的SQL,从DB中查询数据

select * from student;
注:在以下的讨论中,都不考虑SQL分页的情况

用spring可以怎么做呢?
a,使用JdbcTemplate中的queryForList(String sql)方法,该方法签名如下

public List<Map<String, Object>> queryForList(String sql) throws DataAccessException {
return query(sql, getColumnMapRowMapper());
}

相应的接口描述如下:

/**
* Execute a query for a result list, given static SQL.
* <p>Uses a JDBC Statement, not a PreparedStatement. If you want to
* execute a static query with a PreparedStatement, use the overloaded
* {@code queryForList} method with {@code null} as argument array.
* <p>The results will be mapped to a List (one entry for each row) of
* Maps (one entry for each column using the column name as the key).
* Each element in the list will be of the form returned by this interface's
* queryForMap() methods.

* @param sql SQL query to execute
* @return an List that contains a Map per row
* @throws DataAccessException if there is any problem executing the query
* @see #queryForList(String, Object[])
*/
List<Map<String, Object>> queryForList(String sql) throws DataAccessException;

返回结果是以Map为元素的List,其中Map是以column name为key,column value为value。与该方法相对应的方法签名如下

/**
* Execute a query for a result Map, given static SQL.
* <p>Uses a JDBC Statement, not a PreparedStatement. If you want to
* execute a static query with a PreparedStatement, use the overloaded
* {@link #queryForMap(String, Object...)} method with {@code null}
* as argument array.
* <p>The query is expected to be a single row query; the result row will be
* mapped to a Map (one entry for each column, using the column name as the key).
* @param sql SQL query to execute
* @return the result Map (one entry for each column, using the
* column name as the key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws DataAccessException if there is any problem executing the query
* @see #queryForMap(String, Object[])
* @see ColumnMapRowMapper
*/
Map<String, Object> queryForMap(String sql) throws DataAccessException;
这个方法只是为了返回一条记录,其具体实现实现如下

public Map<String, Object> queryForMap(String sql) throws DataAccessException {
return queryForObject(sql, getColumnMapRowMapper());
}

public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, rowMapper);这个倒霉的方法会查询所有数据
return DataAccessUtils.requiredSingleResult(results);

}
DataAccessUtils.requiredSingleResult这个方法的实现个人感觉让人很纠结,实现如下

/**
* Return a single result object from the given Collection.
* <p>Throws an exception if 0 or more than 1 element found.
* @param results the result Collection (can be {@code null})
* @return the single result object
* @throws IncorrectResultSizeDataAccessException if more than one
* element has been found in the given Collection
* @throws EmptyResultDataAccessException if no element at all
* has been found in the given Collection
*/
public static <T> T requiredSingleResult(Collection<T> results) throws IncorrectResultSizeDataAccessException {
int size = (results != null ? results.size() : 0);
if (size == 0) {
throw new EmptyResultDataAccessException(1);
}
if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1, size);
}
return results.iterator().next();
}

requiredSingleResult()方法的目的就是为了验证记录数有且只有一条!
所以在使用queryForMap(String sql)方法要小心,如果你传了一条查询某张表所有数据的sql给这个方法,并且此表的数据非常大,那么恭喜你,最后你得到的结果是:我已经很努力了,但结果却是个屁!

因此,除非你你的查询结果只有一条记录,否则不要用Map<String, Object> queryForMap(String sql) 来查询数据

对于List<Map<String, Object>> queryForList(String sql),该方法的核心实现如下

public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
Map<String, Object> mapOfColValues = createColumnMap(columnCount);
for (int i = 1; i <= columnCount; i++) {
String key = getColumnKey(JdbcUtils.lookupColumnName(rsmd, i));
Object obj = getColumnValue(rs, i);有性能损耗的地方
mapOfColValues.put(key, obj);
}
return mapOfColValues;
}

个人感觉上述方法的实现在性能很接近纯JDBC操作了,先看下getColumnValue(rs, i)的最终实现

/**
......
*/
public static Object getResultSetValue(ResultSet rs, int index) throws SQLException {
Object obj = rs.getObject(index);
String className = null;
if (obj != null) {
className = obj.getClass().getName();
}
if (obj instanceof Blob) {
obj = rs.getBytes(index);
}
else if (obj instanceof Clob) {
obj = rs.getString(index);
}
else if (className != null &&
("oracle.sql.TIMESTAMP".equals(className) ||
"oracle.sql.TIMESTAMPTZ".equals(className))) {
obj = rs.getTimestamp(index);
}
else if (className != null && className.startsWith("oracle.sql.DATE")) {
String metaDataClassName = rs.getMetaData().getColumnClassName(index);
if ("java.sql.Timestamp".equals(metaDataClassName) ||
"oracle.sql.TIMESTAMP".equals(metaDataClassName)) {
obj = rs.getTimestamp(index);
}
else {
obj = rs.getDate(index);
}
}
else if (obj != null && obj instanceof java.sql.Date) {
if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) {
obj = rs.getTimestamp(index);
}
}
return obj;
}

个人观点:queryForList(String sql)方法性能损耗在于使用了ResultSet接口中的getObject()方法!
Mysql对于该方法的部分实现如下:

/**
......
*/
public Object getObject(int columnIndex) throws SQLException {
checkRowPos();
checkColumnBounds(columnIndex);
int columnIndexMinusOne = columnIndex - 1;
if (this.thisRow.isNull(columnIndexMinusOne)) {
this.wasNullFlag = true;
return null;
}
this.wasNullFlag = false;
Field field;
field = this.fields[columnIndexMinusOne];
switch (field.getSQLType()) {
case Types.BIT:
case Types.BOOLEAN:
if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_BIT
&& !field.isSingleBit()) {
return getBytes(columnIndex);
}

// valueOf would be nicer here, but it isn't
// present in JDK-1.3.1, which is what the CTS
// uses.
return Boolean.valueOf(getBoolean(columnIndex))
                ......
                case Types.DATE:
if (field.getMysqlType() == MysqlDefs.FIELD_TYPE_YEAR
&& !this.connection.getYearIsDateType()) {
return Short.valueOf(getShort(columnIndex));
}
return getDate(columnIndex);
case Types.TIME:
return getTime(columnIndex);
case Types.TIMESTAMP:
return getTimestamp(columnIndex);
           }
可以看到mysql对于该方法的处理是一个一个的类型比较,这个是性能消耗的主要原因。

以下为测试代码
测试数据库mysql
student table 的数据是1013960(在实际生产环境中,如此大数据的查询,我们会采用分页的方式)
long time = System.currentTimeMillis();
String sql = "select id,name,age,time_c,timestamp_c from student ";
new JdbcTemplate(utils.dataSource).queryForList(sql);
System.out.println("consumed time " + ((System.currentTimeMillis() - time)) + " ms");
consumed time 11359 ms

long time = System.currentTimeMillis();
String sql = "select id,name,age,time_c,timestamp_c from student ";
Connection con = utils.dataSource.getConnection();
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(sql);
if(rs != null ){
List<Map<String, Object>> list = new ArrayList<Map<String,Object>>();
ResultSetMetaData metaData = rs.getMetaData();
while(rs.next()){
Map<String, Object> map = new HashMap<String, Object>();
map.put(JdbcUtils.lookupColumnName(metaData, 1), rs.getString(1));
map.put(JdbcUtils.lookupColumnName(metaData, 2), rs.getString(2));
map.put(JdbcUtils.lookupColumnName(metaData, 3), rs.getDate(3));
map.put(JdbcUtils.lookupColumnName(metaData, 4), rs.getTime(4));
map.put(JdbcUtils.lookupColumnName(metaData, 5), rs.getTimestamp(5));
list.add(map);
}
}
System.out.println("consumed time " + ((System.currentTimeMillis() - time)) + " ms");
consumed time 8703 ms
//注JdbcUtils来自spring,本文只关心测试结果,相应的资源关闭代码未贴出。

可以看到与纯JDBC的操作相比方法queryForList(String sql)拥有不错的性能开销,再加上new JdbcTemplate(utils.dataSource).queryForList(sql)能取代JDBC那么多样板,这个方法很棒
分享到:
评论

相关推荐

    jdbc+mybatis+spring所有jar包

    所以,搭建ibatis的框架也会有多种方式(我这里mybatis是3.0的,ibatis是2.3的,spring是3.0的,数据库是mysql)。下面介绍3中方式 1,只是用mybatis3。 2,使用mybatis3+spring3(使用mybatis的SqlSessionFactory ...

    产品销售分析系统spring struts2 jfreechart ibatis

    产品销售分析系统 产品销售分析系统示例源码的目录结构介绍 /product: 案例项目工程源码。 /database: 案例数据库文件。 产品销售分析系统的安装配置介绍 运行环境: 1 Java平台选择JDK 6.0或更高版本。 2 Web...

    ibatis和Spring整合的详细例子

    iBatis和Spring整合的详细例子,数据库用的是mysql,开发环境是Eclipse3.2。 1、首先把用到的包导入进来:  spring-framework-1.2.7.jar  iBATIS_DBL-2.1.7.597.jar  mysql-connector-java-5.0.3-bin.jar 2、建...

    Struts2+Ibatis+Spring例子

    这是一个完整的S2SI框架,附jar包和建表语句,里面有添、删、改、查通用查询方法,并且,加了log4j,所以对数据库操作SQL都会在控制台打印出来,加有最新的jQuery插件1.7.2.min.js,建好表,部署完工程直接就可以...

    iBATIS实战

    书的最后给出了一个设计优雅、层次清晰的示例程序JGameStore,该示例涵盖全书的大部分知识点,可以作为iBATIS学习和Web开发的经典案例,非常值得深入研究。 本书既可为广大的开发人员(不仅仅是Web应用程序开发人员)...

    flex+spring+struts2+ibatis 整合的eclipse工程

    flex+spring+struts2+ibatis 整合的eclipse工程,可以... 八,修改jdbc,properties为你的数据库配置,支持mysql,Oracle,根据users.xml文件建user表,字段id,username,password 有问题请发邮件jiping.chen@yahoo.com.cn

    最新最全的spring开发包

    这个jar文件是所有应用都要用到的,它包含访问配置文件、创建和管理bean以及进行Inversion of Control / Dependency Injection(IoC/DI)操作相关的所有类。如果应用只需基本的IoC/DI支持,引入spring-core.jar及...

    SpringBatch批处理 刘相编

    以及Spring Batch框架中经典的三步走策略:数据读、数据处理和数据写,详尽地介绍了如何对CVS格式文件、JSON格式文件、XML文件、数据库和JMS消息队列中的数据进行读操作、处理和写操作,对于数据库的操作详细介绍了...

    halo-dal:java 分布式数据库访问框架,可以结合任何使用PreparedStatement操作的框架。在java jdbc api层实现 分表分库 路由解析的 框架 可以单独或者与用hibernate ibatis spring-jdbc 等框架结合使用,屏蔽api层使用差异,能实现 jdbc 单数据库事务,目的是为了方便的进行分表分库程序的开发

    谢谢大家的关注#halo-dal使用说明#####使用场景:数据库分布式访问#####使用语言:java#####使用条件:支持PreparedStatement处理的任何jdbc框架,最好配合spring管理数据库连接池.#####sql语句必须使用小写字符#####jdk...

    spring4.3.2参考文档(英文)

    Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接...

    SSI实例(源码+mysql数据库+部署)

    SSI实例(源码+数据库+部署说明),数据库使用连接池,自己编写的搭建框架的代码,包含登录,增删改查,包含jar包: commons-dbcp.jar ibatis-2.3.0.677.jar mysql-connector-java-5.1.13.jar spring-aop-3.2.1....

    SpringBoot操作数据库jpa-SB系列之006-配套项目

    Sun引入新的JPA ORM规范出于两个原因:其一,简化现有Java EE和Java SE应用开发工作;其二,Sun希望整合ORM技术,实现天下归一。 ​2 JPA有两个重要的儿子Hibernate ,iBATIS Hibernate是一个开放源代码的对象关系...

    ibatis 开发指南(pdf)

    恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用JDBC 进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作 令人厌烦。 “半自动化”的ibatis,却...

    Spring-Reference_zh_CN(Spring中文参考手册)

    11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口 ...

    Spring 2.0 开发参考手册

    11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口...

    spring in action英文版

     4.6 Spring和iBATIS  4.6.1 配置SQL Map  4.6.2 使用SqlMapClientTemplate  4.7 Spring和OJB  4.8 小结  第5章 事务管理  5.1 理解事务  5.1.1 仅用4个词解释事务  5.1.2 理解Spring对...

    方立勋jDBC ppt文档

    对J2EE有深入理解,尤其是对 Java安全和以Java语言为基础的各种框架有深入研究,包括Struts、Spring、Hibernate 、iBATIS 、AppFuse、AJAX等,有5年J2EE项目经验,具备很强的项目管理能力和丰富的项目实施经验。...

    spring chm文档

    11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口...

    Spring中文帮助文档

    11.2. 利用JDBC核心类控制JDBC的基本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口...

    springmybatis

    MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录. orm工具的基本思想 无论是用过的hibernate,mybatis,你都可以法相他们有一个...

Global site tag (gtag.js) - Google Analytics