JDBC--基础JDBC


  数据库应用程序通过调用其API的方法来与数据库引擎进行交互。Java应用程序使用的API称为JDBC(用于Java数据库连接)。JDBC库由五个Java包组成,其中大多数实现了仅在大型商业应用程序中有用的高级特性。java.sql包中的JDBC核心功能可以分为两部分:基础JDBC,它包含基本使用所需的类和方法;高级JDBC,它包含可选特性,提供额外的便利性和灵活性。

基础JDBC

  JDBC的基本功能体现在五个接口中:驱动器(Driver)、连接(Connection)、语句(Statement)、结果集(ResultSet)和元数据结果集(ResultSetMetaData)。其中这些接口的只有极少数方法是必要的:

Driver
public Connection connect(String url, Properties prop) throws SQLException;

Connection
public Statement createStatement() throws SQLException;
public void close() throws SQLException;

Statement
public ResultSet executeQuery(String qry) throws SQLException;
public int executeUpdate(String cmd) throws SQLException;
public void close() throws SQLException;

ResultSet
public boolean next() throws SQLException;
public int getInt() throws SQLException;
public String getString() throws SQLException;
public void close() throws SQLException;
public ResultSetMetaData getMetaData() throws SQLException;

ResultSetMetaData
public int getColumnCount() throws SQLException;
public String getColumnName(int column) throws SQLException;
public int getColumnType(int column) throws SQLException;
public int getColumnDisplaySize(int column) throws SQLException;

  第一个示例程序是CreateTestDB,它说明了程序如何连接到Derby引擎并断开连接。

CreateTestDB

import org.apache.derby.jdbc.ClientDriver;
import org.apache.derby.jdbc.EmbeddedDriver;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;

public class CreateTestDB {

    public static void main(String[] args) {
        create();
    }

    public static void create(){
        //连接到本地Derby数据库,并创建驱动器
        /*String url = "jdbc:derby://localhost/testdb;create=true";,为了测试方便可以采用内嵌版本
        Driver d = new ClientDriver();//对应org.apache.derby.jdbc.ClientDriver*/
        //为了测试方便可以采用内嵌版本,并选择本地存储
        String url = "jdbc:derby:d:/dbtest/testdb;create=true";
        Driver d =new EmbeddedDriver();
        try {
            Connection conn = d.connect(url, null);
            System.out.println("Database Created");
            conn.close();
        }catch(SQLException e) {
            e.printStackTrace();
        }
    }
}

连接数据库引擎

  每个数据库引擎都将有自己的(也可能是专有的)机制,用于与客户端建立连接。另一方面,客户端希望尽可能地独立于服务器。也就是说,客户机不想知道如何连接到引擎的基本细节;它只是希望引擎为客户端提供一个类来调用。这样的类被称为驱动程序(驱动器,Driver)。
  JDBC驱动类实现了接口驱动程序(Driver)。Derby和SimpleDB每个都有两个驱动程序类:一个用于基于服务器的连接,另一个用于嵌入式连接。基于Derby引擎的服务器连接使用类客户端驱动程序,而嵌入式连接使用嵌入驱动程序;这两个类都在包org.apache.derby.jdbc中。到SimpleDB引擎的基于服务器的连接使用类网络驱动程序(在包simpledb.jdbc.network中),而嵌入式连接使用嵌入式驱动程序(在包simpledb.jdbc.embedded中)。客户端通过调用驱动程序对象的连接方法来连接到数据库引擎。例如:

String url = "jdbc:derby:d:/dbtest/testdb;create=true";
Driver d =new EmbeddedDriver();
Connection conn = d.connect(url, null);

  连接方法需要两个参数。该方法的第一个参数是一个标识驱动程序、服务器(用于基于服务器的连接)和数据库的URL。这个URL被称为连接字符串。以“jdbc:derby://localhost/testdb;create=true”为例:

  • 子字符串“jdbc:derby:”描述了客户机使用的协议。这里,协议说这个客户端是一个说JDBC的Derby客户端。

  • 子字符串“//localhost”描述了服务器所在的计算机。您可以替换任何域名或IP地址,而不是本地主机。

  • 子字符串“/testdb”描述了到服务器上的数据库的路径。对于Derby服务器,该路径从启动该服务器的用户的当前目录开始。路径的末尾(这里是“testdb”)是将存储此数据库的所有数据文件的目录。

  • 连接字符串的其余部分包含要发送到引擎的属性值。在这里,子字符串是“;create=true”,它告诉引擎创建一个新的数据库。通常,可以将多个属性值发送到Derby引擎。例如,如果引擎需要用户身份验证,则还将指定属性的用户名和密码的值。用户“爱因斯坦”的连接字符串可能是这样的:“jdbc:derby://localhost/testdb;create=true;user=einstein;password=emc2”

  连接方法的第二个参数是属性类型的对象。此对象提供了将属性值传递给引擎的另一种方法。在上述CreateTestDB例子中,该参数的值为空(null),因为所有属性都是在连接字符串中指定的。或者,您也可以将属性规范放入第二个参数中,如下所示:

