文档章节

JDBC批处理Select语句

sms95
 sms95
发布于 2015/04/26 09:22
字数 2102
阅读 4
收藏 0
点赞 0
评论 0

在网络上开销最昂贵的资源就是客户端与服务器往返的请求与响应,JDBC中类似的一种情况就是对数据库的调用,如果你在做数据插入、更新、删除操作,可以使用executeBatch()方法减少数据库调用次数,如:

1
2
3
4
5
Statement pstmt = conn.createStatement();
pstmt.addBatch( "insert into settings values(3,'liu')" );
pstmt.addBatch( "insert into settings values(4,'zhi')" );
pstmt.addBatch( "insert into settings values(5,'jun')" );
pstmt.executeBatch();

但不幸的是对于批量查询,JDBC并没有内建(built-in)的方法,而且JDBC执行批处理的时候也不能有SELECT语句,如:

1
2
3
Statement pstmt = conn.createStatement();
pstmt.addBatch( "select * from settings" );
pstmt.executeBatch();

会抛出异常:

1
2
3
4
Exception in thread "main" java.sql.BatchUpdateException: Can not issue SELECT via executeUpdate().
    at com.mysql.jdbc.Statement.executeBatch(Statement.java: 961 )
    at test.SelectBatchTest.test2(SelectBatchTest.java: 49 )
    at test.SelectBatchTest.main(SelectBatchTest.java: 12 )

假设你想从一系列指定的id列表中获取名字,逻辑上,我们要做的事情看起来应该是:

1
2
3
PreparedStatement stmt = conn.prepareStatement(
    "select id, name from users where id in (?)" );
stmt.setString( "1,2,3" );

但是这样做并不能得到预期的结果,JDBC只允许你用单个的字面值来替换“?” JDBC之所以这么做是有必要的,因为如果SQL自身可以改变的话,JDBC驱动就没法预编译SQL语句了,另一方面它还能防止SQL注入攻击。

但有四种可替代的实现方法可供选择:

  1. 分别对每个id做查询

  2. 一个查询做完所有事

  3. 使用存储过程

  4. 分批处理

方法一: 分别对每个id做查询

假设有100个id,那么就有100次数据库调用:

1
2
3
4
5
6
7
PreparedStatement stmt = conn.prepareStatement(
    "select id, name from users where id = ?" );
for ( int i= 0 ; i < 100 ; i++ ) {
  stmt.setInt(i);   // or whatever values you are trying to query by
  // execute statement and get result
}

这种方法写起来非常简单,但是性能非常慢,数据库往返要处理100次。

方法二:一个查询完成所有事

在运行时,你可以使用一个循环来构建如下SQL语句:

1
2
3
4
5
PreparedStatement stmt = conn.prepareStatement(
    "select id, name from users where id in (?, ?, ?)" );
stmt.setInt( 1 );
stmt.setInt( 2 );
stmt.setInt( 3 );

这种方案从代码相比第一种方法算是第二简单的,它解决了来回多次请求数据库的问题,但是如果每次请求参数的个数不一样时预处理语句就必须重新编译,由于每次SQL字面值不匹配,因此如果分别用10个id、3个id、100个,这样会在缓存中产生三个预处理语句。除了重新编译预处理语句之外,先前缓存池中的预处理语句将被移除(受限于缓存池大小),进而导致重新编译已编译过的语句。最后,这种查询方式在内存溢出或磁盘分页操作时查询会占用很长时间。

该方案的另一种变体就是在SQL语句中硬编码:

1
2
PreparedStatement stmt = conn.prepareStatement(
    "select id, name from users where id in (1, 2, 3)" );

这样方式甚至更差,而且没有任何机会对SQL语句重用,至少用“?”还可以对使用相同数量参数的SQL语句进行重用。

1
2
3
4
5
6
7
PreparedStatement stmt = conn.prepareStatement(
  "select id, name from users where id in (?) ; "  
  + "select id, name from users where id in (?); "
  + "select id, name from users where id in (?)" );
stmt.setInt( 1 );
stmt.setInt( 2 );
stmt.setInt( 3 );

这种方法的优点就是每次查询模版语句都一样,数据库不需要每次计算执行路径。然而,从数据库驱动的角度来说SQL每次都不一样,预处理语句每次必须预处理保存在缓存中。而且不是所有数据库系统都支持分号间隔的多个SQL语句的

方法三:使用存储过程

存储过程执行在数据库系统中,因此它可以做很多查询而不需要太多网络负载,存储过程可以收集所有结果一次性返回。这是一种速度很快的解决方案。但是它对数据库的依赖比较强,不能随意的切换数据库系统,否则需要重写存储过程而且需要你分离应用服务器与数据库服务器之间的逻辑。如果应用架构已经使用了存储过程,无疑这是只最佳方案。

方法四:分批处理

