jdbc訪問數據庫詳解

一.jdbc簡介
jdbc,java database Connectivity即java數據庫連接,是一組定義瞭如何連接和操作數據庫的java api。下面結合一個簡單示例說明下具體步驟:

public class Main {
    private static Logger LOG = LoggerFactory.getLogger(Main.class);

    public static String DRIVER    = "com.mysql.jdbc.Driver";
    public static String USER_NAME = "root";
    public static String PASSWORD  = "root";
    public static String TABLE     = "account";
    public static String URL       = "jdbc:mysql://127.0.0.1/bank";

    public static void main(String[] args) {

        //導入驅動
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            LOG.error("驅動加載失敗", e);
        }
        //獲取連接
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
        } catch (SQLException e) {
            LOG.error("建立連接失敗", e);
        }
        try {
            //獲取可用於執行sql的statement,statement中也包含了連接到數據庫的一些信息
            Statement statement = connection.createStatement();
            String sql = "select account_id,avail_balance from " + TABLE + " limit 2";
            //通過stament執行sql,並獲取執行結果,返回的結果resultSet可以看成一個二維表格形式,
            //next方法表明是否還有下一行,可以通過座標或列名獲取下一列;只能遍歷下一行,不能返回或跳到指定行
            ResultSet resultSet = statement.executeQuery(sql);
            while (resultSet.next()) {
                long id = resultSet.getLong(1);
                long balance = resultSet.getLong(2);
                System.out.println(id + " " + balance);
            }
        } catch (Exception e) {
            LOG.error("sql執行異常");
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    LOG.error("異常");
                }
            }
        }
    }
}

可以看到一共分爲5步:加載驅動–>獲取連接–>獲取Statement對象–>執行sql–>解析結果。上面給的sql是執行的查詢操作流程,但對於刪除的新增等操作也是類似的。
上面涉及到的比較重要的幾個類是:Driver、DriverManager、Connection、Statement、ResultSet,下面就依次介紹下這幾個類以及它們之間的聯繫。
二.jdbc中主要的類及其層次關係
下面這張圖比較清楚的說明Driver、DriverManger、Connection、Statement、ResultSet之間的層次關係以及它們大致的功能。
這裏寫圖片描述
下面具體來介紹每個類的作用以及一些值得注意的地方。
1)Driver
使用JDBC操作數據庫的第一步就是將數據庫對應的驅動(Driver)載入內存。從上面的代碼可以看到,所謂的導入驅動本質上只是通過Class.forName()方法將一個具體的類
加載到了內存之中,比如上面的就是:com.mysql.jdbc.Driver這個類,而其實所謂的驅動就是實現了Java.sql.Driver接口的類。下面看下Driver接口中的幾個主要方法:
這裏寫圖片描述
主要使用的是兩個方法:acceptsURL()、connect()。
acceptsURL()負責檢查傳入的url,判斷驅動能否打開該url,需要指出的是這裏的檢查並不會對url進行實際的連接操作而只是會對格式進行檢查;
每個數據庫連接的url都有自己定義的格式,如果檢查符合格式則返回true,否則返回false。
例如mysql的url格式爲: jdbc:mysql://host:port/database_name
connect()方法負責根據傳入的url和用戶名、密碼等信息建立與數據庫的連接,返回可以對數據庫進行操作的Connection對象。
因爲每種數據庫建立連接的方式是不一樣的,所以操作不同的數據庫需要使用不同的Driver類的connect()方法,這就是不同數據庫需要導入不同驅動的原因。
下面手動操作mysql的Driver類進行數據庫的連接和協議測試:

public static void testDriver() throws Exception {
    //導入mysql的驅動,並且獲得Driver實例
    Driver driver = (Driver) Class.forName("com.mysql.jdbc.Driver").newInstance();
    //url格式測試
    System.out.println("測試標準協議1: " + driver.acceptsURL("jdbc:mysql://"));
    System.out.println("測試標準協議2: " + driver.acceptsURL("jdbc:mysql:mxj://"));
    System.out.println("測試標準協議3: " + driver.acceptsURL("jdbc:mysql:replication://"));
    System.out.println("測試oracle協議: " + driver.acceptsURL("jdbc:oracle:thin:@//<host>:<port>/ServiceName"));
    //建立真正連接
    Properties properties = new Properties();
    properties.put("user", USER_NAME);
    properties.put("password", PASSWORD);
    Connection connection = driver.connect(URL, properties);
    System.out.println(connection == null);
}

