使用JDBC连接数据库 - 《Java JDK 6学习笔记》

JDBC(Java DataBase Connectivity)是使用Java存取数据库系统的解决方案,它将不同数据库间各自差异API与标准SQL(Structured Query Language)语句分开看待,实现数据库无关的Java操作接口。开发人员使用JDBC统一的API接口,并专注于标准SQL语句,就可以避免直接处理底层数据库驱动程序与相关操作接口的差异性。

实际的数据库存取是个非常复杂的主题,可以使用专书加以说明,不过在本章中,会告诉您一些JDBC基本API的使用与概念,让您对Java如何存取数据库有所认识。

20.1 使用JDBC连接数据库

在正式使用JDBC进行数据库操作之前,先来认识JDBC的基本架构,了解数据库驱动程序与数据库之间的关系。在这个小节也将看到,如何设计一个简单的工具类,让您在进行数据库连接(Connection)时更为方便。

20.1.1 简介JDBC

如果要连接数据库并进行操作,基本上必须了解数据库所提供的API操作接口,然而各个厂商所提供的API操作界面并不一致,如果今天要使用A厂商的数据库,就必须设计一个专用的程序来操作A厂商数据库所提供的API,将来如果要使用B厂商的数据库,即使上层应用程序本身的目的相同,也是要编写专用于B厂商数据库之存取程序,十分的不方便。

使用JDBC,可由厂商操作实现操作数据库接口的驱动程序,而Java程序设计人员调用JDBC的API并操作SQL,实际对数据库的操作由JDBC驱动程序负责。如果要更换数据库,基本上只要更换驱动程序,Java程序中只要加载新的驱动程序来源,即可完成数据库系统的变更,Java 程序的部分则无需改变。

图20-1是JDBC API、数据库驱动程序与数据库之间的关系:

 

图20-1  应用程序、JDBC与驱动程序之间的关系

简单地说,JDBC希望达到的目的,是让Java程序设计人员在编写数据库操作程序的时候,可以有个统一的操作接口,无需依赖于特定的数据库API,希望达到“写一个Java程序,适用所有的数据库”的目的。

JDBC数据库驱动程序按实现方式可以分为4个类型:

   Type 1:JDBC-ODBC Bridge

用户的计算机上必须事先安装好ODBC驱动程序,Type 1驱动程序利用桥接(Bridge)方式,将JDBC的调用方式转换为ODBC驱动程序的调用方式,如图20-2所示,Microsoft Access数据库存取就是使用这种类型。

 

图20-2  Type 1: JDBC-ODBC Bridge

   Type 2:Native-API Bridge

Type 2驱动程序利用桥接方式,驱动程序上层封装Java程序以与Java应用程序作沟通,将JDBC调用转为本地(Native)程序代码的调用,下层为本地语言(就像C、C++)来与数据库进行沟通,下层的函数库是针对特定数据库设计的,不像Type 1可以对ODBC架构的数据库进行存取,如图20-3所示。

 

图20-3  Type 2: Native-API Bridge

   Type 3:JDBC-middleware

通过中间件(middleware)来存取数据库,用户不必安装特定的驱动程序,而是调用中间件,由中间件来完成所有的数据库存取动作,然后将结果返回给应用程序,如图20-4所示。

 

图20-4  Type 3: JDBC-moddleware

   Type 4:Pure Java Driver

使用纯Java程序来编写驱动程序与数据库进行沟通,而不通过桥接或中间件来存取数据库,如图20-5所示。

 

图20-5  Type 4: Pure Java Driver

在接下来的内容中,将使用MySQL数据库系统进行操作。MySQL的JDBC驱动程序属于Type 4。您可以在以下的网址获得MySQL的JDBC驱动程序,本章中将使用MySQL Connector/J 5.0。

http://www.mysql.com/products/connector/j/index.html

                                 

 

关于数据库系统的使用与操作是个很大的主题,本书中并不针对这方面加以介绍,请寻找数据库系统相关书籍自行学习,不过为了让您能顺利练习本章的范例,附录C中包括了一个MySQL数据库系统的简介,足够您了解这一章中将用到的一些数据库操作命令。

20.1.2 连接数据库