批量查询是方案一和方案二的折衷选择,它预先确定一批查询参数的常量,然后用这些参数构建一批查询。因为这只会涉及到有限个查询,所以它有预处理语句的优势(预编译不会与缓存中的预处理发生碰撞)。批处理多个值在相同的查询保留了服务器来回请求最小化的优势。最后你可以通过控制批处理的上限来避免大查询的内存问题。如果你有很关键的查询对性能方面有要求又不想用存储过程,那么这是一种很好的解决办法,现在我们通过一个例子说明:

1
2
3
4
public static final int SINGLE_BATCH = 1 ;
public static final int SMALL_BATCH = 4 ;
public static final int MEDIUM_BATCH = 11 ;
public static final int LARGE_BATCH = 51 ;

第一件要做的事是你要衡量有多少批处理以及每个批处理的大小。(注意:在真实的代码中,这些值应该写在一个配置文件中而不是采取硬编码的形式,也就是说,你可以在运行时试验并改变批处理的大小)不管真正的批处理大小是多大,你总需要一个单个的批处理—大小为1的批处理(SINGLE_BATTCH)。这样如果有人请求的就是一个值或者在一个很大的查询中最后有遗留下来的单个值都能派上用场。对于批处理的大小,使用素数会更好些。换句话说,大小不应该可以相互的整除或者被相同的数整除。请求数的最大值将有最少的服务器往返。批处理的大小的数量和真正的大小是基于配置变化的。需要注意的是:大的批处理大小不应该太大否则你将遇到内存麻烦。同时最小批处理的大小应该很小,你可能会使用这个来做很多次的查询。