最開始的示例代碼中,我們並不是直接獲取Driver實例然後獲取的Connection而是通過DriverManager來獲取的連接,這是因爲在實際應用中都是通過DriverManager來管理的Driver。
使用DriverManager的好處在於可以方便的管理多個驅動:只需要傳入一個url,DriverManger就會自動需要匹配這個url的驅動並建立連接返回Connection對象。
2)DriverManger
應用程序通過DriverManager來管理驅動,DriverManager的主要功能包括驅動的註冊、刪除,通過url獲取連接或驅動。下面是主要的接口簡介:
這裏寫圖片描述
首先是驅動的註冊問題,DriverManager如何知道有哪些驅動是可以管理的。首先來看下DriverManger的初始化方法:

/**
 * Load the initial JDBC drivers by checking the System property
 * jdbc.properties and then use the {@code ServiceLoader} mechanism
 */
static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

主要是調用了loadInitialDrivers()方法,下面是具體實現:

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    // If the driver is packaged as a Service Provider, load it.
    // Get all the drivers through the classloader
    // exposed as a java.sql.Driver.class service.
    // ServiceLoader.load() replaces the sun.misc.Providers()

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator driversIterator = loadedDrivers.iterator();

            /* Load these drivers, so that they can be instantiated.
             * It may be the case that the driver class may not be there
             * i.e. there may be a packaged driver with the service class
             * as implementation of java.sql.Driver but the actual class
             * may be missing. In that case a java.util.ServiceConfigurationError
             * will be thrown at runtime by the VM trying to locate
             * and load the service.
             *
             * Adding a try catch block to catch those runtime errors
             * if driver not available in classpath but it's
             * packaged as service and that service is there in classpath.
             */
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

簡而言之通過Class.forName()加載系統屬性:jdbc.drivers配置的驅動以及通過ServiceLoad加載META-INF/services目錄java.sql.Driver文件中配置的驅動(ServiceLoad是Java提供的一種根據配置文件加載類的加載器,類似於spring的依賴注入)。但是這兩種方式都只是加載了驅動而並沒有向DriverManger註冊的步驟,其實真正的註冊操作是在Driver裏面實現的,具體的來說是通過Driver中包含了一段將自身向DriverManger註冊的靜態代碼塊,在Driver被加載到內存中的時候這段代碼塊就會執行。下面是mysql Driver的註冊實現:

//
// Register ourselves with the DriverManager
//
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

除了DriverManger本身初始化加載驅動的方式,我們在代碼中通過Class.forName()加載的驅動也是這樣註冊到DriverManager上去的;這些註冊到DriverManger上去的驅動,會被保存在下面的數組中

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();

解決完了驅動註冊的問題,下面再介紹下DriverManger其它接口的大致實現。
getConnection()
這個方法有多個重載形式,作用是根據傳入的url建立和對應數據庫的連接並返回Connection對象。核心邏輯如下代碼–依次嘗試每一個註冊的驅動能否建立連接:

for(DriverInfo aDriver : registeredDrivers) {
    // If the caller does not have permission to load the driver then
    // skip it.
    if(isDriverAllowed(aDriver.driver, callerCL)) {
        try {
            println("    trying " + aDriver.driver.getClass().getName());
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
                // Success!
                println("getConnection returning " + aDriver.driver.getClass().getName());
                return (con);
            }
        } catch (SQLException ex) {
            if (reason == null) {
                reason = ex;
            }
        }

    } else {
        println("    skipping: " + aDriver.getClass().getName());
    }
}

getDriver()
傳入一個url,返回可以打開這個url的驅動。這裏基本邏輯同上,只是並不嘗試建立實際的連接,而是通過Driver的acceptsURL方法進行校驗

// Walk through the loaded registeredDrivers attempting to locate someone
// who understands the given URL.
for (DriverInfo aDriver : registeredDrivers) {
    // If the caller does not have permission to load the driver then
    // skip it.
    if(isDriverAllowed(aDriver.driver, callerClass)) {
        try {
            if(aDriver.driver.acceptsURL(url)) {
                // Success!
                println("getDriver returning " + aDriver.driver.getClass().getName());
            return (aDriver.driver);
            }

        } catch(SQLException sqe) {
            // Drop through and try the next driver.
        }
    } else {
        println("    skipping: " + aDriver.driver.getClass().getName());
    }
}

3)Connection
Connection表示與數據庫的連接,具體來說通過Connection對象可以獲取到數據庫的一些信息,這些信息包括:其表信息,應該支持的SQL語法,數據庫內有什麼存儲過程,此鏈接功能的信息等等。下面簡要介紹下常用的三個功能:
1.獲取可以執行sql或存儲過程的Statement對象,與數據庫進行交互。

Class.forName(DRIVER);
Driver driver = DriverManager.getDriver(URL);
Properties properties = new Properties();
properties.put("name", USER_NAME);
properties.put("password", PASSWORD);
Connection connection = driver.connect(URL,properties);
//靜態Statement對象
Statement statement = connection.createStatement();
//預編譯Statement對象
PreparedStatement preparedStatement = connection.prepareStatement(SQL);
//存儲過程Statement
CallableStatement callableStatement = connection.prepareCall(SQL);

