zoukankan      html  css  js  c++  java
  • 使用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/),您可以自行参考官方网站上的相关文件,了解它们是如何设置与使用的。

  • 相关阅读:
    C#及时释放代码
    软件本质是什么?
    WCF学习
    android 更新ui
    ijkplayer视频播放
    androidstudio集成ijkplayer教程
    IJKPlayer问题集锦之不定时更新
    github上十二款最著名的Android播放器开源项目
    让ubuntu支持GBK编码AAAAA
    adb命令--之查看进程及Kill进程
  • 原文地址:https://www.cnblogs.com/warmingsun/p/2767462.html
Copyright © 2011-2022 走看看