PreparedStatement
:PreparedStatement
是预编译的,对于批量处理可以大大提高效率,也叫 JDBC存储过程
。Statement
:使用 Statement
对象,在对数据库只执行一次性存取的时侯,用 Statement
对象进行处理。PreparedStatement
对象的开销比 Statement
大,对于一次性操作并不会带来额外的好处。Statement
每次执行 sql语句
,相关数据库都要执行 sql语句
的编译,而 PreparedStatement
是预编译的,且支持批处理。Statement stmt = conn.createStatement();
String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′";
stmt.executeUpdate(updateString);
PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
updateSales.setInt(1, 75);
updateSales.setString(2, "Colombian");
updateSales.executeUpdate();
PreparedStatement
对象,而前者是普通的 Statement
对象。PreparedStatement
对象不仅包含了 SQL语句
,而且大多数情况下这个语句已经被预编译过,因而当其执行时, 只需 DBMS(数据库管理系统)
运行 SQL语句
,而不必先编译。Statement
对象多次的时候,PreparedStatement
对象将会大大降低运行时间,当然也加快了访问数据库的速度。SQL语句
的句法,而只需更改其中变量的值,便可重新执行 SQL语句
。PreparedStatement
对象与否,在于相同句法的 SQL语句
是否执行了多次,而且两次之间的差别仅仅是变量的不同。SQL语句
的 JDBC程序
产生大量的 Statement
和 PreparedStatement
对象。PreparedStatement
对象比 Statement
对象更有效,特别是如果带有不同参数的同一 SQL语句
被多次执行的时候。PreparedStatement
对象允许数据库预编译 SQL语句
,这样在随后的运行中可以节省时间并增加代码的可读性。Oracle
环境中,开发人员实际上有更大的灵活性。Statement
或 PreparedStatement
对象时,Oracle数据库
会缓存 SQL语句
以便以后使用。Java应用程序
和 Oracle服务器
间增加的网络活动,执行 PreparedStatement
对象实际上会花更长的时间。PreparedStatement
对象,那就是安全性。PreparedStatement
对象的参数可被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。公共Web站点
上的用户传来的数据的时候,安全性的问题就变得极为重要。PreparedStatement
的字符串参数会自动被驱动器忽略,这就意味着当你的程序试着将 字符串“D'Angelo”
插入到 VARCHAR2
中时,该语句将不会识别第一个 “,”
,从而导致悲惨的失败,几乎很少有必要创建你自己的字符串忽略代码。Web环境
中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。公共Web站点
上,在没有首先通过 PreparedStatement
对象处理的情况下,所有的用户输入都不应该传递给 SQL语句
。SQL语句
的地方,如 HTML
的隐藏区域或一个查询字符串上,SQL语句
都不应该被显示出来。SQL命令
时,我们有二种选择:可以使用 PreparedStatement
对象,也可以使用 Statement
对象。SQL命令
,PreparedStatement
都只对它解析和编译一次。Statement
对象时,每次执行一个 SQL命令
时,都会对它进行解析和编译。
PreparedStatement
会先初始化SQL
,先把这个SQL
提交到数据库中进行预处理,多次使用可提高效率。
Statement
不会初始化,没有预处理,没次都是从0
开始执行SQL
。
PreparedStatement
可以使用 ? 替换变量// 在 SQL语句中可以包含 ? ps = conn.prepareStatement("select * from Cust where ID=?"); int sid = 1001; ps.setInt(1, sid); rs = ps.executeQuery();
而
Statement
只能按下面写法来实现int sid = 1001; Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from Cust where ID=" + sid);
JDBC驱动
的最佳化是基于使用的是什么功能,选择 PreparedStatement
还是 Statement
取决于你要怎么使用它们。SQL语句
选择 Statement
是最好的。相反,如果 SQL语句
被多次执行选用 PreparedStatement
是最好的。PreparedStatement
的第一次执行消耗是很高的,它的性能体现在后面的重复执行。Employee ID
,使用 prepared
的方式来执行一个针对 Employee表
的查询,JDBC驱动
会发送一个网络请求到数据解析和优化这个查询,而执行时会产生另一个网络请求,在 JDBC驱动
中,减少网络通讯是最终的目的。Statement
。Statement
,同一个查询只会产生一次网络到数据库的通讯。PreparedStatement池
的情况下,本指导原则有点复杂。PreparedStatement池
时,如果一个查询很特殊,并且不太会再次执行到,那么可以使用 Statement
。Statement池
可能被再次执行,那么请使用 PreparedStatement
。Statement池
的同样情况下,请使用 Statement
。Update
大量的数据时,先 Prepare
一个 INSERT语句
再多次的执行,会导致很多次的网络连接。JDBC
的调用次数改善性能,你可以使用 PreparedStatement
的 AddBatch()方法
一次性发送多个查询给数据库。PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {
ps.setString(name[n]);
ps.setLong(id[n]);
ps.setInt(salary[n]);
ps.executeUpdate();
}
PreparedStatement
被用来多次执行 INSERT语句
。100次INSERT操作
,共有 101次网络往返
。1次往返
是预储 PreparedStatement
,另外 100次往返
执行每个迭代。PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {
ps.setString(name[n]);
ps.setLong(id[n]);
ps.setInt(salary[n]);
ps.addBatch();
}
ps.executeBatch();
100次INSERT操作
中使用 addBatch()方法
时,只有 2次网络往返
。1次往返
是预储 PreparedStatement
,另一次是执行 batch命令
。Batch命令
会用到更多的数据库的 CPU周期
,但是通过减少网络往返,性能得到提高。总结
JDBC
的性能最大的增进是减少 JDBC驱动
与数据库之间的网络通讯次数
注意
Oracel 10G
的 JDBC Driver
限制最大 Batch size
是 16383条
,addBatch
超过这个限制,那么 executeBatch
时就会出现 “无效的批值”(Invalid Batch Value)
异常。Oracle10G
,在此 bug
减少前,Batch size
需要控制在一定的限度。mysql 5.5.28
批量执行的数据最大限度是多少不清楚,但自己试了 1w
、2w
、3w
都没问题,url
后面添加:rewriteBatchedStatements=true
表示批量插入,addbatch()
,executeBatch()
在后台入库的地方还是不会一次请求入库而是多次请求入库。Forward-Only型
的光标提供了最好的性能。Forward-Only型
的光标更快。JDBC
的 Scroll-Insensitive型光标
是较理想的选择。JDBC驱动
采用 'lazy'
方式获取数据时或许是很多的而不是全部的数据)并且储存在客户端。'lazy'
方法时或许只是有限的网络交通) 并且处理起来很快。Scroll-Insensitive型光标
不应该被使用在单行数据的获取上。Scroll-Insensitive型光标
,因为这样可能会造成内存耗尽。Scroll-Insensitive型光标
的实现方式是在数据库的临时表中缓存数据来避免性能问题,但多数还是将数据缓存在应用程序中。Scroll-Sensitive型光标
,有时也称为 Keyset-Driven光标
,使用标识符,像数据库的 ROWID
之类。1000行数据
到程序中。JDBC驱动
不会执行程序提供的 SELECT语句
。SELECT查询
,例如 ROWID
。1000个键值
。JDBC驱动
直接从本地缓存中找到相应的键值,然后构造一个包含了 'WHERE ROWID=?'
子句的最佳化查询,再接着执行这个修改过的查询,最后从服务器取得该数据行。Scroll-Insensitive型光标
一样提供足够缓存时,Scroll-Sensitive型光标
可以被替代用来作为动态的可滚动的光标。JDBC
提供多种方法从 ResultSet
中取得数据,像 getInt()
、getString()
和 getObject()
等等,而 getObject()
方法是最泛化了的,提供了最差的性能。JDBC驱动
必须对要取得的值的类型作额外的处理以映射为特定的对象,所以就对特定的数据类型使用相应的方法。getString(1)
、getLong(2)
和 getInt(3)
等来替代字段名。getString("foo")
… JDBC驱动
可能会将字段名转为大写(如果需要),并且在到字段名列表中逐个比较来找到 "foo"
字段。100行15列
的 ResultSet
,字段名不包含在其中。EMPLOYEENAME(字串型)
、EMPLOYEENUMBER(长整型)
和 SALARY(整型)
,你指定getString(“EmployeeName”)
、getLong(“EmployeeNumber”)
和 getInt(“Salary”)
,查询每个字段名必须被转换为 metadata
中相对应的大小写,然后才进行查找,如果你使用 getString(1)
、getLong(2)
和 getInt(15)
,性能就会有显著改善.在 JDBC3.0
之前,应用程序只可在插入数据后通过立即执行一个 SELECT语句
来取得隐含列的值.
// 插入行
int rowcount = stmt.executeUpdate("insert into LocalGeniusList (name) values ('Karen')");
// 现在为新插入的行取得磁盘位置 - rowid
ResultSet rs = stmt.executeQuery("select rowid from LocalGeniusList where name = 'Karen'");
这种取得隐含列的方式有两个主要缺点
1、取得隐含列是在一个独立的查询中,它要透过网络送到服务器后再执行。
2、因为不是主键,查询条件可能不是表中的 唯一性ID
。在后个例子中,可能返回了多个隐含列的值,程序无法知道哪个是最后插入的行的值。
译者:
1、由于不同的数据库支持的程度不同,返回 rowid
的方式各有差异。
2、在 SQL Server
中,返回最后插入的记录的 id
可以用这样的查询语句:SELECT @IDENTITY
JDBC3.0
规范中的一个可选特性提供了一种能力,可以取得刚刚插入到表中的记录的自动生成的键值。
int rowcount = stmt.executeUpdate("insert into LocalGeniusList (name) values ('Karen')",
// 插入行并返回键值
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
// 得到生成的键值
现在,程序中包含了一个 唯一性ID
,可以用来作为查询条件来快速的存取数据行,甚至于表中没有主键的情况也可以。
这种取得自动生成的键值的方式给 JDBC
的开发者提供了灵活性,并且使存取数据的性能得到提升。
schema
时,应选择能被最有效地处理的数据类型。JDBC驱动程序
并没有实现可滚动光标。rs.last()
和 rs.getRow()
方法去找出数据集的最大行数。JDBC驱动程序
模拟了可滚动光标,调用 rs.last()
导致了驱动程序透过网络移到了数据集的最后一行。ResultSet
遍历一次计数或者用 SELECT查询
的 COUNT函数
来得到数据行数。在 JDBC应用
中,如果你已经是稍有水平开发者,你就应该始终以 PreparedStatement
代替 Statement
。
也就是说,在任何时候都不要使用 Statement
。基于以下的原因:
虽然用 PreparedStatement
来代替 Statement
会使代码多出几行,但这样的代码无论从可读性还是可维护性上来说,都比直接用 Statement
的代码高很多档次
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
不用我多说,对于第一种方法,别说其他人去读你的代码,就是你自己过一段时间再去读,都会觉得伤心。
Connection
中多次执行的预编译语句被缓存,而是对于整个 DB
中,只要预编译的语句语法和缓存中匹配。Statement
的语句中,即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配。比如:insert into tb_name (col1,col2) values ('11','22');
insert into tb_name (col1,col2) values ('11','23');
即使到目前为止,仍有一些人连基本的 恶义SQL语法
都不知道。
String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'"
如果我们把 [' or '1' = '1]
作为 var passwd
传入进来,用户名 随意
,看看会成为什么?
select * from tb_name = '随意' and passwd = '' or '1' = '1'
因为 '1'='1'
肯定成立,所以可以任何通过验证,更有甚至把 [';drop table tb_name;]
作为 var passwd
传入进来,则
select * from tb_name = '随意' and passwd = '';drop table tb_name
Statement
,有可能要对 drop
,等做费尽心机的判断和过滤。PreparedStatement
吗?上面是三篇文章,三篇文章详细介绍了PreparedStatement
和 Statement
两个对象的使用以及效率、安全问题。
1、上面说了如果 sql
中只有数值在变则效率高
2、PreparedStatement
具有 防sql注入
3、代码可读性比较好
PreparedStatement 的 addBatch 和 executeBatch 实现批量添加
Connection connection = getConnection();
connection.setAutoCommit(false);
PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");
Connection connection = getConnection();
Statement statement = connection.createStatement();
// 记录1
statement.setInt(1, 1);
statement.setString(2, "Cujo");
statement.addBatch();
// 记录2
statement.setInt(1, 2);
statement.setString(2, "Fred");
statement.addBatch();
// 记录3
statement.setInt(1, 3);
statement.setString(2, "Mark");
statement.addBatch();
// 批量执行上面 3 条语句,一口吞了,很爽
int[] counts = statement.executeBatch();
// Commit it 咽下去,到肚子(DB)里面
connection.commit();
Connection connection = getConnection();
Statement stmt = connection.createStatement();
stmt.addBatch("update TABLE1 set 题目="盛夏话足部保健1" where id="3407"");
stmt.addBatch("update TABLE1 set 题目="夏季预防中暑膳食1" where id="3408"");
stmt.addBatch("INSERT INTO TABLE1 VALUES("11","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("12","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("13","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("14","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("15","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("16","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("17","12","13","","")");
stmt.addBatch("INSERT INTO TABLE1 VALUES("18","12","13","","")");
int[] updateCounts = stmt.executeBatch();
connection.commit();
public static void insertData(List<Map<String, String>> list, Logger log) {
// 获取的数据
List<Map<String, String>> nlist = list;
String upsql = "update hrd_staff set position=? where id=?";
Iterator<Map<String, String>> iter = nlist.iterator();
Connection con = Utils.getCon();
int count = 0;
try {
// 在数据添加的时候注意事务提交方式
con.setAutoCommit(false);
// PreparedStatement方法的使用
PreparedStatement pstm = con.prepareStatement(upsql);
while (iter.hasNext()) {
count++;
Map<String, String> map = iter.next();
String jon_name = map.get("job_name");
String uid = map.get("uid");
pstm.setString(1, jon_name);
pstm.setString(2, uid);
// 添加到缓存中
pstm.addBatch();
// 如果数据量很大,不能一次性批量添加所以我们要分批次添加,这里就是300条一次
if (count % 300 == 0) {
// 持久化
int[] res = pstm.executeBatch();
// 提交事务,持久化数据
con.commit();
pstm.clearBatch();
log.info("300整除插入结果: " + res.length);
}
}
// 小于300条的在这里持久化
int[] ress = pstm.executeBatch();
// 事务提交持久化
con.commit();
pstm.clearBatch();
log.info("插入数据结果:" + ress.length);
} catch (SQLException e) {
try {
con.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
if (null != con) {
con.close();
con.setAutoCommit(true);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
注意:这里除了下面说的 url
中的批量设置外,我们也要注意事务的设置,不能设置为自动提交,要批量添加后在提交事务
addBatch()
就是把你的处理内容添加到批处理单元中,即添加到了 batch
中。executeBatch()
此时,数据库把刚才加到 batch
中的命令批量处理。使用批量插入的好处:
100次INSERT操作
中使用 addBatch()方法
时,只有 两次网络往返
,一次往返是预储 statement
,另一次是执行 batch命令
。Batch命令
会用到更多的数据库的 CPU周期
,但是通过减少网络往返,性能得到提高。JDBC
的性能最大的增进是减少 JDBC驱动
与数据库之间的网络通讯。网络往返101次
这样会耗很多时间,自然效率也就一般。这里要注意:
在 mysql
下使用批量执行的时候要在,url
后面添加手动设置支持批量添加,实例如下:
String url = "jdbc:mysql://localhost:3306/music?rewriteBatchedStatements=true";
默认情况下 rewriteBatchedStatements
的值为 false
,也就是批量添加功能是关闭的,如果使用则要手动开启!
还有就是事务的设置,不能使自动提交,要批量添加后才提交!!!
Statement、PreparedStatement 的用法和解释