为了要连接数据库系统,您必须要有JDBC驱动程序,由于接下来将使用MySQL数据库进行操作,所以请将下载的tar.gz文件使用解压缩软件解开,并将其中的mysql-connector-java-*.jar加入至Classpath的设置之中,假设是放在c:\workspace\library\mysql-connector-java-5.0.3-bin.jar,则Classpath中必须有c:\workspace\library\mysql-connector-java-5.0.3-bin.jar这个路径设置。

在Java SE中与数据库操作相关的JDBC类都位于java.sql包中,要连接数据库,基本上必须有以下几个动作。

   加载JDBC驱动程序

首先必须通过java.lang.Class类的forName()动态加载驱动程序类,并向DriverManager注册JDBC驱动程序(驱动程序会自动通过DriverManager.registerDriver()方法注册)。MySQL的驱动程序类是com.mysql.jdbc.Driver,一个加载JDBC驱动程序的程序片段如下所示:

try {
    Class.forName("com.mysql.jdbc.Driver");
}
catch(ClassNotFoundException e) {
    System.out.println("找不到驱动程序类");
}

   提供JDBC URL

JDBC URL定义了连接数据库时的协议、子协议、数据源识别。

协定:子协定:数据源识别

“协议”在JDBC中总是jdbc开始;“子协议”是桥接的驱动程序或是数据库管理系统名称,使用MySQL的话是 "mysql";“数据源识别”标出找出数据库来源的地址与连接端口。举个例子来说,MySQL的JDBC URL编写方式如下:

jdbc:mysql://主机名称:连接端口/数据库名称?参数=值&参数=值

主机名称可以是本机localhost或是其他连接主机,连接端口为3306,假如要连接demo数据库,并指明用户名称与密码,可以如下指定:

jdbc:mysql://localhost:3306/demo?user=root&password=123

如果要使用中文存取的话,还必须给定参数useUnicode及characterEncoding,表明是否使用Unicode,并指定字符编码方式,例如:

jdbc:mysql://localhost:3306/demo?user=root&password=123&useUnicode=true&characterEncoding=Big5

   获得Connection

要连接数据库,可以向java.sql.DriverManager要求并获得java.sql.Connection对象。Connection是数据库连接的具体代表对象,一个Connection对象就代表一个数据库连接,您可以使用DriverManager的getConneciton()方法,指定JDBC URL作为自变量并获得Connection对象:

try {
    String url =  "jdbc:mysql://localhost:3306/demo?" +
                     "user=root&password=123";
    Connection conn = DriverManager.getConnection(url);
    ....
}
catch(SQLException e) {
    ....
}

java.sql.SQLException是在处理JDBC时经常遇到的异常对象,SQLException是受检异常 (Checked Exception),您必须使用try...catch或throws明确处理,它表示JDBC操作过程中若发生错误时的具体对象代表。

获得Connection对象之后,可以使用isClosed()方法测试与数据库的连接是否关闭,在操作完数据库之后,如果确定不再需要连接,则必须使用close()来关闭与数据库的连接,以释放连接时相关的必要资源。

getConnection()方法可以在参数上指定用户名称与密码,例如:

String url = "jdbc:mysql://localhost:3306/demo";
String user = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url,
                                         user, password);

20.1.3 简单的Connection工具类

在20.1.2节获得Connection的程序片段中,您可以看到其中直接用字符串在程序中写下JDBC URL、用户名称与密码等信息,实际的程序并不会将这些敏感信息写在程序代码之中,而且这么做的话,如果要更改用户名称或密码时,还要修改程序、重新编译,在程序维护上并不方便。

您可以将JDBC URL、用户名称与密码等设置信息编写在一个属性文件中,由程序读取这个属性文件中的信息,如果需要变更信息,则只要修改属性文件,无须修改程序、重新编译。在Java SE中,属性文件的读取可以交给java.util.Properties类。

举个实际的例子,假设您使用了以下命令在MySQL中建立了demo数据库:

CREATE DATABASE demo;

由于获得Connection的方式,按所使用的环境及程序需求而有所不同,因而您可以先设计一个DBSource接口,规范获得Connection的方法,如范例20.1所示。

Ü 范例20.1  DBSource.java

package onlyfun.caterpillar;

import java.sql.Connection;

import java.sql.SQLException;