2.控制sql語句的事務
默認情況下,Connection對關聯的Statemen執行的sql語句都是自動提交的,即Statement語句執行完成以後,自動commit影響物理數據庫;通過Connection設置
setAutoCommit爲false,可以進行事務控制,手動控制Statement語句的commit和rollback。

 Class.forName(DRIVER);
    Driver driver = DriverManager.getDriver(URL);
    Properties properties = new Properties();
    properties.put("name", USER_NAME);
    properties.put("password", PASSWORD);
    Connection connection = driver.connect(URL, properties);
    //設置不自動提交
    connection.setAutoCommit(false);
    Statement statement = connection.createStatement();
    try {
        statement.executeUpdate(SQL);
        //statement對象也可以獲取創建它的Connection對象
        //手動提交
        statement.getConnection().commit();
    } catch (SQLException e) {
        //發生異常時,手動迴歸
        statement.getConnection().rollback();
    }
}

3.獲取連接數據庫的元數據,即關於數據庫整體情況的一些數據

DatabaseMetaData metaData = connection.getMetaData();

4) Statement
Statement用於通過Connection將sql提交給服務器執行,並獲取執行的結果;對於批量sql或預編譯sql Statement還負責將其整理成數據庫能夠識別的語句再提交給服務器執行。
這裏寫圖片描述

Sql語句分爲增刪改查幾種(insert,delete,update,select),而JDBC根據是否改變數據,將其分爲兩類:
查詢操作:select
更新操作:update、insert、update
相應的,Statement執行具體sql的方法分爲以下幾類:
1.不區分語句類型,直接執行
statement提供了execute(String sql)來執行這種查詢,定義如下:
這裏寫圖片描述
如果執行的是查詢操作,函數返回true,然後可以通過statement.getResultSet() 方法來獲取 Resultset結果集;
如果執行的是更新操作,函數返回false,然後可以通過statement.getUpdateCount()方法獲取影響的行數。
2.執行查詢操作
stament提供了executeQuery(String sql)方法來執行查詢操作,具體定義如下:
這裏寫圖片描述
3.執行更新操作
statement提供了executeUpdate()方法來執行更新操作,具體定義如下:
這裏寫圖片描述
4.批量執行sql
有時候需要將一些sql語句一起提交給數據庫,批量執行,statement提供了一些方法,對批量sql的支持:
這裏寫圖片描述
這裏寫圖片描述
以上都是普通Statement的功能,對於PrepareStatement和CallBackStatement還會有些不同。
ResultSet指的是查詢語句的返回結果,是數據庫數據的映射,可以看成一個二維的數組,不過需要根據迭代器來逐行遍歷。
Resultset 提供了很多遊標定位的方法,部分方法已經在下面列出:
這裏寫圖片描述
三.jdbc訪問數據庫基本流程總結
一個基本的JDBC工作流程,分爲以下幾步:
1.加載特定數據庫驅動器實現類,並註冊驅動器(Driver會註冊到DriverManager中);
2. 根據特定的URL,返回可以接受此URL的數據庫驅動對象Driver;
3.使用數據庫驅動 Driver 創建數據庫連接Connection會話;
4. 使用 Connection對象創建 用於操作sql的Statement對象;
5. statement對象 .執行 sql語句,返回結果ResultSet 對象;
6. 處理ResultSet中的結果;
7. 關閉連接,釋放資源。

public class DBConnection {

    static final String  URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
    static final String USER_NAME ="louluan";
    static final String PASSWORD = "123456";

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

    public static void connectionTest(){

        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;

        try {
            //1.加載類,並註冊驅動器(Driver會註冊到DriverManager中)

            //加載Oracle數據庫驅動
            Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();

            //2.根據特定的URL,返回可以接受此URL的數據庫驅動對象
            Driver driver = DriverManager.getDriver(URL);
            Properties props = new Properties();
            props.put("user", USER_NAME);
            props.put("password", PASSWORD);

            //3.使用數據庫驅動創建數據庫連接Connection會話
            connection = driver.connect(URL, props);

            //4.獲得Statement對象
            statement = connection.createStatement();
            //5.執行 sql語句,返回結果
            resultSet = statement.executeQuery("select * from hr.employees");
            //6.處理結果,取出數據
            while(resultSet.next())
            {
                System.out.println(resultSet.getString(2));
            }

            //7.關閉鏈接,釋放資源
        } catch (ClassNotFoundException e) {
            System.out.println("加載Oracle類失敗!");
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
                //使用完成後管理鏈接,釋放資源,釋放順序應該是: ResultSet ->Statement ->Connection
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章