Java数据库JDBC——prepareStatement的用法和解释

更新时间:2023-07-05 09:40:38 阅读: 评论:0

Java数据库JDBC——prepareStatement的⽤法和解释
⼀、prepareStatement 的⽤法和解释
1.PreparedStatement是预编译的,对于批量处理可以⼤⼤提⾼效率. 也叫JDBC存储过程
2.使⽤ Statement 对象。在对数据库只执⾏⼀次性存取的时侯,⽤ Statement 对象进⾏处理。PreparedStatement 对象的开销⽐Statement⼤,对于⼀次性操作并不会带来额外的好处。
3.statement每次执⾏sql语句,相关数据库都要执⾏sql语句的编译,preparedstatement是预编译得, preparedstatement⽀持批处理4、
Code Fragment 1:
String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′";
Code Fragment 2:
社区故事PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
updateSales.tInt(1, 75);
updateSales.tString(2, "Colombian");
⽚断2和⽚断1的区别在于,后者使⽤了PreparedStatement对象,⽽前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句,⽽且⼤多数情况下这个语句已经被预编译过,因⽽当其执⾏时,只需DBMS运⾏SQL语句,⽽不必先编译。当你需要执⾏Statement对象多次的时候,PreparedStatement对象将会⼤⼤降低运⾏时间,当然也加快了访问数据库的速度。
这种转换也给你带来很⼤的便利,不必重复SQL语句的句法,⽽只需更改其中变量的值,便可重新执⾏SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执⾏了多次,⽽且两次之间的差别仅仅是变量的不同。如果仅仅执⾏了⼀次的话,它应该和普通的对象毫⽆差异,体现不出它预编译的优越性。
5.执⾏许多SQL语句的JDBC程序产⽣⼤量的Statement和PreparedStatement对象。通常认为Prepare
dStatement对象⽐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命令时,都会对它进⾏解析和编译。
第⼀:
prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进⾏预处理,多次使⽤可提⾼效率。
Statement不会初始化,没有预处理,没次都是从0开始执⾏SQL
第⼆:
prepareStatement可以替换变量
在SQL语句中可以包含?,可以⽤ps=conn.prepareStatement("lect * from Cust where ID=?");
int sid=1001;
ps.tInt(1, sid);
rs = ps.executeQuery();
可以把?替换成变量。
⽽Statement只能⽤
int sid=1001;
Statement stmt = ateStatement();
ResultSet rs = uteQuery("lect * from Cust where ID="+sid);
来实现。
⼆、深⼊理解statement 和prepareStatement
1、使⽤Statement⽽不是PreparedStatement对象
JDBC驱动的最佳化是基于使⽤的是什么功能. 选择PreparedStatement还是Statement取决于你要怎么使⽤它们. 对于只执⾏⼀次的SQL 语句选择Statement是最好的. 相反, 如果SQL语句被多次执⾏选⽤PreparedStatement是最好的.
PreparedStatement的第⼀次执⾏消耗是很⾼的. 它的性能体现在后⾯的重复执⾏. 例如, 假设我使⽤Employee ID, 使⽤prepared的⽅式来执⾏⼀个针对Employee表的查询. JDBC驱动会发送⼀个⽹络请求到数据解析和优化这个查询. ⽽执⾏时会产⽣另⼀个⽹络请求.在JDBC 驱动中,减少⽹络通讯是最终的⽬的. 如果我的程序在运⾏期间只需要⼀次请求, 那么就使⽤Statement. 对于Statement, 同⼀个查询只会产⽣⼀次⽹络到数据库的通讯.
对于使⽤PreparedStatement池的情况下, 本指导原则有点复杂. 当使⽤PreparedStatement池时, 如果⼀个查询很特殊, 并且不太会再次执⾏到, 那么可以使⽤Statement. 如果⼀个查询很少会被执⾏,但连接池中的Statement池可能被再次执⾏, 那么请使⽤PreparedStatement. 在不是Statement池的同样情况下, 请使⽤Statement.
2、使⽤PreparedStatement的Batch功能
Update⼤量的数据时, 先Prepare⼀个INSERT语句再多次的执⾏, 会导致很多次的⽹络连接. 要减少JDBC的调⽤次数改善性能, 你可以使⽤PreparedStatement的AddBatch()⽅法⼀次性发送多个查询给
数据库. 例如, 让我们来⽐较⼀下下⾯的例⼦.
例 1: 多次执⾏PreparedStatement,多次数据库请求(⽹络请求)
PreparedStatement ps = conn.prepareStatement(
"INSERT into employees values (?, ?, ?)");
for (n = 0; n < 100; n++) {
ps.tString(name[n]);
pmps.tLong(id[n]);
ps.tInt(salary[n]);
}
例 2: 使⽤Batch,以此请求执⾏多条
PreparedStatement ps = conn.prepareStatement(
"INSERT into employees values (?, ?, ?)");未成年人保护法学习心得
for (n = 0; n < 100; n++) {
ps.tString(name[n]);
ps.tLong(id[n]);
ps.tInt(salary[n]);
ps.addBatch();
}小船图片大全
在例 1中, PreparedStatement被⽤来多次执⾏INSERT语句. 在这⾥, 执⾏了100次INSERT操作, 共有101次⽹络往返.
其中,1次往返是预储PreparedStatement, 另外100次往返执⾏每个迭代.
在例2中, 当在100次INSERT操作中使⽤addBatch()⽅法时, 只有两次⽹络往返.
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() 在后台⼊库的地⽅还是不会⼀次请求⼊库⽽是多次请求⼊库。
3、选择合适的光标类型常州欢乐谷
的光标类型以最⼤限度的适⽤你的应⽤程序. 本节主要讨论三种光标类型的性能问题.
对于从⼀个表中顺序读取所有记录的情况来说, Forward-Only型的光标提供了最好的性能. 获取表中的数据时, 没有哪种⽅法⽐使⽤Forward-Only型的光标更快. 但不管怎样, 当程序中必须按⽆次序的⽅式
处理数据⾏时, 这种光标就⽆法使⽤了.
对于程序中要求与数据库的数据同步以及要能够在结果集中前后移动光标, 使⽤JDBC的Scroll-Innsitive型光标是较理想的选择. 此类型的光标在第⼀次请求时就获取了所有的数据(当JDBC驱动采⽤'lazy'⽅式获取数据时或许是很多的⽽不是全部的数据)并且储存在客户端. 因此,第⼀次请求会⾮常慢, 特别是请求长数据时严重. ⽽接下来的请求并不会造成任何⽹络往返(当使⽤'lazy'⽅法时或许只是有限的⽹络交通) 并且处理起来很快. 因为第⼀次请求速度很慢, Scroll-Innsitive型光标不应该被使⽤在单⾏数据的获取上. 当有要返回长数据时, 开发者也应避免使⽤Scroll-Innsitive型光标, 因为这样可能会造成内存耗尽. 有些Scroll-Innsitive型光标的实现⽅式是在数据库的临时表中缓存数据来避免性能问题, 但多数还是将数据缓存在应⽤程序中.
Scroll-Sensitive型光标, 有时也称为Keyt-Driven光标, 使⽤标识符, 像数据库的ROWID之类. 当每次在结果集移动光标时, 会重新该标识符的数据. 因为每次请求都会有⽹络往返, 性能可能会很慢. ⽆论怎样, ⽤⽆序⽅式的返回结果⾏对性能的改善是没有帮助的.
现在来解释⼀下这个, 来看这种情况. ⼀个程序要正常的返回1000⾏数据到程序中. 在执⾏时或者第⼀⾏被请求时, JDBC驱动不会执⾏程序提供的SELECT语句. 相反, 它会⽤键标识符来替换SELECT查询, 例如, ROWID. 然后修改过的查询都会被驱动程序执⾏,跟着会从数据库获取所有1000个键值. 每⼀次对⼀⾏结果的请求都会使JDBC驱动直接从本地缓存中找到相应的键值, 然后构造⼀个包含了'WHERE
怎么画一条龙
ROWID=?'⼦句的最佳化查询, 再接着执⾏这个修改过的查询, 最后从取得该数据⾏.
当程序⽆法像Scroll-Innsitive型光标⼀样提供⾜够缓存时, Scroll-Sensitive型光标可以被替代⽤来作为动态的可滚动的光标.
4、使⽤有效的getter⽅法
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). 性能就会有显著改善.
5、获取⾃动⽣成的键值
有许多数据库提供了隐藏列为表中的每⾏记录分配⼀个唯⼀键值. 很典型, 在查询中使⽤这些字段类型是取得记录值的最快的⽅式, 因为这些隐含列通常反应了数据在磁盘上的物理位置. 在JDBC3.0之前, 应⽤程序只可在插⼊数据后通过⽴即执⾏⼀个SELECT语句来取得隐含列的值.
例 3: JDBC3.0之前
一带一路纪录片//插⼊⾏
int rowcount = uteUpdate (
"inrt into LocalGeniusList (name) values ('Karen')");
// 现在为新插⼊的⾏取得磁盘位置 - rowid
ResultSet rs = uteQuery (
"lect rowid from LocalGeniusList where name = 'Karen'");
这种取得隐含列的⽅式有两个主要缺点. 第⼀, 取得隐含列是在⼀个独⽴的查询中, 它要透过⽹络送到后再执⾏. 第⼆, 因为不是主键, 查询条件可能不是表中的唯⼀性ID. 在后⾯⼀个例⼦中, 可能返回了多个隐含列的值, 程序⽆法知道哪个是最后插⼊的⾏的值.
(译者:由于不同的数据库⽀持的程度不同,返回rowid的⽅式各有差异。在SQL Server中,返回最后插⼊的记录的id可以⽤这样的查询语句:SELECT @IDENTITY )
JDBC3.0规范中的⼀个可选特性提供了⼀种能⼒, 可以取得刚刚插⼊到表中的记录的⾃动⽣成的键值.
例 4: JDBC3.0之后
int rowcount = uteUpdate (
"inrt into LocalGeniusList (name) values ('Karen')",
孤岛之鲸// 插⼊⾏并返回键值
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = GeneratedKeys ();
// 得到⽣成的键值
现在, 程序中包含了⼀个唯⼀性ID, 可以⽤来作为查询条件来快速的存取数据⾏, 甚⾄于表中没有主键的情况也可以.
这种取得⾃动⽣成的键值的⽅式给JDBC的开发者提供了灵活性, 并且使存取数据的性能得到提升.
6、选择合适的数据类型
接收和发送某些数据可能代价昂贵. 当你设计⼀个schema时, 应选择能被最有效地处理的数据类型. 例如, 整型数就⽐浮点数或实数处理起来要快⼀些. 浮点数的定义是按照数据库的内部规定的格式, 通常是⼀种压缩格式. 数据必须被解压和转换到另外种格式, 这样它才能被数据的协议处理.
7、获取ResultSet
由于数据库系统对可滚动光标的⽀持有限, 许多JDBC驱动程序并没有实现可滚动光标. 除⾮你确信数据库⽀持可滚动光标的结果集, 否则不要调⽤rs.last()和rs.getRow()⽅法去找出数据集的最⼤⾏数. 因为JDBC驱动程序模拟了可滚动光标, 调⽤rs.last()导致了驱动程序透过⽹络移到了数据集的最后⼀⾏. 取⽽代之, 你可以⽤ResultSet遍历⼀次计数或者⽤SELECT查询的COUNT函数来得到数据⾏数.
通常情况下,请不要写那种依赖于结果集⾏数的代码, 因为驱动程序必须获取所有的数据集以便知道查询会返回多少⾏数据.
三、preparestatement 防⽌sql注⼊
在JDBC应⽤中,如果你已经是稍有⽔平开发者,你就应该始终以PreparedStatement代替Statement.也就是说,在任何时候都不要使⽤Statement.基于以下的原因:
1、代码的可读性和可维护性.虽然⽤PreparedStatement来代替Statement会使代码多出⼏⾏,但这样的代码⽆论从可读性还是可维护性上来说.都⽐直接⽤Statement的代码⾼很多档次:
perstmt = con.prepareStatement("inrt into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.tString(1,var1);perstmt.tString(2,var2);perstmt.tString(3,var3);perstmt.tString(4,var4);
不⽤我多说,对于第⼀种⽅法.别说其他⼈去读你的代码,就是你⾃⼰过⼀段时间再去读,都会觉得伤⼼.

本文发布于:2023-07-05 09:40:38,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1068619.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:数据   数据库   对象   语句   光标   查询   性能   处理
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图