public interface DBSource {

    public Connection getConnection() throws SQLException;

    public void closeConnection(Connection conn) throws SQLException;

}

接着可以实现DBSource接口,您的目的是从属性文件中读取设置信息、加载JDBC驱动程序,可以通过getConnection()方法获得Connection对象,并通过closeConnection()方法关闭Connection对象,在这里以一个简单的SimpleDBSource类作为示范,如范例20.2所示。

Ü 范例20.2  SimpleDBSource.java

package onlyfun.caterpillar;

import java.io.FileInputStream;

import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.Properties;

public class SimpleDBSource implements DBSource {

    private Properties props;

    private String url;

    private String user;

    private String passwd;

    public SimpleDBSource() throws IOException,

                                         ClassNotFoundException {

        this("jdbc.properties");

    }

     

    public SimpleDBSource(String configFile) throws IOException,

                                                    ClassNotFoundException {

        props = new Properties();

        props.load(new FileInputStream(configFile));

              

        url = props.getProperty("onlyfun.caterpillar.url");

        user = props.getProperty("onlyfun.caterpillar.user");

        passwd = props.getProperty("onlyfun.caterpillar.password");

              

        Class.forName(

                    props.getProperty("onlyfun.caterpillar.driver"));

    }

    public Connection getConnection() throws SQLException {

        return DriverManager.getConnection(url, user, passwd);

    }

    public void closeConnection(Connection conn) throws SQLException {

        conn.close();

    }

}

默认的构造函数设置中,是读取jdbc.properties文件中的设置,如果打算自行指定属性文件名称,则可以使用另一个有参数的构造函数。Properties的getProperty()方法会读取属性文件中的“键(Key)”对应的“值(Value)”,假设您的属性文件设置如下:

Ü 范例20.3  jdbc.properties

onlyfun.caterpillar.driver=com.mysql.jdbc.Driver

onlyfun.caterpillar.url=jdbc:mysql://localhost:3306/demo

onlyfun.caterpillar.user=root

onlyfun.caterpillar.password=123456

DBSource的getConnection()方法简单地从DriverManager的getConnection()方法获得Connection对象,而closeConnection()方法则是将给定的Connection关闭。就简单的连接程序来说,这样已经足够,不过待会还会介绍连接池(Connection pool)的概念,到时将会修改一下DBSource的getConnection()和closeConnection()方法,以达到重复使用Connection、节省资源的目的。

最后,范例20.4使用一个简单的程序来测试SimpleDBSource是否可以正确地获得与数据库的连接,以及是否正确地关闭连接。

Ü 范例20.4  ConnectionDemo.java

package onlyfun.caterpillar;

import java.io.IOException;

import java.sql.Connection;

import java.sql.SQLException;

public class ConnectionDemo {