String url = "jdbc:derby://localhost/testdb";
Properties prop = new Properties();
prop.put("create", "true");
prop.put("username", "einstein");
prop.put("password", "emc2");
Driver d = new ClientDriver();
Connection conn = d.connect(url, prop);

  每个数据库引擎都有自己的连接字符串语法。SimpleDB的基于服务器的连接字符串与Derby的不同之处在于,它只包含一个协议和机器名。(字符串包含数据库的名称没有意义,因为数据库是在SimpleDB服务器启动时指定的。而且连接字符串没有指定属性,因为SimpleDB服务器不支持任何属性。)例如,以下三行代码可以连接到SimpleDB服务器:

String url = "jdbc:simpledb://localhost";
Driver d = new NetworkDriver();
conn = d.connect(url, null);

  尽管驱动程序类和连接字符串语法基于供应商的,但JDBC程序的其余部分是完全与供应商无关的。例如CreateTestDB例子中的变量d和conn。它们对应的JDBC类型,即驱动程序(Driver)和连接(Connection)都是接口。可以从代码中看出,变量d被分配给了一个客户端驱动程序对象。但是,conn被分配给由方法连接返回的连接对象,并且没有办法知道它的实际类。这种情况适用于所有的JDBC程序。除了驱动程序类的名称及其连接字符串外,JDBC程序只知道并关心与供应商无关的JDBC接口。因此,一个基本的JDBC客户端将从两个包中导入:

  • 导入java.sql包,以获取与供应商无关的JDBC接口定义
  • 导入包含该驱动程序类的由供应商提供的软件包

断开数据库连接

  在客户端连接到数据库引擎期间,该引擎可以分配资源供客户端使用。例如,客户端可以从其服务器请求锁,以阻止其他客户端访问数据库的部分。甚至连连接到数据库引擎的能力也可以是一种资源。一个公司可能拥有一个商业数据库系统的站点许可证,它限制了同时连接的数量,这意味着持有一个连接可能会占用另一个客户端的连接。由于连接拥有宝贵的资源,因此一旦不再需要数据库,客户端就应该立即断开与引擎的连接。客户端程序通过调用其连接对象的关闭方法来断开与其引擎的连接。如CreateTestDB例子中的close。

SQL异常

  由于许多原因,客户端和数据库引擎之间的交互可能会产生异常。例如:

  • 客户端要求数据库引擎执行一个格式错误的SQL语句或一个SQL查询,该查询访问一个不存在的表或比较两个不兼容的值。
  • 由于客户端与并发客户端之间存在死锁,数据库引擎会中止客户端。
  • 在数据库引擎代码中有一个错误。
  • 客户端无法访问该引擎(用于基于服务器的连接)。可能主机名错误,或者主机无法访问。

  不同的数据库引擎都有它们自己的处理这些异常的内部方法。例如,SimpleDB在网络问题上抛出远程异常(RemoteException),在SQL语句问题上抛出错误同步异常(BadSyntaxException ),在死锁上抛出缓冲区阻塞异常或阻塞异常,在服务器问题上抛出通用运行时异常(RuntimeException)。
  为了使异常处理独立于供应商,JDBC提供了自己的异常类,称为SQLExcepent。当数据库引擎遇到一个内部异常时,它会将其包装为一个SQL异常,并将其发送到客户端程序。
  与SQL异常关联的消息字符串标识了导致该异常的内部异常。每个数据库引擎都可以免费提供它自己的消息。例如,Derby有近900条错误消息,而SimpleDB将所有可能的问题合并为6条消息:“网络问题”、“非法SQL语句”、“服务器错误”、“不支持操作”和两种形式的“事务中止”。
  大多数JDBC方法(以及CreateTestDB中的所有方法)都抛出一个SQL异常。SQL异常被检查,这意味着客户端必须通过捕获它们或抛出它们来显式地处理它们。CreateTestDB中的两个JDBC方法是在一个尝试块中执行的;如果其中一个导致异常,代码将打印堆栈跟踪并返回。
  CreateTestDB中的代码有一个问题,即当抛出异常时,它的连接没有被关闭。这是一个资源泄漏的例子——在客户端死亡后,引擎无法轻松地回收连接的资源。解决这个问题的一种方法是关闭捕获块内的连接。但是,需要从一个尝试块中调用关闭方法,这意味着CreateTestDB中的捕获块确实应该是这样的:

try (Connection conn = d.connect(url, null)) {//资源的创建及释放交由try...catch...管理
System.out.println("Database Created");
}
catch (SQLException e) {
e.printStackTrace();
}

执行SQL语句

  一个连接可以被看作是与数据库引擎的一个“会话”,在此期间,数据库引擎为客户机执行SQL语句。
  连接对象(Connection)具有方法创建语句,它返回一个语句对象(createStatement)。语句对象有两种执行SQL语句的方法:方法执行查询(executeQuery)和执行更新(executeUpdate)。它还具有关闭的方法(close),用于释放由对象持有的资源。

ChangeMajor

  如下程序(ChangeMajor)调用执行程序更新来修改Amy的学生记录的MajorId值。该方法的参数是一个表示SQL更新语句的字符串;该方法返回已更新的记录数。

import org.apache.derby.jdbc.EmbeddedDriver;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Statement;

public class ChangeMajor {

    public static void main(String[] args) {
        updateStudent();
    }

    public static void updateStudent(){
        String url = "jdbc:derby://localhost/studentdb";
        String cmd = "update STUDENT set MajorId=30 where SName='amy'";
        Driver d = new EmbeddedDriver();
        try (Connection conn = d.connect(url, null);
             Statement stmt = conn.createStatement()) {
            int howmany = stmt.executeUpdate(cmd);
            System.out.println(howmany + " records changed.");
        }
        catch(SQLException e) {
            e.printStackTrace();
        }
    }
}

  语句对象(Statement)和连接对象(Connection)一样需要关闭。最简单的解决方案是交由try…catch…块自动管理创建与释放资源。

CreateStudentDB

  该代码执行SQL语句来创建五个表,并在其中插入记录。

import org.apache.derby.jdbc.EmbeddedDriver;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Statement;

public class CreateStudentDB {
    public static void main(String[] args) {
        creat();
    }

