• JDBC的基本使用


    1、JDBC和JDBC驱动的基本概念

    JDBC(Java DataBase Connectivity),指 Java 数据库连接,是一种标准Java应用编程接口(JAVA API),是 Java 语言用来连接和操作数据库的。使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问。而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。

    实际上,JDBC 就是官方定义的一套操作所有关系型数据库的规则,也就是接口。而 JDBC 驱动就是实现了这些接口的实现类,各个数据库厂商去实现这些接口,也就是 JDBC 驱动,提供数据库驱动 jar 包,我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动 jar 包中的实现类。

    JDBC 是一套接口规范,它在Java的标准库java.sql里放着,不过这里面大部分都是接口。接口并不能直接实例化,而是必须实例化对应的实现类,然后通过接口引用这个实例。JDBC接口的实现类就是 JDBC 驱动。JDBC接口并不知道我们要使用哪个数据库,所以,用哪个数据库,我们就去使用哪个数据库的“实现类”,我们把某个数据库实现了JDBC接口的jar包称为 JDBC 驱动

    例如,我们在Java代码中要访问MySQL,那么必须编写代码操作JDBC接口。注意到JDBC接口是Java标准库自带的,所以可以直接编译。而具体的JDBC驱动是由数据库厂商提供的,例如,MySQL的JDBC驱动由Oracle提供。因此,访问某个具体的数据库,我们只需要引入该厂商提供的JDBC驱动,就可以通过JDBC接口来访问,这样保证了Java程序编写的是一套数据库访问代码,却可以访问各种不同的数据库,因为他们都提供了JDBC驱动:

    如果从代码上来看,Java标准库自带的JDBC接口其实就是定义了一组接口,而某个具体的JDBC驱动其实就是实现了这些接口的类:

    实际上,一个MySQL的JDBC的驱动就是一个jar包,它本身也是纯Java编写的。我们自己编写的代码只需要引用Java标准库提供的java.sql包下面的相关接口,由此再间接地通过MySQL驱动的jar包通过网络访问MySQL服务器,所有复杂的网络通讯都被封装到JDBC驱动中,因此,Java程序本身只需要引入一个MySQL驱动的jar包就可以正常访问MySQL服务器:

    1.1、使用JDBC的好处

    JDBC API 是一个 Java API,它可以访问任何类型的表格数据,特别是可以访问存储在关系数据库里的数据。JDBC 可以用 Java 语言在各种平台上实现,比如 Windows 系统, Mac OS 系统,和各种版本的 UNIX 系统。

    并且:

    • 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发;

    • Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;

    • 可随时替换底层数据库,访问数据库的Java代码基本不变。

    2、下载导入 JDBC 驱动

    2.1、下载 JDBC 驱动

    可参考:https://www.cnblogs.com/NyanKoSenSei/p/11510438.html

    2.2、将 JDBC 驱动导入项目

    将 JDBC 驱动导入项目就跟将普通的 jar 包导入项目一样。下载完 JDBC 驱动后,将其解压,可以看到里面有两个 jar 包。

    在 java 项目中新建一个 lib 文件夹,将 JDBC 解压后里面的 xxx.bin.jar 包复制到项目的 lib 文件夹中,右键要使用的jar包,选择Build Path-->Add to Build Path,将其添加为项目依赖即可。

    可参考:https://jingyan.baidu.com/article/bad08e1e23982609c851219e.html

    3、使用JDBC操作数据库

    构建一个 JDBC 应用程序包括以下六个步骤-

    • 导入数据包:需要你导入含有需要进行数据库编程的 JDBC 类的包。大多数情况下,使用 import java.sql. 就足够了。

    • 注册 JDBC 驱动器:需要你初始化一个驱动器,以便于你打开一个与数据库的通信通道。

    • 打开连接:需要使用 DriverManager.getConnection() 方法创建一个 Connection 对象,它代表与数据库的物理连接。

    • 执行查询:需要使用类型声明的对象建立并提交一个 SQL 语句到数据库。

    • 提取结果数据:要求使用适当的 ResultSet.getXXX() 方法从结果集中检索数据。

    • 清理环境:依靠 JVM 的垃圾收集来关闭所有需要明确关闭的数据库资源。

    3.1、操作数据库代码示例

    在导入驱动 jar 包后,我们就可以使用 JDBC 驱动来操作数据库了。

    代码示例:

    package jdbcTest;
    
    import java.net.URL;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class JDBCTest {
        public static void main(String[] args) throws Exception {
            //1.注册驱动(mysql5之后的驱动jar包可以省略注册驱动的步骤)
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取数据库连接对象
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db_test", "root", "123456");
            //3.定义sql语句
            String sql = "update students set name = 'hahaha' where id = 1";
            //4.获取执行sql的对象
            Statement stmt = conn.createStatement();
            5.执行sql
            int count = stmt.executeUpdate(sql);
            
            System.out.println(count);   
            //6.释放资源
            stmt.close();
            conn.close();
        }
    }

    3.2、常用的 JDBC 组件介绍

    JDBC 的 API 提供了以下接口和类:

    DriverManager :这个类管理一系列数据库驱动程序。匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。

    Driver : 这个接口处理与数据库服务器的通信。你将很少直接与驱动程序互动。相反,你使用 DriverManager 中的对象,它管理此类型的对象。它也抽象与驱动程序对象工作相关的详细信息。

    Connection : 此接口具有接触数据库的所有方法。该连接对象表示通信上下文,即,所有与数据库的通信仅通过这个连接对象进行。

    Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。除了执行存储过程以外,一些派生的接口也接受参数。

    ResultSet : 在你使用语句对象执行 SQL 查询后,这些对象保存从数据获得的数据。它作为一个迭代器,让您可以通过它的数据来移动。

    SQLException : 这个类处理发生在数据库应用程序的任何错误。

    3.3、先注册 JDBC 驱动程序(mysql5之后的驱动程序可省略)

    在使用驱动程序之前,你必须在你的程序里面注册它。我们可以通过加载 Oracle 驱动程序的类文件到内存中来注册驱动程序,在程序里做一次注册即可。

    注册一个驱动程序中最常用的方法是使用 Java 的Class.forName() 方法来动态加载驱动程序的类文件到内存中,它会自动将其注册。这种方法更优越一些,因为它允许你对驱动程序的注册信息进行配置,便于移植。

    try {
       Class.forName("oracle.jdbc.driver.OracleDriver");
    }
    catch(ClassNotFoundException ex) {
       System.out.println("Error: unable to load driver class!");
    }

    (注意:在mysql5之后的驱动jar包可以省略注册驱动的步骤) 

    3.4、然后使用JDBC连接数据库(Connection)

    要通过 JDBC 操作数据库,我们需要先连接数据库。当你加载了驱动程序之后,你可以通过 DriverManager.getConnection() 方法建立一个连接。Connection代表一个JDBC连接,它相当于 Java 程序到数据库的连接(通常是TCP连接)。

    打开一个Connection时,需要准备URL、用户名和密码,才能成功连接到数据库。

    数据库连接的代码示例如下:

    // JDBC连接的URL, 不同数据库有不同的格式:
    String JDBC_URL = "jdbc:mysql://localhost:3306/test";  //URL
    String JDBC_USER = "root";           //用户名
    String JDBC_PASSWORD = "password";   //密码
    
    // 获取连接
    Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
    
    ...
    
    // 最后需关闭连接
    conn.close();

    核心代码是DriverManager提供的静态方法getConnection()DriverManager会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。

    JDBC连接是一种昂贵的资源,使用后要及时释放。可以使用try (resource)来自动释放JDBC连接:

    try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
           ...
    }

    在 JDBC 程序的末尾,它必须明确关闭所有的连接到数据库的连接,以结束每个数据库会话。但是,如果忘了,Java 垃圾收集器也会关闭连接,它会完全清除过期的对象。依托垃圾收集器,特别是在数据库编程,是非常差的编程习惯,我们应该养成用 close()方法关闭连接对象的习惯。

    3.4.1、数据库连接的URL格式

    URL是由数据库厂商指定的格式,例如,MySQL的URL是:

    jdbc:mysql://服务器IP:端口号/数据库名称?key1=value1&key2=value2

    假设数据库运行在本机localhost,端口使用标准的3306,数据库名称是learnjdbc。示例如下:

    jdbc:mysql://localhost:3306/learnjdbc?useSSL=false&characterEncoding=utf8
    
    # 如果连接的是本地服务器,并且服务器默认端口是3306,那么可以简写为以下形式,即省略服务器地址和端口号:
    jdbc:mysql:///数据库名称

    (后面的两个参数表示不使用SSL加密,使用UTF-8作为字符编码(注意MySQL的UTF-8是utf8),不写也行) 

    下表列出了常用的 JDBC 驱动程序名和数据库URL。

    上表中 URL 格式所有加粗的部分都是静态的,你需要将剩余部分按照你的数据库实际情况进行设置。

    3.4.2、如何创建连接对象

    我们可以通过 DriverManager.getConnection() 方法来建立一个数据库连接,加载 DriverManager.getConnection() 参数有以下三种:

    1. getConnection(String url)
    2. getConnection(String url, Properties prop)
    3. getConnection(String url, String user, String password)

    使用URL、用户名、密码:getConnection() 最常用的方式是需要你提供一个数据库 URL,用户名和密码:

    String URL = "jdbc:oracle:thin:@amrood:1521:EMP";
    String USER = "username";
    String PASS = "password"
    Connection conn = DriverManager.getConnection(URL, USER, PASS);

    只使用URL:在只使用url时,数据库的 URL ,包括用户名和密码,将表现为以下的格式:

    jdbc:oracle:driver:username/password@database

    示例:

    String URL = "jdbc:oracle:thin:username/password@amrood:1521:EMP";
    Connection conn = DriverManager.getConnection(URL);

    使用数据库 URL 和 Properties 对象:Properties 对象保存了一组关键数值。它通过调用 getConnection() 方法,将驱动程序属性传递给驱动程序。

    import java.util.*;
    
    String URL = "jdbc:oracle:thin:@amrood:1521:EMP";
    Properties info = new Properties( );
    info.put( "user", "username" );
    info.put( "password", "password" );
    
    Connection conn = DriverManager.getConnection(URL, info);

    3.5、最后使用JDBC操作数据库

    一旦我们获得了数据库的连接,我们就可以和数据库进行交互。JDBC 的 Statement,CallableStatement 和 PreparedStatement 接口定义的方法和属性,可以让你发送 SQL 命令或 PL/SQL 命令到数据库,并从你的数据库接收数据。在数据库中,它们还定义了帮助 Java 和 SQL 数据类型之间转换数据差异的方法。

    下表提供了每个接口的用途概要,根据实际目的决定使用哪个接口。

    查询数据库可以分为以下几步:

    第一步,通过Connection提供的createStatement()方法创建一个Statement对象,用于执行一个查询;

    第二步,执行Statement对象提供的executeQuery("SELECT * FROM students")并传入SQL语句,执行查询并获得返回的结果集,使用ResultSet来引用这个结果集;

    第三步,反复调用ResultSetnext()方法并读取每一行结果。

    完整查询代码如下:

    try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
        try (Statement stmt = conn.createStatement()) {
            try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) {
                while (rs.next()) {
                    long id = rs.getLong(1); // 注意:索引从1开始
                    long grade = rs.getLong(2);
                    String name = rs.getString(3);
                    int gender = rs.getInt(4);
                }
            }
        }
    }

    注意要点:

    StatmentResultSet都是需要关闭的资源,因此嵌套使用try (resource)确保及时关闭;

    rs.next()用于判断是否有下一行记录,如果有,将自动把当前行移动到下一行(一开始获得ResultSet时当前行不是第一行);

    ResultSet获取列时,索引从1开始而不是0

    必须根据SELECT的列的对应位置来调用getLong(1)getString(2)这些方法,否则对应位置的数据类型不对,将报错。

    3.5.1、使用 Statement 对象来操作数据库

    在你准备使用 Statement 对象执行 SQL 语句之前,你需要使用 Connection 对象的 createStatement() 方法先创建一个 Statement 对象。

    Connection conn = DriverManager.getConnection(URL, 用户名, 密码);
    Statement stmt = conn.createStatement( );

    当你创建了一个 Statement 对象之后,你可以用它的三个执行方法的任一方法来执行 SQL 语句。

    • boolean execute(String SQL) : 如果 ResultSet 对象可以被检索,则返回的布尔值为 true ,否则返回 false 。当你需要使用真正的动态 SQL 时,可以使用这个方法来执行 SQL DDL 语句。

    • int executeUpdate(String SQL) : 一般使用该方法执行DML语句(增删改),也可执行DDL语句(操作数据库和表结构),它返回的是执行 SQL 语句影响的行的数目。

    • ResultSet executeQuery(String SQL) : 一般使用该方法执行DQL语句(查询),它返回一个 ResultSet 对象。
    String sql = "update students set name = 'hahaha' where id = 1";
    Statement stmt = conn.createStatement();
    int count = stmt.executeUpdate(sql);

    在使用后我们应该关闭 Statement 对象。通过调用 close() 方法就可以关闭 Statement 对象。其实在我们关闭了 Connection 对象后,它也会自动关闭 Statement 对象。但我们应该始终明确关闭 Statement 对象,以确保真正的清除。

    Statement stmt = null;
    try {
       stmt = conn.createStatement( );
       . . .
    }
    catch (SQLException e) {
       . . .
    }
    finally {
       stmt.close();
    }

    3.5.2、使用PreparedStatement对象操作数据库

    PreparedStatement 接口扩展了 Statement 接口,它让你用一个常用的 Statement 对象增加几个高级功能。这个 statement 对象可以提供灵活多变的动态参数。

    创建 PreparedStatement 对象:

    String SQL = "Update Employees SET age = ? WHERE id = ?";
    PreparedStatement pstmt = conn.prepareStatement(SQL);

    JDBC 中所有的参数都被用 ? 符号表示,这是已知的参数标记。在执行 SQL 语句之前,你必须赋予每一个参数确切的数值。

    setXXX() 方法将值绑定到参数,其中 XXX 表示你希望绑定到输入参数的 Java 数据类型。如果你忘了赋予值,你将收到一个 SQLException。每个参数标记映射它的序号位置。第一标记表示位置 1 ,下一个位置为 2 等等。这种方法不同于 Java 数组索引,它是从 0 开始的。

    String sql = "SELECT * FROM user WHERE login=? AND pass=?";
    PreparedStatement ps = conn.prepareStatement(sql);
    ps.setObject(1, name);
    ps.setObject(2, pass);

    所有的 Statement对象 的方法都与数据库交互,(a) execute(),(b) executeQuery(),及 (c) executeUpdate() 也能被 PreparedStatement 对象引用。然而,这些方法被 SQL 语句修改后是可以输入参数的。

    PreparedStatement 对象在使用后也需要关闭,只需简单调用 close() 方法就可以完成这项工作。如果你关闭了 Connection 对象,那么它也会关闭 PreparedStatement 对象。然而,你应该始终明确关闭 PreparedStatement 对象,以确保真正的清除。

    4、JDBC事务

    数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列,有点类似于Java的synchronized同步。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:

    • Atomicity:原子性
    • Consistency:一致性
    • Isolation:隔离性
    • Durability:持久性

    数据库事务可以并发执行,而数据库系统从效率考虑,对事务定义了不同的隔离级别。SQL标准定义了4种隔离级别,分别对应可能出现的数据不一致的情况:

    要在JDBC中执行事务,本质上就是如何把多条SQL包裹在一个数据库事务中执行。

    JDBC的事务代码如下: 

    Connection conn = openConnection();
    try {
        // 关闭自动提交:
        conn.setAutoCommit(false);
        // 执行多条SQL语句:
        insert(); update(); delete();
        // 提交事务:
        conn.commit();
    } catch (SQLException e) {
        // 回滚事务:
        conn.rollback();
    } finally {
        conn.setAutoCommit(true);  //最后恢复至自动提交
        conn.close();
    }

    其中,开启事务的关键代码是conn.setAutoCommit(false),表示关闭自动提交。提交事务的代码在执行完指定的若干条SQL语句后,调用conn.commit()。要注意事务不是总能成功,如果事务提交失败,会抛出SQL异常(也可能在执行SQL语句的时候就抛出了),此时我们必须捕获并调用conn.rollback()回滚事务。最后,在finally中通过conn.setAutoCommit(true)Connection对象的状态恢复到初始值。

    实际上,默认情况下,我们获取到Connection连接后,总是处于“自动提交”模式,也就是每执行一条SQL都是作为事务自动执行的,这种事务也就是“隐式事务”。只要关闭了ConnectionautoCommit,那么就可以在一个事务中执行多条语句,事务以commit()方法结束。

    4.1、JDBC定义事务的隔离级别

    如果要设定事务的隔离级别,可以使用如下代码:

    // 设定隔离级别为READ COMMITTED:
    conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);

    如果没有调用上述方法,那么会使用数据库的默认隔离级别。MySQL的默认隔离级别是REPEATABLE READ

    5、SQL注入问题

    使用Statement拼字符串非常容易引发SQL注入的问题,这是因为SQL参数往往是从方法参数传入的。

    假设用户登录的验证方法如下:

    User login(String name, String pass) {
        ...
        stmt.executeQuery("SELECT * FROM user WHERE login='" + name + "' AND pass='" + pass + "'");
        ...
    }

    其中,参数namepass通常都是Web页面输入后由程序接收到的。

    如果用户的输入是程序期待的值,就可以拼出正确的SQL。例如:name = "bob",pass = "1234"

    SELECT * FROM user WHERE login='bob' AND pass='1234'

    但是,如果用户的输入是一个精心构造的字符串,就可以拼出意想不到的SQL,这个SQL也是正确的,但它查询的条件不是程序设计的意图。例如:name = "bob' OR pass=", pass = " OR pass='"

    SELECT * FROM user WHERE login='bob' OR pass=' AND pass=' OR pass=''

    这个SQL语句执行的时候,根本不用判断口令是否正确,这样一来,登录就形同虚设。

    要避免SQL注入攻击,一个办法是针对所有字符串参数进行转义,但是转义很麻烦,而且需要在任何使用SQL的地方增加转义代码。

    还有一个办法就是使用PreparedStatement。使用PreparedStatement可以完全避免SQL注入的问题,因为PreparedStatement始终使用?作为占位符,并且把数据连同SQL本身传给数据库,这样可以保证每次传给数据库的SQL语句是相同的,只是占位符的数据不同,还能高效利用数据库本身对查询的缓存。上述登录SQL如果用PreparedStatement可以改写如下:

    User login(String name, String pass) {
        ...
        String sql = "SELECT * FROM user WHERE login=? AND pass=?";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setObject(1, name);
        ps.setObject(2, pass);
        ...
    }

    所以,PreparedStatementStatement更安全,而且更快。

    注意:使用Java对数据库进行操作时,必须使用PreparedStatement,严禁任何通过参数拼字符串的代码!

    把上面使用Statement的代码改为使用PreparedStatement,如下:

    try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
        try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
            ps.setObject(1, "M"); // 注意:索引从1开始
            ps.setObject(2, 3);
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    long id = rs.getLong("id");
                    long grade = rs.getLong("grade");
                    String name = rs.getString("name");
                    String gender = rs.getString("gender");
                }
            }
        }
    }

    使用PreparedStatementStatement稍有不同,必须首先调用setObject()设置每个占位符?的值,最后获取的仍然是ResultSet对象。另外注意到从结果集读取列时,使用String类型的列名比索引要易读,而且不易出错。

    注意到JDBC查询的返回值总是ResultSet,即使我们写这样的聚合查询SELECT SUM(score) FROM ...,也需要按结果集读取:

    ResultSet rs = ...
    if (rs.next()) {
        double sum = rs.getDouble(1);
    }

    使用JDBC的时候,我们需要在SQL数据类型和Java数据类型之间进行转换。JDBC在java.sql.Types定义了一组常量来表示如何映射SQL数据类型,但是平时我们使用的类型通常也就以下几种:

    只有最新的JDBC驱动才支持LocalDateLocalTime。 

  • 相关阅读:
    HasMap
    SQL Server 2005新特性之使用with关键字解决递归父子关系
    Silverlight4调用本地音频设备并将音频文件上传到服务器
    sql中exists,not exists的用法
    ie中jQuery无法解析xml文件的解决方案
    NeatUpload的安装使用
    C# 中 MSCHART 饼状图显示百分比
    JSON
    silverlight 独立存储
    SharePoint2010 自定义搜索
  • 原文地址:https://www.cnblogs.com/wenxuehai/p/13388398.html
Copyright © 2020-2023  润新知