    public static void main(String[] args) {

        try {

            DBSource dbsource = new SimpleDBSource();

            Connection conn = dbsource.getConnection();

           

            if(!conn.isClosed()) {

                System.out.println("数据库连接已开启…");

            }

           

            dbsource.closeConnection(conn);

           

            if(conn.isClosed()) {

                System.out.println("数据库连接已关闭…");

            }

           

        } catch (IOException e) {

            e.printStackTrace();

        } catch (ClassNotFoundException e) {

            e.printStackTrace();

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

}

如果您的demo数据库已建立,并正确设置jdbc.properties中的相关信息,则应该能看到以下的执行结果:

数据库连接已开启…

数据库连接已关闭…

20.1.4 简单的连接池(Connection pool)

在数据库应用程序中,数据库连接的获得是一个耗费时间与资源的操作,包括了建立Socket connection、交换数据(用户密码验证、相关参数)、会话(Session)、日志(Logging)、分配进程(Process)等资源。

如果数据库的操作是很频繁的动作,则要考虑到重复使用连接的需求,以节省在获得连接时的时间与资源,通常会实现一个连接池(Connection pool),有需要连接时可以从池中获得,不需要连接时就将连接放回池中,而不是直接关闭连接。

这里将实现一个简单的连接池,示范连接池中,重复使用连接的基本概念,范例20.5使用java.util.ArrayList来实现连接池,可以将先前使用过的连接放到ArrayList对象中,下一次需要连接时则直接从ArrayList中获得。

Ü 范例20.5  BasicDBSource.java

package onlyfun.caterpillar;

import java.io.FileInputStream;

import java.io.IOException;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.List;

import java.util.Properties;

public class BasicDBSource implements DBSource {

    private Properties props;

    private String url;

    private String user;

    private String passwd;

    private int max; // 连接池中最大Connection数目

    private List<Connection> connections;

    public BasicDBSource() throws IOException, ClassNotFoundException {

        this("jdbc.properties");

    }

   

    public BasicDBSource(String configFile) throws IOException,

                                                     ClassNotFoundException {

        props = new Properties();

        props.load(new FileInputStream(configFile));

       

        url = props.getProperty("onlyfun.caterpillar.url");

        user = props.getProperty("onlyfun.caterpillar.user");

        passwd = props.getProperty("onlyfun.caterpillar.password");

        max = Integer.parseInt(

                   props.getProperty("onlyfun.caterpillar.poolmax"));

        Class.forName(

                   props.getProperty("onlyfun.caterpillar.driver"));

       

        connections = new ArrayList<Connection>();

    }

    public synchronized Connection getConnection()

                                                    throws SQLException {

        if(connections.size() == 0) {

            return DriverManager.getConnection(url, user, passwd);

        }

        else {

            int lastIndex = connections.size() - 1;

            return connections.remove(lastIndex);

        }

    }

   

    public synchronized void closeConnection(Connection conn)

                                                        throws SQLException {

        if(connections.size() == max) {

            conn.close();

        }

        else {

            connections.add(conn);

        }

    }

}

BasicDBSource也实现DBSource接口,考虑这个类可能在多线程的环境中使用,因此在getConnection()与closeConnection()方法上使用syhchronized加以修饰。在获得连接时,如果目前池中没有Connection对象,则新建立一个连接,如果有存在的Connection对象,则从池中移出。

BasicDBSource可以设置连接池中最大Connection保存数量,如果超过这个数量,则传入closeConnection()方法的Connection对象直接关闭,否则就放入连接池中,以在下一次需要数据库连接时直接使用。范例20.6是个测试BasicDBSource的简单程序。

Ü 范例20.6  ConnectionPoolDemo.java

package onlyfun.caterpillar;

import java.io.IOException;

import java.sql.Connection;

import java.sql.SQLException;

public class ConnectionPoolDemo {

    public static void main(String[] args) {

        try {

            DBSource dbsource = new BasicDBSource("jdbc2.properties");

            Connection conn1 = dbsource.getConnection();

            dbsource.closeConnection(conn1);

            Connection conn2 = dbsource.getConnection();

            System.out.println(conn1 == conn2);

           

        } catch (IOException e) {

            e.printStackTrace();

        } catch (ClassNotFoundException e) {

            e.printStackTrace();

        } catch (SQLException e) {

            e.printStackTrace();

        }

       

    }

}

这里所使用的设置文件是jdbc2.properties,当中多了连接池最大数量之设置,如下所示:

Ü 范例20.7  jdbc2.properties

onlyfun.caterpillar.driver=com.mysql.jdbc.Driver

onlyfun.caterpillar.url=jdbc:mysql://localhost:3306/demo

onlyfun.caterpillar.user=root

onlyfun.caterpillar.password=123456

onlyfun.caterpillar.poolmax=10

程序中获得Connection之后,将之使用closeConnection()方法关闭,但实际上closeConnection()方法并不是真正使用Connection的close()方法,而是放回池中,第二次获得Connection时,所获得的是先前放入池中的同一对象,因此执行的结果会显示true。

在更复杂的情况下,您还需要考虑到初始的Connection数量、Connection最大idle的数量、如果超过多久时间,要回收多少数量的Connection等问题。实际上也不需要自行设计连接池的程序,现在网络上有不少优秀的开放原始码连接池程序,例如Proxool(http://proxool.sourceforge.net/index.html)或Apache Jakarta的Common DBCP(http://jakarta.apache.org/commons/dbcp/),您可以自行参考官方网站上的相关文件,了解它们是如何设置与使用的。

posted on 2012-11-13 09:27  一笑之奈何  阅读(591)  评论(0编辑  收藏  举报