    public static void creat() {
        String url = "jdbc:derby:d:/test/studentdb;create=true";
        Driver d = new EmbeddedDriver();
        try (Connection conn = d.connect(url, null);
             Statement stmt = conn.createStatement()) {

            String s = "create table STUDENT(SId int, SName varchar(10), MajorId int, GradYear int)";
            stmt.executeUpdate(s);
            System.out.println("Table STUDENT created.");

            s = "insert into STUDENT(SId, SName, MajorId, GradYear) values ";
            String[] studvals = {"(1, 'joe', 10, 2021)",
                    "(2, 'amy', 20, 2020)",
                    "(3, 'max', 10, 2022)",
                    "(4, 'sue', 20, 2022)",
                    "(5, 'bob', 30, 2020)",
                    "(6, 'kim', 20, 2020)",
                    "(7, 'art', 30, 2021)",
                    "(8, 'pat', 20, 2019)",
                    "(9, 'lee', 10, 2021)"};
            for (int i = 0; i < studvals.length; i++)
                stmt.executeUpdate(s + studvals[i]);
            System.out.println("STUDENT records inserted.");

            s = "create table DEPT(DId int, DName varchar(8))";
            stmt.executeUpdate(s);
            System.out.println("Table DEPT created.");

            s = "insert into DEPT(DId, DName) values ";
            String[] deptvals = {"(10, 'compsci')",
                    "(20, 'math')",
                    "(30, 'drama')"};
            for (int i = 0; i < deptvals.length; i++)
                stmt.executeUpdate(s + deptvals[i]);
            System.out.println("DEPT records inserted.");

            s = "create table COURSE(CId int, Title varchar(20), DeptId int)";
            stmt.executeUpdate(s);
            System.out.println("Table COURSE created.");

            s = "insert into COURSE(CId, Title, DeptId) values ";
            String[] coursevals = {"(12, 'db systems', 10)",
                    "(22, 'compilers', 10)",
                    "(32, 'calculus', 20)",
                    "(42, 'algebra', 20)",
                    "(52, 'acting', 30)",
                    "(62, 'elocution', 30)"};
            for (int i = 0; i < coursevals.length; i++)
                stmt.executeUpdate(s + coursevals[i]);
            System.out.println("COURSE records inserted.");

            s = "create table SECTION(SectId int, CourseId int, Prof varchar(8), YearOffered int)";
            stmt.executeUpdate(s);
            System.out.println("Table SECTION created.");

            s = "insert into SECTION(SectId, CourseId, Prof, YearOffered) values ";
            String[] sectvals = {"(13, 12, 'turing', 2018)",
                    "(23, 12, 'turing', 2019)",
                    "(33, 32, 'newton', 2019)",
                    "(43, 32, 'einstein', 2017)",
                    "(53, 62, 'brando', 2018)"};
            for (int i = 0; i < sectvals.length; i++)
                stmt.executeUpdate(s + sectvals[i]);
            System.out.println("SECTION records inserted.");

            s = "create table ENROLL(EId int, StudentId int, SectionId int, Grade varchar(2))";
            stmt.executeUpdate(s);
            System.out.println("Table ENROLL created.");

            s = "insert into ENROLL(EId, StudentId, SectionId, Grade) values ";
            String[] enrollvals = {"(14, 1, 13, 'A')",
                    "(24, 1, 43, 'C' )",
                    "(34, 2, 43, 'B+')",
                    "(44, 4, 33, 'B' )",
                    "(54, 4, 53, 'A' )",
                    "(64, 6, 53, 'A' )"};
            for (int i = 0; i < enrollvals.length; i++)
                stmt.executeUpdate(s + enrollvals[i]);
            System.out.println("ENROLL records inserted.");

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

结果集(ResultSet)

  调用语句的executeQuery方法执行SQL查询。此方法的参数是一个表示SQL查询的字符串,它返回一个ResultSet类型的对象。结果集对象表示查询的输出记录。客户端可以通过搜索结果集来检查这些记录。

StudentMajor

  对于说明结果集使用的示例程序(StudentMajor)。它对executeQuery方法的调用返回一个包含每个学生的名称和专业的结果集。后续的当循环将打印结果集中的每条记录。

import org.apache.derby.jdbc.EmbeddedDriver;
import java.sql.*;

public class StudentMajor {
    public static void main(String[] args) {
        query();
    }

    public static void query() {
        String url = "jdbc:derby:d:/test/studentdb";
        String qry = "select SName, DName "
                + "from DEPT, STUDENT "
                + "where MajorId = DId";

        Driver d = new EmbeddedDriver();
        try (Connection conn = d.connect(url, null);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(qry)) {
            System.out.println("Name\tMajor");
            while (rs.next()) {
                String sname = rs.getString("SName");
                String dname = rs.getString("DName");
                System.out.println(sname + "\t" + dname);
            }
        }
        catch(SQLException e) {
            e.printStackTrace();
        }
    }
}

   一旦客户端获得了一个结果集,它就会通过下次(next)调用该方法来遍历输出记录。此方法移动到下一个记录,如果移动成功则返回true,如果没有更多记录则返回false。通常,客户端使用一个循环移动来获取所有的记录,依次处理每条记录。StudentMajor就是一个这样的循环的例子。在第n次通过这个循环期间,变量rs将被放置在结果集的第n次记录上。当没有更多的可要处理的记录时,该循环将结束。
  一个新的结果集对象总是定位在第一个记录之前,因此需要先调用它,然后才能查看第一个记录。由于这个要求,循环记录的典型方法是这样的:

String qry = "select ...";
ResultSet rs = stmt.executeQuery(qry);
while (rs.next()) {
... // process the record(处理该条数据记录)
}

  在处理记录时,客户端会使用getInt和getString方法来检索其字段的值。每个方法都使用一个字段名称作为参数,并返回该字段的值。在StudentMajor中,代码检索并打印每条记录的字段SName和DName的值。
  结果集占用了数据库引擎上的宝贵资源。close方法将释放这些资源,并使它们可用于其他客户端。因此,客户应该努力成为一个“好公民”,并尽快确定结果。一种选择是显式地调用close,通常是在上述while-循环的末尾。StudentMajor中所使用的另一个选项是使用Java自动关闭机制(即try…catch…管理)。

使用查询元数据

  结果集信息由字段的名称、类型和显示大小构成。此信息可通过结果集元数据接口(ResultSetMetaData)提供。
  当客户端执行一个查询时,它通常知道输出表的模式。例如,对于StudentMajor,客户端是知道其结果集包含两个字符串字段SName和DName。
  但是,假设一个客户端程序允许用户提交查询作为输入。程序可以在查询的结果集上调用方法getMetaData,该方法返回结果集元数据类型的对象(ResultSetMetaData)。然后,它可以调用此对象的方法来确定输出表的模式。例如,如下printSchema方法中的代码使用结果集元数据类型来打印参数结果集的信息。

public static void printSchema(ResultSet rs) throws SQLException {
        ResultSetMetaData md = rs.getMetaData();
        for(int i=1; i<=md.getColumnCount(); i++) {//索引从1开始
            String name = md.getColumnName(i);
            int size = md.getColumnDisplaySize(i);
            int typecode = md.getColumnType(i);
            String type;
            if (typecode == Types.INTEGER)
                type = "int";
            else if (typecode == Types.VARCHAR)
                type = "string-varchar/char";
            else
                type = "other";
            System.out.println(name + "\t" + type+"(typecode:"+typecode+')' + "\t" + size);
        }
    }

  此代码说明了结果集元数据(ResultSetMetaData)对象的典型用法。它首先调用获取列数方法(getColumnCount)以返回结果集中字段的数量;然后调用获取列名(getColumnName)、列类型(getColumnType)和列显示大小(getColumnDisplaySize)方法,以确定每个字段的名称、类型和大小。注意,列数从1开始,而不是0。
  方法getColumnType返回一个编码字段类型的整数。这些代码被定义为JDBC类类型中的常量。这个类包含了30种不同类型的代码,这应该可以让您了解SQL语言有多广泛。这些类型的实际值并不重要,因为JDBC程序应该总是按名称引用变量,而不是通过值。

SimpleIJ

  此程序为SimpleDB中的一个类。

public class SimpleIJ {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("Connect> ");
        String s = sc.nextLine();
        Driver d = (s.contains("//")) ? new NetworkDriver() : new EmbeddedDriver();

        try (Connection conn = d.connect(s, null);
             Statement stmt = conn.createStatement()) {
            System.out.print("\nSQL> ");
            while (sc.hasNextLine()) {
                // process one line of input
                String cmd = sc.nextLine().trim();
                if (cmd.startsWith("exit"))
                    break;
                else if (cmd.startsWith("select"))
                    doQuery(stmt, cmd);
                else
                    doUpdate(stmt, cmd);
                System.out.print("\nSQL> ");
            }
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
        sc.close();
    }

    private static void doQuery(Statement stmt, String cmd) {
        try (ResultSet rs = stmt.executeQuery(cmd)) {
            ResultSetMetaData md = rs.getMetaData();
            int numcols = md.getColumnCount();
            int totalwidth = 0;

            // print header
            for(int i=1; i<=numcols; i++) {
                String fldname = md.getColumnName(i);
                int width = md.getColumnDisplaySize(i);
                totalwidth += width;
                String fmt = "%" + width + "s";
                System.out.format(fmt, fldname);
            }
            System.out.println();
            for(int i=0; i<totalwidth; i++)
                System.out.print("-");
            System.out.println();

            // print records
            while(rs.next()) {
                for (int i=1; i<=numcols; i++) {
                    String fldname = md.getColumnName(i);
                    int fldtype = md.getColumnType(i);
                    String fmt = "%" + md.getColumnDisplaySize(i);
                    if (fldtype == Types.INTEGER) {
                        int ival = rs.getInt(fldname);
                        System.out.format(fmt + "d", ival);
                    }
                    else {
                        String sval = rs.getString(fldname);
                        System.out.format(fmt + "s", sval);
                    }
                }
                System.out.println();
            }
        }
        catch (SQLException e) {
            System.out.println("SQL Exception: " + e.getMessage());
        }
    }

    private static void doUpdate(Statement stmt, String cmd) {
        try {
            int howmany = stmt.executeUpdate(cmd);
            System.out.println(howmany + " records processed");
        }
        catch (SQLException e) {
            System.out.println("SQL Exception: " + e.getMessage());
        }
    }
}

  主方法首先从用户中读取一个连接字符串,并使用它来确定要使用的正确驱动程序。代码将在连接字符串中查找字符“//”。如果出现这些字符,则字符串必须指定基于服务器的连接,否则必须指定嵌入式连接。然后,该方法通过将连接字符串传递到适当的驱动程序的连接方法来建立连接。
  主方法在其whlie循环的每次迭代中处理一行文本。如果文本是SQL语句,则会合适地调用doQuery或doUpdate方法。用户可以通过输入“exit”来退出循环,此时程序将退出。
  doQuery方法执行查询,获得输出表的结果集和元数据。调用获取列显示大小(getColumnDisplaySize)方法返回每个字段的空间要求;代码使用这些数字来构造一个格式字符串,允许字段值正确排列。这段代码的复杂性说明了“细节中就是魔鬼”的格言。也就是说,由于结果集和结果集元数据(ResultSetMetaData)很容易编码,而排列数据这一简单的任务需要大部分的编码工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zszyejing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值