• jdbc编程


    一、JDBC常用接口、类介绍

    JDBC提供对独立于数据库统一的API,用以执行SQL命令。API常用的类、接口如下:

    DriverManager,管理JDBC驱动的服务类,主要通过它获取Connection数据库链接,常用方法如下:

    static synchronized Connection getConnection(String url, String user, String password) throws Exception;该方法获得url对应的数据库的连接。

    Connection常用数据库操作方法:

    Statement createStatement throws SQLException: 该方法返回一个Statement对象。
    PreparedStatement prepareStatement(String sql) throws SQLException;该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译。
    CallableStatement prepareCall(String sql) throws SQLException:该方法返回CallableStatement对象,该对象用于存储过程的调用。
    上面的三个方法都是返回执行SQL语句的Statement对象,PreparedStatement、CallableStatement的对象是Statement的子类,
    只有获得Statement之后才可以执行SQL语句。
     
    Connection控制事务的方法:
    Savepoint setSavepoint(): 创建一个保存点
    Savepoint setSavepoint(String name):创建一个带有名称的保存点
    void setTransactionIsolation(int level):设置事务隔离级别
    void rollback():回滚事务
    void rollback(Savepoint savepoint):回滚到指定保存点
    void setAutoCommit(boolean autoCommit): 关闭自动提交,打开事务
    void commit():提交事务
     

    Statement,用于执行SQL语句的API接口,该对象可以执行DDL、DCL语句,也可以执行DML语句,还可以执行SQL查询语句,当执行查询语句是返回结果集,常用方法如下:
    ResultSet  executeQuery(String sql) throws SQLException:该方法用于执行查询语句,并返回查询结果对应的ResultSet对象,该方法只用于查询语句。
    int  executeUpdate(String sql) throws SQLException:该方法用于执行DML语句,并返回受影响的行数;该方法也可以执行DDL,执行DDL返回0;
    boolean execute(String sql) throws SQLException:该方法可以执行任何SQL语句,如果执行后第一个结果是ResultSet对象,则返回true;如果执行后第一个结果为受影响的行数或没有任何结果,则返回false;

    PreparedStatement,预编译的statement对象,PreparedStatement是Statement的子接口,它允许数据库预编译SQL(通常指带参数SQL)语句,以后每次只改变SQL命令参数,避免数据库每次都编译SQL语句,这样性能就比较好。而相对于Statement而言,使用PreparedStatement执行SQL语句时,无需重新传入SQL语句,因为它已经预编译了SQL语句。但是PreparedStatement需要为编译的SQL语句传入参数值,所以它比了如下方法:
    void setXxx(int index, value)根据该方法传入的参数值的类型不同,需要使用不同的方法。
    传入的值的类型根据传入的SQL语句参数而定。
     
    ResultSet
    void close() throws SQLException:释放、关闭ResultSet对象
    boolean absolute(int row):将结果集移动到第几行,如果row是负数,则移动到倒数第几行。如果移动到的记录指针指向一条有效记录,则该方法返回true;
    void beforeFisrt(): 将ResultSet的记录指针定位到首行之前,这是ResultSet结果集记录指针的初始状态:记录指针的起始位置位于第一行之前。
    boolean first():将ResultSet的记录指针定位到首行。如果移动后的记录指针指向一条有效记录,则该方法返回true。
    boolean previous():将ResultSet的记录指针定位到上一行,如果移动后的记录指针指向一条有效记录,则该方法返回true。
    boolean next():将ResultSet的记录指针定位到下一行。如果移动后的记录指针指向一条有效记录,则返回true。
    boolean last():将ResultSet的记录指针定位到最后一行。如果移动后的记录指针指向一条有效记录,则返回true。
    void afterLast():将ResultSet的记录指针定位到最后一行之后。

    二、JDBC编程步骤
    进行jdbc编程步骤大致如下:

    1、加载数据库驱动
    Class.forName(driverClass)
    上面的dirverClass就是数据库驱动类所对应的类路径字符串,根据不同数据库厂商提供的驱动也不同。

    2、通过DriverManager获取数据库的链接
    DriverManager.getConnection(String url, Stirng user, String pass)
    当使用DriverManager来获取链接,需要传入三个参数:分别是数据量的url、用户名、密码。

    3、通过Connection对象创建Statement对象,Connection创建Statement的方法如下三个:
    createStatement()创建基本的Statement对象。
    prepareStatement(String sql):根据传入的sql语句创建预编译的Statement对象。
    prepareCall(String sql):根据传入的sql语句创建CallableStatement对象

    4、Statement执行SQL语句,Statement有三大方法来执行SQL语句:
    execute:可以执行任何SQL语句,单比较麻烦
    executeUpdate:可以执行DML、DDL语句。执行DML返回受影响的SQL语句行数,执行DDL返回0;
    executeQuery:只能执行查询语句,执行后返回代表查询结果的ResultSet对象。
     
    5、操作结果集,针对ResultSet
    主要移动指针和获得值
    next、previous、first、last、beforeFrist、afterLast、absolute等移动指针的方法。
    getXxx获得移动指针指向行,特定列、索引的值。使用列名作为获取值的参数可读性好、使用索引作为获取参数性能好。

    6、关闭数据库连接资源

    三、 使用PreparedStatement执行SQL语句

    使用PreparedStatement的好处在于他可以预编译SQL语句,提高sql的执行效率,并且,在其中可以使用带占位符(?)参数的SQL语句来代替它:insert into student_table values(null,?,?)

    实现方式: 创建PreparedStatement对象使用Connection的preparedStatement()方法,该方法传入一个字符串,该SQL字符串可以包含占位符。pstmt = conn.prepareStatement("insert into student_table values(null,?,1)");

    示例:

     
    1. package com.chen.yuan.jdbc;  
    2.   
    3. import java.io.FileInputStream;  
    4. import java.sql.Connection;  
    5. import java.sql.DriverManager;  
    6. import java.sql.PreparedStatement;  
    7. import java.util.Properties;  
    8.   
    9. public class PreparedStatementDemo {  
    10.     private String driver;  
    11.     private String url;  
    12.     private String user;  
    13.     private String pass;  
    14.   
    15.     /** 
    16.      * 初始化属性参数 
    17.      *  
    18.      * @param paramFile 
    19.      * @throws Exception 
    20.      */  
    21.     public void initParam(String paramFile) throws Exception {  
    22.         Properties props = new Properties();  
    23.         props.load(new FileInputStream(paramFile));  
    24.         this.driver = props.getProperty("driver");  
    25.         this.url = props.getProperty("url");  
    26.         this.user = props.getProperty("user");  
    27.         this.pass = props.getProperty("pass");  
    28.         // 加载数据库驱动  
    29.         Class.forName(driver);  
    30.     }  
    31.   
    32.     /** 
    33.      * 使用PreparedStatement添加数据 
    34.      *  
    35.      * @throws Exception 
    36.      */  
    37.     public void insertUsePrepare() throws Exception {  
    38.         long start = System.currentTimeMillis();  
    39.         Connection conn = null;  
    40.         PreparedStatement pstmt = null;  
    41.         try {  
    42.             conn = DriverManager.getConnection(url, user, pass);  
    43.             pstmt = conn  
    44.                     .prepareStatement("insert into student_table values(null,?,1)");  
    45.             // 向其中插入100条数据  
    46.             for (int i = 0; i < 100; i++) {  
    47.                 pstmt.setString(1, "姓名" + i);  
    48.                 pstmt.executeUpdate();  
    49.             }  
    50.             System.out.println("使用PreparedStatement费时:"  
    51.                     + (System.currentTimeMillis() - start));  
    52.   
    53.         } catch (Exception e) {  
    54.             e.printStackTrace();  
    55.         } finally {  
    56.             if (pstmt != null) {  
    57.                 pstmt.close();  
    58.             }  
    59.             if (conn != null) {  
    60.                 conn.close();  
    61.             }  
    62.         }  
    63.     }  
    64.   
    65.     public static void main(String[] args) throws Exception {  
    66.         PreparedStatementDemo pstmtdemo = new PreparedStatementDemo();  
    67.         pstmtdemo.initParam("mysql.ini");  
    68.         pstmtdemo.insertUsePrepare();  
    69.   
    70.     }  
    71. }  

    使用PreparedStatement除了可以提供sql的执行效率外,还防止SQL注入。

    我们假设,有如下一段程序,直接使用Statement执行sql,这段程序主要是验证用户名和密码,通过之后,可登陆。

     
    1. private boolean validate(String userName, String userPass)  
    2.     {  
    3.         // 执行查询的SQL语句  
    4.         String sql = "select * from jdbc_test "  
    5.             + "where jdbc_name='" + userName   
    6.             + "' and jdbc_desc='" + userPass + "'";  
    7.         System.out.println(sql);  
    8.         try{  
    9.             Connection conn = DriverManager.getConnection(url  
    10.                 , user ,pass);  
    11.             Statement stmt = conn.createStatement();  
    12.             ResultSet rs = stmt.executeQuery(sql);  
    13.           
    14.             // 如果查询的ResultSet里有超过一条的记录,则登录成功  
    15.             if (rs.next())  
    16.             {  
    17.                 return true;  
    18.             }  
    19.         }catch(Exception e)  
    20.         {  
    21.             e.printStackTrace();  
    22.         }  
    23.         return false;  
    24. }  

    如果,我们而已传入userName参数,注入sql语句:

     
    1. # 利用sql注入后生成的sql语句  
    2. select * from jdbc_test where jdbc_name='' or true or '' and jdbc_desc='';  


    看到这条语句我们可以发现,该语句总是可以执行通过的,所以用户可以通过这种sql注入方式侵入一般的系统。

    那我们现在看一下PreparedStatement如何来处理这种登录呢: 

     
    1. private boolean validate(String userName, String userPass)  
    2.     {  
    3.     try{  
    4.         Connection conn = DriverManager.getConnection(url   
    5.             , user ,pass);  
    6.         PreparedStatement pstmt = conn.prepareStatement(  
    7.             "select * from jdbc_test where jdbc_name=? and jdbc_desc=?");  
    8.         pstmt.setString(1, userName);  
    9.         pstmt.setString(2, userPass);  
    10.             ResultSet rs = pstmt.executeQuery())  
    11.             //如果查询的ResultSet里有超过一条的记录,则登录成功  
    12.             if (rs.next())  
    13.             {  
    14.                 return true;  
    15.             }  
    16.     }  
    17.     catch(Exception e)  
    18.     {  
    19.         e.printStackTrace();  
    20.     }  
    21.     return false;  
    22. }  


    总体看来,PreparedStatement预编译SQL语句,性能更好; 无需拼接sql语句,编程更简单; PreparedStatement可以防止依赖注入,安全性好。


    四、 使用CallableStatement调用存储过程

    1) 我们首先需要在mysql或oracle数据库中建立存储过程。下面以mysql为例:

     
    1. delimiter //  
    2. create procedure add_pro(a int,b int, out sum int)  
    3. begin  
    4. set sum = a+b;  
    5. end;  
    6. //  

    关于如何创建存储过程,请参看相关的书籍。

    2) jdbc调用存储过程,需要使用CallableStatement,可以使用Connection的prepareCall()方法来创建CallableStatement对象,创建该对象时需要传入调用存储过程的sql语句。调用存储过程的格式:{call 过程名(?,?,?.....)},其中?作为存储过程的占位符。例如,下面创建一个调用上述存储过程的CallableStatement对象。

    cstmt = conn.prepareCall("{call add_pro(?,?,?)}");


    存储过程有传入参数和传出参数,所谓的传入参数就是Java程序必须为这些参数传入值,可以通过CallableStatement的setXxx()方法为之传入值;所谓传出参数就是Java程序可以通过该参数获取存储过程里值,CallableStatement需要调用registerOutParameter()方法来注册该参数。

    cstmt.registerOutParameter(3,Types.INTEGER);

    示例;

    1. public class CallableStatementTest  
    2. {  
    3.     private String driver;  
    4.     private String url;  
    5.     private String user;  
    6.     private String pass;  
    7.     public void initParam(String paramFile)throws Exception  
    8.     {  
    9.         // 使用Properties类来加载属性文件  
    10.         Properties props = new Properties();  
    11.         props.load(new FileInputStream(paramFile));  
    12.         driver = props.getProperty("driver");  
    13.         url = props.getProperty("url");  
    14.         user = props.getProperty("user");  
    15.         pass = props.getProperty("pass");  
    16.     }  
    17.     public void callProcedure()throws Exception  
    18.     {  
    19.         // 加载驱动  
    20.         Class.forName(driver);  
    21.         try(  
    22.             // 获取数据库连接  
    23.             Connection conn = DriverManager.getConnection(url  
    24.                 , user , pass);  
    25.             // 使用Connection来创建一个CallableStatment对象  
    26.             CallableStatement cstmt = conn.prepareCall(  
    27.                 "{call add_pro(?,?,?)}"))  
    28.         {  
    29.             cstmt.setInt(1, 4);  
    30.             cstmt.setInt(2, 5);  
    31.             // 注册CallableStatement的第三个参数是int类型  
    32.             cstmt.registerOutParameter(3, Types.INTEGER);  
    33.             // 执行存储过程  
    34.             cstmt.execute();  
    35.             // 获取,并输出存储过程传出参数的值。  
    36.             System.out.println("执行结果是: " + cstmt.getInt(3));  
    37.         }  
    38.     }  
    39.     public static void main(String[] args) throws Exception  
    40.     {  
    41.         CallableStatementTest ct = new CallableStatementTest();  
    42.         ct.initParam("mysql.ini");  
    43.         ct.callProcedure();  
    44.     }  
    45. }  



    总结:(节摘自http://www.cnblogs.com/hoojo/archive/2011/06/10/2077643.html)

    1、 executeUpdate执行DDL、DML语句

    Statement提供了execute、executeUpdate、executeQuery三种方法执行,下面用executeUpdate来执行DDL、DML语句,
    executeUpdate执行DDL返回值是0,执行了DML是返回影响后的记录条数。

    2、 execute执行SQL语句

    当我们知道SQL语句是完成修改语句时,我们就知道使用executeUpdate语句来完成操作;
    如果SQL语句是完成查询操作的时候,我们就使用executeQuery来完成。
    如果我们不知道SQL语句完成什么操作的时候,就可以使用execute方法来完成。
    当我们使用Statement对象的execute方法执行SQL语句后返回的是boolean值,这就说明该语句能否返回ResultSet对象。
    那么,如何判断是否是ResultSet对象?方法如下:
    getResultSet():获取该Statement执行查询语句返回的ResultSet对象
    getUpdateCount():获取该Statement执行修改语句影响的行数

    3、 PrepareStatement执行SQL语句

    对于我们操作数据库的时候,执行某一条SQL语句的时候。只有它的参数不同,而SQL语句相同。
    我们可以使用占位符来设置我们的参数信息,PrepareStatement中的占位符是?,用?代替参数的位置。
    insert into table values(?, ‘abc’, ?);
    占位符仅仅支持PrepareStatement,而Statement不支持占位符。PrepareStatement是预编译SQL语句的,
    然后将占位符替换成参数。而Statement就不能做到。
     
    PrepareStatement对象也有execute、executeUpdate、executeQuery这三个方法,但这三个方法都无需传递参数。
    只需用PrepareStatement来设置占位符的参数,通过用setXxxx(index, value)来完成设置参数信息即可。
    PrepareStatement的效率要比Statement的效率高。
    PrepareStatement设置参数可以不拼接字符串,而Statement设置参数信息的时候需要手动拼接字符串。
    拼接字符串容易操作程序错误、可读性降低、维护性升高、程序性能下降。而PrepareStatement直接设置参数
    信息就降低了编程的复杂度。并且它可以放在SQL注入。因为它是通过setXxx方法进行设置参数信息,
    而Statement是通过拼接字符串,很容易就造成SQL注入。
     
    综上所述,PrepareStatement比Statement有以下优点:
    预编译SQL语句,性能更好
    无需拼接SQL语句,编程更简单
    可以防止SQL语句注入,安全性更好

    4、 CallableStatement调用存储过程

    存储过程的调用可以通过CallableStatement,通过Connection对象的prepareCall方法来创建CallableStatement对象。
    然后传入存储过程的SQL语句,即可调用存储过程,格式如下:
    {call proc_name(?, ?, ?)}
    上面的?是占位符,表示传递的参数。
    存储过程有传入参数、传出参数。传入参数是程程序必须传入的参数,可以 通过setXxx方法进行设置参数值。
    而传出参数则需要通过程序进行设置,可以用CallableStatement对象的registerOutParameter方法来
    注册输出参数,cs.registerOutParameter(3, Types.STRING);
    设置完毕后,当调用存储过程后要获取输出参数值,可以通过getXxx方法来完成。
  • 相关阅读:
    【ZJOI2007】矩阵游戏
    【洛谷1402】酒店之王
    【洛谷2756】飞行员配对方案问题
    【BZOJ2125】最短路
    【SDOI2018】战略游戏
    【APIO2018】铁人两项
    【FJOI2014】最短路径树问题
    【GXOI/GZOI2019】旅行者
    【Cerc2012】Farm and factory
    【CERC2017】Gambling Guide
  • 原文地址:https://www.cnblogs.com/eer123/p/8525966.html
Copyright © 2020-2023  润新知