1
while ( totalNumberOfValuesLeftToBatch > 0 ) {

按如下方式重复操作直到推出循环。

1
2
3
4
5
6
7
8
9
int batchSize = SINGLE_BATCH;
if ( totalNumberOfValuesLeftToBatch >= LARGE_BATCH ) {
  batchSize = LARGE_BATCH;
} else if ( totalNumberOfValuesLeftToBatch >= MEDIUM_BATCH ) {
  batchSize = MEDIUM_BATCH;
} else if ( totalNumberOfValuesLeftToBatch >= SMALL_BATCH ) {
  batchSize = SMALL_BATCH;
}
totalNumberOfValuesLeftToBatch -= batchSize;

这种方案在这里是查找到最大的批处理大小,可能这个最大值比我们实际要查询的值稍大。举例说明:假设查询有75个参数,那么首先选择51个元素(LARGE_BATCH),现在还剩24个待查询,然后接着用11个元素的查询(MEDIUM_BATCH)。现在还有13个值,因为仍然大于11,再做一次11个元素的查询,现在只剩下2个值,它少于那个最小的批处理4(SMALL_BATCH),所以做两次单查询。总共5次往返用了3次预处理在缓存中。这是一个很重要的改进比单独地坐75次单查询。

1
2
3
4
5
6
7
8
9
10
11
12
StringBuilder inClause = new StringBuilder();
boolean firstValue = true ;
for ( int i= 0 ; i < batchSize; i++) {
  inClause.append( '?' );
  if ( firstValue ) {
    firstValue = false ;
  } else {
    inClause.append( ',' );
  }
}
PreparedStatement stmt = conn.prepareStatement(
    "select id, name from users where id in (" + inClause.toString() + ')' );

现在已经构建了一个真实的预处理语句,由于一直用相同的方式构建的查询,驱动注意到SQL是相同的。(注意:如果你还没有用Java5,使用StringBuffer替换StringBuilder才能正常编译),返回id很重要这样有利于查找哪个名字对应哪个id。

1
2
3
for ( int i= 0 ; i < batchSize; i++) {
  stmt.setInt(i);   // or whatever values you are trying to query by
}

设置合适的值数量去查询,包括其他搜索条件查询。仅仅只要把这些参数在之举参数之后。在这种情况你可以最终当前的索引。

从这点来看,你仅仅只是执行查询返回了结果,在第一次尝试的时候,你应该关注一下性能的提升,根据具体情况调整优化批处理的大小(batch size)。

© 著作权归作者所有

共有 人打赏支持
sms95
粉丝 1
博文 31
码字总数 22644
作品 0
长沙
Spring JdbcTemplate方法详解

JdbcTemplate主要提供以下五类方法: execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句; update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate...

zqcju
2014/06/13
0
6
JavaWeb07-HTML篇笔记(二)

1.1 案例一:使用JDBC完成CRUD的操作:1.1.1 需求: 对分类管理使用JDBC进行CRUD的操作. 1.1.2 分析:1.1.2.1 技术分析: 【JDBC的概述】 Ø JDBC:Java DataBase Connectivity Java数据库的连...

我是小谷粒
05/16
0
0
day14_DBUtils学习笔记

一、DBUtils介绍 Apache公司开发的框架。   DBUtils是java编程中的数据库操作实用工具,小巧简单实用。   DBUtils封装了对JDBC的操作,简化了JDBC操作。可以少写代码。 commons-dbutils 是...

黑泽明军
05/20
0
0
关于Jmeter中JDBC相关参数的应用

一.数据库驱动类和URL格式: Database Driver class Database URL MySQL com.mysql.jdbc.Driver jdbc:mysql://host[:port]/dbname PostgreSQL org.postgresql.Driver jdbc:postgresql:{dbname......

蓝蝶飞扬
2014/10/27
0
0
通过JDBC进行简单的增删改查(以MySQL为例)

前言:什么是JDBC 一、准备工作(一):MySQL安装配置和基础学习 二、准备工作(二):下载数据库对应的jar包并导入 三、JDBC基本操作 (1)定义记录的类(可选) (2)连接的获取 (3)insert (4...

Airship
2015/07/13
0
0
Spring之jdbc Template实现CRUD操作

Spring为各种持久化技术都提供了简单操作的模板回调。比如jdbc、hibernate、Mybatis以及JPA等。 这里我们就以JDBC为例,看看JDBC template怎么实现CRUD操作。 JdbcTemplate主要提供以下几类方...

Java攻城玩家
05/31
0
0
编写高性能 Java 代码的最佳实践

摘要:本文首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践。最后研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整。以下是译...

这篇文章
06/20
0
0
关于java Runtime.getRunTime.exec(String command)的使用

当要调用一个外部程序的时候,java提供了exec方法,具体用法是:Runtime.getRunTime.exec("cmd /C Start mailto: ").其中cmd /c是调用cmd下的start命令,它相当于对一个文件双击。也可以用R...

hebeijpp
2013/05/19
0
1
Android———利用JDBC连接服务器数据库

1、Android平台下与服务器数据库通信的方法 在Android平台下,连接电脑服务器的MySQL、PostgreSQL、Oracle、Sybase、Microsoft SQLServer等数据库管理系统DBMS(database management system),...

xiahuawuyu
2012/10/10
0
1
spring中的JdbcTemplate简单记录

JdbcTemplate主要提供以下五类方法: execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句; update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate...

刘谱_smile
2015/07/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

idea tomcat 远程调试

tomcat 配置 编辑文件${tomcat_home}/bin/catalina.sh,在文件开头添加如下代码。    CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=7829" Idea端配......

qwfys
今天
1
0
遍历目录下的文件每250M打包一个文件

#!/usr/bin/env python # -*- utf-8 -*- # @Time : 2018/7/20 0020 下午 10:16 # @Author : 陈元 # @Email : abcmeabc@163.com # @file : tarFile.py import os import tarfile import thr......

寻爱的小草
今天
1
0
expect同步文件&expect指定host和要同步的文件&构建文件分发系统&批量远程执行命令

20.31 expect脚本同步文件 expect通过与rsync结合,可以在一台机器上把文件自动同步到多台机器上 编写脚本 [root@linux-5 ~]# cd /usr/local/sbin[root@linux-5 sbin]# vim 4.expect#!/...

影夜Linux
今天
1
0
SpringBoot | 第九章:Mybatis-plus的集成和使用

前言 本章节开始介绍数据访问方面的相关知识点。对于后端开发者而言,和数据库打交道是每天都在进行的,所以一个好用的ORM框架是很有必要的。目前,绝大部分公司都选择MyBatis框架作为底层数...

oKong
今天
12
0
win10 上安装解压版mysql

1.效果 2. 下载MySQL 压缩版 下载地址: https://downloads.mysql.com/archives/community/ 3. 配置 3.1 将下载的文件解压到合适的位置 我最终将myql文件 放在:D:\develop\mysql 最终放的位...

Lucky_Me
今天
2
0
linux服务器修改mtu值优化cpu

一、jumbo frames 相关 1、什么是jumbo frames Jumbo frames 是指比标准Ethernet Frames长的frame,即比1518/1522 bit大的frames,Jumbo frame的大小是每个设备厂商规定的,不属于IEEE标准;...

问题终结者
今天
2
0
expect脚本同步文件expect脚本指定host和要同步的文件 构建文件分发系统批量远程执行命令

expect脚本同步文件 在一台机器上把文件同步到多台机器上 自动同步文件 vim 4.expect [root@yong-01 sbin]# vim 4.expect#!/usr/bin/expectset passwd "20655739"spawn rsync -av ro...

lyy549745
今天
1
0
36.rsync下 日志 screen

10.32/10.33 rsync通过服务同步 10.34 linux系统日志 10.35 screen工具 10.32/10.33 rsync通过服务同步: rsync还可以通过服务的方式同步。那需要开启一个服务,他的架构是cs架构,客户端服务...

王鑫linux
今天
1
0
matplotlib 保存图片时的参数

简单绘图 import matplotlib.pyplot as pltplt.plot(range(10)) 保存为csv格式,放大后依然很清晰 plt.savefig('t1.svg') 普通保存放大后会有点模糊文件大小20多k plt.savefig('t5.p...

阿豪boy
今天
3
0
java 8 复合Lambda 表达式

comparator 比较器复合 //排序Comparator.comparing(Apple::getWeight);List<Apple> list = Stream.of(new Apple(1, "a"), new Apple(2, "b"), new Apple(3, "c")) .collect(......

Canaan_
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部