ps=conn.preparestatement in(sql)这句话怎么会抛指针异常呢

Statement, PreparedStatement, CallableStatement
一旦建立好连接, 就可以与数据库交互. JDBC 中Statement, PreparedStatement 和 CallableStatement 提供了SQL操作的相关API. 其中 CallableStatement 继承自 PreparedStatement, 而 PreparedStatement 又继承自 Statement. 他们的区别是:
Statement 提供基本的 SQL 操作. 适合静态SQL语句, 且传入的 SQL 语句无法接受参数.
PreparedStatement 可以在 SQL 中传递参数, 适合多次使用的 SQL 语句.
CallableStatement 可以调用 PL/SQL 存储过程.
尽管接口功能有不同, 但是使用方式大体相同, 分以下几步:
创建 Statement
执行 SQL 语句
关闭 Statement
在执行 SQL 语句的时候, 常用以下几个方法:
boolean execute(String SQL) : 如果有 ResultSet 产生返回true, 否则, 返回 false. 一般用于 CREATE, ALTER 这些操作, 或者用来检查一个Query有没有返回.
int executeUpdate(String SQL) : 返回被影响的记录的条数, 一般用于 INSERT, UPDATE, DELETE 这些操作.
ResultSet executeQuery(String SQL) : 返回查询结果集, 专用语 SELECT.
以下三个例子分别示例了如何适用他们.
Statement 例子.
public class StatementExample {
private Properties dbProps = new Properties();
StatementExample() {}
public void setDBProperties(Properties dbProps) {
this.dbProps = dbP
public Connection getConnection() throws SQLException {
String url = dbProps.getProperty(&url&);
String user = dbProps.getProperty(&user&);
Connection conn =
if (user.length() == 0) {
conn = DriverManager.getConnection(url);
conn = DriverManager.getConnection(url, dbProps);
String dbName = dbProps.getProperty(&dbName&);
conn.setCatalog(dbName);
public void deleteAll() throws SQLException {
String sql = &DELETE FROM posts&;
Connection conn = getConnection();
Statement stmt = conn.createStatement();
int nRows = stmt.executeUpdate(sql);
System.out.println(nRows + (nRows == 1 ? & post is & : & posts are &) + &deleted.&);
stmt.close();
conn.close();
public void insertPost(Post post) throws SQLException {
String sql = &INSERT INTO posts VALUES&;
String title = post.getTitle();
String content = post.getContent();
Boolean visible = post.isVisible();
sql += &(&
+ &NULL& + &,&
+ &\&& +title + &\&& + &,&
+ &\&& + content + &\&& + &,&
+ &DEFAULT& + &,&
+ (visible ? &TRUE& : &FALSE&)
Connection conn = getConnection();
Statement stmt = conn.createStatement();
int nRows = stmt.executeUpdate(sql);
stmt.close();
conn.close();
public ArrayList&Post& queryAll() throws SQLException {
ArrayList&Post& list = new ArrayList&Post&();
String sql = &SELECT * FROM posts&;
Connection conn = getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
rs.beforeFirst();
while (rs.next()) {
Post temp = new Post();
temp.setId(rs.getInt(&id&));
temp.setTitle(rs.getString(&title&));
temp.setContent(rs.getString(&content&));
temp.setDate(rs.getTimestamp(&dt_create&));
temp.setVisible(rs.getBoolean(&visible&));
list.add(temp);
stmt.close();
conn.close();
public static void main(String[] args) {
Properties props = new Properties();
props.load(ClassLoader.getSystemResourceAsStream(&db.mysql.props&));
StatementExample example = new StatementExample();
example.setDBProperties(props);
ArrayList&Post& posts = example.queryAll();
System.out.println(posts);
Post toInsert = new Post();
toInsert.setTitle(&new Post&);
toInsert.setContent(&This is a new post!&);
example.insertPost(toInsert);
posts = example.queryAll();
System.out.println(posts);
example.deleteAll();
posts = example.queryAll();
System.out.println(posts);
} catch (SQLException e) {
DBUtils.printSQLException(e);
} catch (Exception e) {
e.printStackTrace();
PreparedStatement 例子.
public class PreparedStatExample {
private Properties dbProps = new Properties();
PreparedStatExample() {}
public void setDBProperties(Properties dbProps) {
this.dbProps = dbP
public Connection getConnection() throws SQLException {
String url = dbProps.getProperty(&url&);
String user = dbProps.getProperty(&user&);
Connection conn =
if (user.length() == 0) {
conn = DriverManager.getConnection(url);
conn = DriverManager.getConnection(url, dbProps);
String dbName = dbProps.getProperty(&dbName&);
conn.setCatalog(dbName);
public void deletePost(int id) throws SQLException {
String sql = &DELETE FROM posts WHERE id = ?&;
Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, id);
stmt.executeUpdate();
DBUtils.printWarnings(stmt.getWarnings());
stmt.close();
conn.close();
public void insertPost(Post post) throws SQLException {
String sql = &INSERT INTO posts VALUES(NULL, ?, ?, DEFAULT, ?)&;
Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, post.getTitle());
stmt.setString(2, post.getContent());
stmt.setBoolean(3, post.isVisible());
stmt.executeUpdate();
DBUtils.printWarnings(stmt.getWarnings());
stmt.close();
conn.close();
public ArrayList&Post& queryByTitle(String title) throws SQLException {
ArrayList&Post& list = new ArrayList&Post&();
String sql = &SELECT * FROM posts WHERE title like ?&;
Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, title);
ResultSet rs = stmt.executeQuery();
rs.beforeFirst();
while (rs.next()) {
Post temp = new Post();
temp.setId(rs.getInt(&id&));
temp.setTitle(rs.getString(&title&));
temp.setContent(rs.getString(&content&));
temp.setDate(rs.getTimestamp(&dt_create&));
temp.setVisible(rs.getBoolean(&visible&));
list.add(temp);
stmt.close();
conn.close();
public static void main(String[] args) {
Properties props = new Properties();
props.load(ClassLoader.getSystemResourceAsStream(&db.mysql.props&));
PreparedStatExample example = new PreparedStatExample();
example.setDBProperties(props);
// 此时数据库中有一条 title 为 111 的数据
ArrayList&Post& posts = example.queryByTitle(&111&);
System.out.println(posts); //[Post{id=34, title='111', content='111', date= 12:58:32.0, visible=true}]
Post toInsert = new Post();
toInsert.setTitle(&111&);
toInsert.setContent(&111111&);
example.insertPost(toInsert);
posts = example.queryByTitle(&111&);
System.out.println(posts); // [Post{id=39, title='111', content='111', date= 13:00:49.0, visible=true}, Post{id=41, title='111', content=';, date= 13:00:59.0, visible=false}]
example.deletePost(posts.get(0).getId());
posts = example.queryByTitle(&111&);
System.out.println(posts); // [Post{id=41, title='111', content=';, date= 13:00:59.0, visible=false}]
} catch (SQLException e) {
DBUtils.printSQLException(e);
} catch (Exception e) {
e.printStackTrace();
CallableStatement 例子.
public class CallableStatExample {
private Properties dbProps = new Properties();
public CallableStatExample() {}
public void setDBProperties(Properties dbProps) {
this.dbProps = dbP
public Connection getConnection() throws SQLException {
String url = dbProps.getProperty(&url&);
String user = dbProps.getProperty(&user&);
Connection conn =
if (user.length() == 0) {
conn = DriverManager.getConnection(url);
conn = DriverManager.getConnection(url, dbProps);
String dbName = dbProps.getProperty(&dbName&);
conn.setCatalog(dbName);
public String getTitleById(int id) throws SQLException {
Connection conn = getConnection();
String sql = &{call getTitleById (?, ?)}&;
CallableStatement stmt = conn.prepareCall(sql);
// 绑定传入参数
stmt.setInt(1, id);
// 对于传出参数, 要先注册
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
stmt.execute();
String title = stmt.getString(2);
stmt.close();
conn.close();
public static void main(String[] args) throws IOException, SQLException {
Properties props = new Properties();
props.load(ClassLoader.getSystemResourceAsStream(&db.mysql.props&));
CallableStatExample example = new CallableStatExample();
example.setDBProperties(props);
int id = 35;
String title = example.getTitleById(id);
System.out.println(&Find title : & + title + & by ID : & + id); // Find title : 222 by ID : 35
Note: 请先将以下存储过程存入 MySQL 数据库
DELIMITER $$
CREATE PROCEDURE `testdb`.`getTitleById`
(IN post_id INT, OUT post_name VARCHAR(255))
SELECT title INTO post_name
FROM posts
WHERE ID = post_
DELIMITER ;
SQL 的批处理操作
SQL 批处理能够允许添加多个 SQL 到 一个Statement对象, 并一并提交执行结果. 这减少了与 SQL 通信的频率. 但是, SQL 批处理不是 JDBC 要求一定要支持的. 使用前应该用 DatabaseMetaData.supportsBatchUpdates() 检查支持情况.
SQL 批处理相关的 API 有:
Statement.addBatch(): 往批处理中添加 SQL 语句
Statement.executeBatch(): 执行批处理, 并返回一个整型数组, 其中每个元素代表对应序号 SQL 的执行结果.
Statement.clearBatch(): 从批处理中删除已添加的所有 SQL 语句.
以下示例如何使用批处理往数据库添加数据:
public static void batchInsertPosts(ArrayList&Post& posts) throws SQLException {
Connection conn = getConnectionFromDS(dbProps);
conn.setAutoCommit(false);
// 见 &事务& 一章
DatabaseMetaData md = conn.getMetaData();
System.out.println(&If support batch updates: & + md.supportsBatchUpdates());
String sql = &INSERT INTO POSTS\n&
+ &VALUES(NULL, ?, ?, DEFAULT, ?)&;
PreparedStatement stmt = conn.prepareCall(sql);
for (Post post : posts) {
stmt.setString(1, post.getTitle());
stmt.setString(2, post.getContent());
stmt.setBoolean(3, post.isVisible());
stmt.addBatch();
stmt.executeBatch();
} catch (SQLException e) {
DBUtils.printSQLException(e);
conn.rollback();
DBUtils.printWarnings(stmt.getWarnings());
stmt.close();
conn.close();
SQL 异常处理
JDBC 中最常用的异常就是 SQLException, 不管是在建立连接, 还是在执行 SQL 语句的时候, 都有可能抛出这个异常. SQLException 包含以下信息:
关于错误的描述. 通过调用getMessage() 获得.
一个 SQL 状态码. 通过调用 getSQLState( ) 获取. SQL 状态码由5位字母和数字组成, 符合 XOPEN 规范.
一个错误码. 这个错误码的含义由实现规定, 有可能是数据库的错误码. 通过调用 SQLException.getErrorCode() 获取.
错误缘由. 引发异常的缘由, 有可能是一个或者多个 Throwable 的对象组成的一条链. 要想检查这些缘由, 要递归遍历 SQLException.getCause() 直到返回一个 null.
异常链. 通过 getNextException() 获取下一个异常.
以下代码示例如何打印异常链中的每个SQLException异常, 并且打印每个异常的 cause 链.
public static void printSQLException(SQLException ex) {
for (Throwable e : ex) {
// Iterator 会调用 getNextException()
if (e instanceof SQLException) {
e.printStackTrace(System.err);
System.err.println(&SQLState: & +
((SQLException)e).getSQLState());
System.err.println(&Error Code: & +
((SQLException)e).getErrorCode());
System.err.println(&Message: & + e.getMessage());
Throwable t = ex.getCause();
while(t != null) {
// 打印每个 cause
System.out.println(&Cause: & + t);
t = t.getCause();
除了发生致命错误产生抛出 SQLException 之外, Connection, Statement, ResultSet 都有一个 getWarnings() 方法, 它返回一个 SQLWarning. SQLWarning 继承自 SQLException, 可以向遍历 SQLException 一样遍历它:
public static void printWarnings(SQLWarning warning)
throws SQLException {
while (warning != null) {
System.out.println(&Message: & + warning.getMessage());
System.out.println(&SQLState: & + warning.getSQLState());
System.out.print(&Vendor error code: &);
System.out.println(warning.getErrorCode());
System.out.println(&&);
warning = warning.getNextWarning();
本文目前还没有评论……<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&JDBC为什么要使用PreparedStatement而不是Statement - ImportNew
| 分类: ,
PreparedStatement是用来执行SQL查询语句的API之一,Java提供了 Statement、PreparedStatement 和 CallableStatement三种方式来执行查询语句,其中 Statement 用于通用查询, PreparedStatement 用于执行参数化查询,而 CallableStatement则是用于存储过程。同时PreparedStatement还经常会在Java面试被提及,譬如:Statement与PreparedStatement的区别以及如何避免SQL注入式攻击?这篇教程中我们会讨论为什么要用PreparedStatement?使用PreparedStatement有什么样的优势?PreparedStatement又是如何避免SQL注入攻击的?
PreparedStatement是什么?
PreparedStatement是java.sql包下面的一个接口,用来执行SQL语句查询,通过调用connection.preparedStatement(sql)方法可以获得PreparedStatment对象。数据库系统会对sql语句进行预编译处理(如果JDBC驱动支持的话),预处理语句将被预先编译好,这条预编译的sql查询语句能在将来的查询中重用,这样一来,它比Statement对象生成的查询速度更快。下面是一个例子:
public class PreparedStmtExample {
public static void main(String args[]) throws SQLException {
Connection conn = DriverManager.getConnection(&mysql:\\localhost:1520&, &root&, &root&);
PreparedStatement preStatement = conn.prepareStatement(&select distinct loan_type from loan where bank=?&);
preStatement.setString(1, &Citibank&);
ResultSet result = preStatement.executeQuery();
while(result.next()){
System.out.println(&Loan Type: & + result.getString(&loan_type&));
Loan Type: Personal Loan
Loan Type: Auto Loan
Loan Type: Home Loan
Loan Type: Gold Loan
这个例子中,如果还是用 PreparedStatement 做同样的查询,哪怕参数值不一样,比如:&#8221;Standard Chated&#8221; 或者&#8221;HSBC&#8221;作为参数值,数据库系统还是会去调用之前编译器编译好的执行语句(系统库系统初次会对查询语句做最大的性能优化)。默认会返回&#8221;TYPE_FORWARD_ONLY&#8221;类型的结果集( ResultSet ),当然你也可以使用preparedstatment()的重载方法返回不同类型的结果集。
预处理语句的优势
PreparedStatement提供了诸多好处,企业级应用开发中强烈推荐使用PreparedStatement来做SQL查询,下面列出PreparedStatement的几点优势。
PreparedStatement可以写动态参数化的查询
用PreparedStatement你可以写带参数的sql查询语句,通过使用相同的sql语句和不同的参数值来做查询比创建一个不同的查询语句要好,下面是一个参数化查询:
SELECT interest_rate FROM loan WHERE loan_type=?
现在你可以使用任何一种loan类型如:&#8221;personal loan&#8221;,&#8221;home loan&#8221; 或者&#8221;gold loan&#8221;来查询,这个例子叫做参数化查询,因为它可以用不同的参数调用它,这里的&#8221;?&#8221;就是参数的占位符。
PreparedStatement比 Statement 更快
使用 PreparedStatement 最重要的一点好处是它拥有更佳的性能优势,SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。为了减少数据库的负载,生产环境中德JDBC代码你应该总是使用PreparedStatement 。值得注意的一点是:为了获得性能上的优势,应该使用参数化sql查询而不是字符串追加的方式。下面两个SELECT 查询,第一个SELECT查询就没有任何性能优势。
SQL Query 1:字符串追加形式的PreparedStatement
String loanType = getLoanType();
PreparedStatement prestmt = conn.prepareStatement(&select banks from loan where loan_type=& + loanType);
SQL Query 2:使用参数化查询的PreparedStatement
PreparedStatement prestmt = conn.prepareStatement(&select banks from loan where loan_type=?&);
prestmt.setString(1,loanType);
第二个查询就是正确使用PreparedStatement的查询,它比SQL1能获得更好的性能。
PreparedStatement可以防止SQL注入式攻击
如果你是做Java web应用开发的,那么必须熟悉那声名狼藉的SQL注入式攻击。去年Sony就遭受了SQL注入攻击,被盗用了一些Sony play station(PS机)用户的数据。在SQL注入攻击里,恶意用户通过SQL元数据绑定输入,比如:某个网站的登录验证SQL查询代码为:
strSQL = &SELECT * FROM users WHERE name = &#039;& + userName + &&#039; and pw = &#039;&+ passWord +&&#039;;&
恶意填入:
userName = &1&#039; OR &#039;1&#039;=&#039;1&;
passWord = &1&#039; OR &#039;1&#039;=&#039;1&;
那么最终SQL语句变成了:
strSQL = &SELECT * FROM users WHERE name = &#039;1&#039; OR &#039;1&#039;=&#039;1&#039; and pw = &#039;1&#039; OR &#039;1&#039;=&#039;1&#039;;&
因为WHERE条件恒为真,这就相当于执行:
strSQL = &SELECT * FROM&
因此可以达到无账号密码亦可登录网站。如果恶意用户要是更坏一点,用户填入:
strSQL = &SELECT * FROM&
SQL语句变成了:
strSQL = &SELECT * FROM users WHERE name = &#039;any_value&#039; and pw = &#039;&#039;; DROP TABLE users&
这样一来,虽然没有登录,但是数据表都被删除了。
然而使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统(eg:MySQL)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破坏性的指令,也不会被数据库所运行。
补充:避免SQL注入的第二种方式:
在组合SQL字符串的时候,先对所传入的参数做字符取代(将单引号字符取代为连续2个单引号字符,因为连续2个单引号字符在SQL数据库中会视为字符中的一个单引号字符,譬如:
strSQL = &SELECT * FROM users WHERE name = &#039;& + userName + &&#039;;&
传入字符串:
= & 1&#039; OR 1=1 &
把userName做字符替换后变成:
userName = & 1&#039;&#039; OR 1=1&
最后生成的SQL查询语句为:
strSQL = &SELECT * FROM users WHERE name = &#039;1&#039;&#039; OR 1=1&#039;
这样数据库就会去系统查找name为“1&#8242; &#8216; OR 1=1”的记录,而避免了SQL注入。
比起凌乱的字符串追加似的查询,PreparedStatement查询可读性更好、更安全。
PreparedStatement的局限性
尽管PreparedStatement非常实用,但是它仍有一定的限制。
1. 为了防止SQL注入攻击,PreparedStatement不允许一个占位符(?)有多个值,在执行有**IN**子句查询的时候这个问题变得棘手起来。下面这个SQL查询使用PreparedStatement就不会返回任何结果
SELECT * FROM loan WHERE loan_type IN (?)
preparedSatement.setString(1, &&#039;personal loan&#039;, &#039;home loan&#039;, &#039;gold loan&#039;&);
那如何解决这个问题呢?请你继续关注本博客,下期告诉你答案。
不算总结的总结
关于PreparedStatement接口,需要重点记住的是:
1. PreparedStatement可以写参数化查询,比Statement能获得更好的性能。
2. 对于PreparedStatement来说,数据库可以使用已经编译过及定义好的执行计划,这种预处理语句查询比普通的查询运行速度更快。
3. PreparedStatement可以阻止常见的SQL注入式攻击。
4. PreparedStatement可以写动态查询语句
5. PreparedStatement与java.sql.Connection对象是关联的,一旦你关闭了connection,PreparedStatement也没法使用了。
6. &#8220;?&#8221; 叫做占位符。
7. PreparedStatement查询默认返回FORWARD_ONLY的ResultSet,你只能往一个方向移动结果集的游标。当然你还可以设定为其他类型的值如:&#8221;CONCUR_READ_ONLY&#8221;。
8. 不支持预编译SQL查询的JDBC驱动,在调用connection.prepareStatement(sql)的时候,它不会把SQL查询语句发送给数据库做预处理,而是等到执行查询动作的时候(调用executeQuery()方法时)才把查询语句发送个数据库,这种情况和使用Statement是一样的。
9. 占位符的索引位置从1开始而不是0,如果填入0会导致*java.sql.SQLException invalid column index*异常。所以如果PreparedStatement有两个占位符,那么第一个参数的索引时1,第二个参数的索引是2.
以上就是为什么要使用PreparedStatement的全部理由,不过你仍然可以使用Statement对象用来做做测试。但是在生产环境下你一定要考虑使用 PreparedStatement 。
更多参考:
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
程序员,关注 Java、Python、云计算,移动互联网。(新浪微博:)
Hello, yeah this post is actually fastidious and I...
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
&#8211; 好的话题、有启发的回复、值得信赖的圈子
&#8211; 写了文章?看干货?去头条!
&#8211; 为IT单身男女服务的征婚传播平台
&#8211; 优秀的工具资源导航
&#8211; 活跃 &#038; 专业的翻译小组
&#8211; 国内外的精选博客文章
&#8211; UI,网页,交互和用户体验
&#8211; JavaScript, HTML5, CSS
&#8211; 专注Android技术分享
&#8211; 专注iOS技术分享
&#8211; 专注Java技术分享
&#8211; 专注Python技术分享
& 2016 ImportNew}

我要回帖

更多关于 preparestatement in 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信