JDBC Statement与PreparedStatement的区别

JDBC预编译sql语句处理

时间:20180311


使用Statement与PreparedStatement执行DDL与DML的区别?

/**
 * jdbc通用方法
 * @author mengjie
 *
 */
public class JdbcUtil {
    //url
    private static String url = "jdbc:mysql://localhost:3306/day16";
    //user 
    private static String user  = "root";
    //password
    private static String password = "root";
    
    /**
     *z只注册一次驱动,静态代码块 
     */
    static {
        //注册驱动程序
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    /**
     * 获取连接的方法
     * @throws SQLException 
     */
    public static Connection getConnection() {
        try {
            Connection conn = DriverManager.getConnection(url, user, password);
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放资源的方法
     */
    public static void close(Statement stmt, Connection conn) {
        if(stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }
    
    /**
     * 释放资源的方法
     */
    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        if(rs!=null) {
            try {
                rs.close();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        if(stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
        
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }
}
/**
 * 使用PreparedStatement执行CRUD操作
 * 执行预编译的sql语句
 * @author mengjie
 *
 */
public class Demo1 {
    public static void main(String[] args) {
        //insert();
        //update();
        query();
    }

    private static void query() {
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet res = null;
        try{
            //1)建立连接
            conn = JdbcUtil.getConnection();
            //2) 创建sql
            String sql = "select * from student where id = ?";
            //3) 创建PreparedStatement,预编译sql语句
            stmt = conn.prepareStatement(sql);
            //4) 给参数赋值
            /**
             * 注意参数的位置,一定要对应字段的位置
             */
            stmt.setInt(1, 5);
            //5) 执行sql
            res = stmt.executeQuery();
            while(res.next()) {
                int id = res.getInt("id");
                String name = res.getString("name");
                int age = res.getInt("age");
                System.out.println(id + "\t" +name+"\t"+age);
            }
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtil.close(stmt, conn);
        }
    }

    private static void update() {
        /**
         * 修改
         */
        Connection conn = null;
        PreparedStatement stmt = null;
        try{
            //1)建立连接
            conn = JdbcUtil.getConnection();
            //2) 创建sql
            String sql = "update student set name = ?, age = ? where id = ?";
            //3) 创建PreparedStatement,预编译sql语句
            stmt = conn.prepareStatement(sql);
            //4) 给参数赋值
            /**
             * 注意参数的位置,一定要对应字段的位置
             */
            stmt.setInt(3, 4);
            stmt.setString(1, "wanzhe");
            stmt.setInt(2, 100);
            //5) 执行sql
            int n = stmt.executeUpdate();
            System.out.println("影响了"+ n + "行");
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            JdbcUtil.close(stmt, conn);
        }
    }

    private static void insert() {
        /**
         * 插入
         */
        Connection conn = null;
        PreparedStatement stmt = null;
        try {
            //1)获取连接
            conn = JdbcUtil.getConnection();
            //2)准备sql//?号是参数的占位符,一个问好代表是一个参数。
            String sql = "insert into student(id,name,age) values(?,?,?)";//
            //3)创建PreparedStatement,预编译sql语句
            stmt = conn.prepareStatement(sql);//预编译sql语句(sql语法和权限的检查),
            //4)给参数赋值
            /**
             * 参数一: 参数的位置。从1开始
             * 参数二:参数值
             */
            stmt.setInt(1, 5);
            stmt.setString(2, "里斯");
            stmt.setInt(3, 40);
            //5)发送参数到数据库,执行sql,
            //executeUpdate方法中并没有参数,是将参数和sql组装起来,并执行
            int n = stmt.executeUpdate();
            System.out.println("影响了"+ n + "行");
        }catch(Exception e){
            e.printStackTrace( );
        }finally {
            //关闭资源,而且不用更改close方法(此时传递的stmt是preparedStatement对象),不用重载
            //实现参数为preparedStatement对象的close方法
            //原因是statement是preparedstatement类的父类
            JdbcUtil.close(stmt, conn);
        }
    }
}

为什么要使用PreparedStatement?

1.PreparedStatement接口继承了Statement,PreparedStatement实例包含已编译的SQL语句,所以其执行速度要快于Statement对象。
2.作为Statement的子类,PreparedStatment继承了Statement的所有功能。三种方法execute,executeQuery和executeUpdate已被更改以使之不再需要参数。
3.在JDBC应用中,在任何时候都不要是使用Statement,原因如下:
  1)、代码的可读性和可维护性。Statement需要不断地拼接,而PreparedStatement不会。
  2)、PreparedStatement尽最大可能提高性能。DB有缓存机制,相同的预编译语句再次被调用不会再次需要编译。
  3)、最重要的一点是极大的提高了安全性。Statement容易被SQL注入,而PreparedStatement传入的内容和参数不会和sql语句发生任何匹配关系。

关于statement和PreparedStatement的安全性做具体分析如下:

例如用户登陆的案例
在数据库中操作:

--创建用户表
CREATE TABLE user_list(
    id INT PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(20),
    PASSWORD VARCHAR(20)
)
--插入数据
INSERT INTO user_list(NAME,PASSWORD) VALUES('eric','123456');
INSERT INTO user_list(NAME,PASSWORD) VALUES('jacky','654321');
SELECT * FROM user_list;

如何判断登陆是否成功呢?根据下列sql:

--登陆成功
SELECT * FROM user_list WHERE NAME='eric' AND PASSWORD='123456'
能成功登陆的情况

但是在数据库sql中存在如下情况:

SELECT * FROM user_list WHERE 1=1; --1=1; 恒成立
sql恒成立的情况

登陆失败的情况

SELECT * FROM user_list WHERE NAME='rose' AND PASSWORD='123456'

可将上述sql更改为恒成立情况,注意-- 前后的空格

SELECT * FROM user_list WHERE NAME ='rose' OR 1=1 -- ' AND PASSWORD='123456';
sql注入的情况

以上都是在客户端中执行的sql语句进行的测试对比
分割线


以下都是在代码中执行的sql语句进行的测试对比

分别在Statement和PreparedStatement中执行带有sql注入的sql,验证Statement和PreparedStatement的安全性。

  • Statement情况
public class Demo2 {
    private static Connection conn = null;
    private static Statement stmt = null;
    private static ResultSet res = null;
    public static void main(String[] args) throws Exception {
                String username = "rose";
                String password = "123456";
        conn = JdbcUtil.getConnection();
        stmt = conn.createStatement();
        String sql = "SELECT * FROM user_list WHERE NAME =' "+username+" ' AND PASSWORD=' "+password+" '";
        res = stmt.executeQuery(sql);
        if(res.next()) {
            System.out.println("登陆成功");
        }else {
            System.out.println("登陆失败");
        }
    }
}

结果:
登陆失败

  • Statement SQL注入的情况
public class Demo2 {
    private static Connection conn = null;
    private static Statement stmt = null;
    private static ResultSet res = null;
    public static void main(String[] args) throws Exception {
                String username = "rose' OR 1=1 -- ";
                String password = "123456";
        conn = JdbcUtil.getConnection();
        stmt = conn.createStatement();
        String sql = "SELECT * FROM user_list WHERE NAME =' "+username+" ' AND PASSWORD=' "+password+" '";
        res = stmt.executeQuery(sql);
        if(res.next()) {
            System.out.println("登陆成功");
        }else {
            System.out.println("登陆失败");
        }
    }
}

结果:
登陆成功

  • PreparedStatement情况
public class Demo2 {
    private static Connection conn = null;
    private static PreparedStatement stmt = null;
    private static ResultSet res = null;
    public static void main(String[] args){
        conn = JdbcUtil.getConnection();
        String sql = "SELECT * FROM user_list WHERE NAME = ? AND PASSWORD= ?";
        try {
            stmt = conn.prepareStatement(sql);
            stmt.setString(1, "rose' OR 1=1 -- ");
            stmt.setString(2, "123456");
            res = stmt.executeQuery();
            if(res.next()) {
                System.out.println("登陆成功");
            }else {
                System.out.println("登陆失败");
            }
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
            JdbcUtil.close(res,stmt,conn);
        }
    }
}

结果:
登陆失败

解析PreparedStatement是如何防止SQL注入的?

  • 执行stmt = conn.prepareStatement(sql);(此句话叫预编译sql)1.先将sql发送至数据库。2.发送之后验证sql的语法。3.验证sql中语法发现sql中有两个参数NAME、PSASSWORD(该sql执行时必须要有NAME、PSASSWORD两个个参数)。
  • 执行stmt.setString(1, "rose' OR 1=1 -- ");
    stmt.setString(2, "123456");参数赋值。
  • 执行stmt.executeQuery();即执行sql,就将赋值的两个参数塞进验证sql语法验证的两个参数中去。此时"rose' OR 1=1 -- "是作为NAME的参数,因此回去数据库中查找NAME名为"rose' OR 1=1 -- "的数据,显然是查询不到的,说明PreparedStatement传入的内容不会和sql语句发生任何关系
  • Statement中并不进行sql语法验证(参数验证)
Statement的情况

说明PreparedStatement传入的内容不会和sql语句发生任何关系,这也是statement中的sql(静态sql)能被注入sql的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • JDBC简介 SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC。JDBC...
    奋斗的老王阅读 1,490评论 0 51
  • 本节介绍Statement接口及其子类PreparedStatement和CallableStatement。 它...
    zlb阅读 1,110评论 0 0
  • 以下我是归纳的JDBC知识点图: 图上的知识点都可以在我其他的文章内找到相应内容。 JDBC常见面试题 JDBC操...
    Java3y阅读 1,718评论 0 15
  • 趋利避害是动物的特性,这并不羞于谈起,正是因为人类的聪慧,把这种行为发挥到了极致,才使得我们在大自然的变迁中中存活...
    草莓布丁猫阅读 973评论 3 21
  • 佛教对待烦恼的态度: 如果你有办法,不必烦恼。 如果你没办法,不必烦恼。 大乘经典常用类似的分析法,简单透彻。
    吉莲心语阅读 303评论 0 1