Connection (建立連線)

Sidney發表於2015-01-27

在於資料庫互動的時候, 第一件事是和資料來源(Data Source)也就是資料庫建立連線(Connection). 可以從這兩個類從資料來源取得連線:

  • DriverManager: 在 java.sql 包中, 連線時必須要指定 URL 去連線, 在 JDBC4.0 之前都要顯式地去載入驅動類, JDBC4.0後自動載入 CLASSPATH 中的驅動.

  • DataSource: 在 javax.sql 包中, 與 Driver Manager 不同的是, 一個資料庫驅動是一個DataSource. DataSource 可以提供連線池功能, 在 EE 中使用較為頻繁.

下面以 JAVADB 和 MySQL 為例, 在 JAVA DB 中有以下表:

    CREATE TABLE tags
    (
    id INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
    name VARCHAR(32) NOT NULL,
    CONSTRAINT primary_key PRIMARY KEY (id)
    );

表結構如下:

    ij> describe tags;
    COLUMN_NAME         |TYPE_NAME|DEC&|NUM&|COLUM&|COLUMN_DEF|CHAR_OCTE&|IS_NULL&
    ------------------------------------------------------------------------------
    ID                  |INTEGER  |0   |10  |10    |AUTOINCRE&|NULL      |NO      
    NAME                |VARCHAR  |NULL|NULL|32    |NULL      |64        |NO

(Note: JAVA DB 有兩種執行模式, 一種是 Embedded 模式, 一種是 Client-Server 模式. 前者的url是jdbc:derby:dbName, Embedded模式只能由執行時的Java程式訪問; 後者url是jdbc:derby://host:port/dbName, 可以由Java程式和ij工具同時訪問. 在這裡,我們適用後者).

在 MySQL 中有以下表:

    CREATE TABLE posts
    (
    id INTEGER NOT NULL AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    dt_create DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    visible BOOLEAN NOT NULL DEFAULT TRUE,
    PRIMARY KEY (id)
    );

表結構如下:

    mysql> describe posts;
    +-----------+--------------+------+-----+-------------------+----------------+
    | Field     | Type         | Null | Key | Default           | Extra          |
    +-----------+--------------+------+-----+-------------------+----------------+
    | id        | int(11)      | NO   | PRI | NULL              | auto_increment |
    | title     | varchar(255) | NO   |     | NULL              |                |
    | content   | text         | NO   |     | NULL              |                |
    | dt_create | datetime     | NO   |     | CURRENT_TIMESTAMP |                |
    | visible   | tinyint(1)   | NO   |     | 1                 |                |
    +-----------+--------------+------+-----+-------------------+----------------+
    5 rows in set (0.01 sec)

使用Driver Manager

在資料庫中建立好表後, 我們該往資料庫中寫資料了, 以下程式碼示例瞭如何從 DriverManager 新建一個連線, 並往資料庫中插入一條資料:

    package self.learn.sid.basic;

    import java.io.IOException;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.Properties;

    public class BlogDB {

        private static Connection getConnection(Properties props) throws SQLException {
            String url = props.getProperty("url");
            String user = props.getProperty("user");
            Connection conn = null;
            if (user.length() == 0) {
                conn = DriverManager.getConnection(url);
            } else {
                conn = DriverManager.getConnection(url, props);
            }
            String dbName = props.getProperty("dbName");
            conn.setCatalog(dbName);
            return conn;
        }
        public static void InsertNewPost() throws SQLException, IOException {
            Properties dbProps = new Properties();
            dbProps.load(ClassLoader.getSystemResourceAsStream("db.mysql.props"));
            Connection conn = getConnection(dbProps);
            Statement stmt = conn.createStatement();
            String sql = "INSERT INTO posts "
                       + "VALUES (NULL, \"First Post\", \"Hello!\", DEFAULT, true)";
            int nRows = stmt.executeUpdate(sql);
            assert(nRows == 1);
            stmt.close();
            conn.close();
        }

        public static void InsertNewTag() throws SQLException, IOException {
            Properties dbProps = new Properties();
            dbProps.load(ClassLoader.getSystemResourceAsStream("db.javadb.props"));
            Connection conn = getConnection(dbProps);
            Statement stmt = conn.createStatement();
            String sql = "INSERT INTO TAGS "
                    + "VALUES (DEFAULT, \'Java\')";
            int nRows = stmt.executeUpdate(sql);
            assert(nRows == 1);
            stmt.close();
            conn.close();
        }

        public static void main(String[] args) throws IOException, SQLException {
            BlogDB.InsertNewPost();
            BlogDB.InsertNewTag();
        }
    }

在這個例子中, 有以下兩個要點:

  • DriverManager 同時管理著 Mysql 和 Derby 這兩個資料庫驅動
  • 當需要一個資料庫連線的時候, 通過呼叫DriverManager.getConnection(url), 根據url的不同來獲取相應的資料庫連線.

使用 DataSource

使用 DataSource 建立連線時, 可以不用傳入 url. 值得注意的是, 雖然 DataSource 介面相同, 但是各個廠商的完成度有差異. 而且沒有 DataSourceManager 這樣的介面, 對於 DataSource 需要我們自己管理. 下面我們修改 DriverManager 的程式碼, 從 DataSource 新建連線:

    public class BlogDB {
        private static final String KEY_MYSQL = "MYSQL";
        private static final String KEY_DERBY = "DERBY";
        private static Map<String, DataSource> dsMap = new HashMap<String, DataSource>();

        private static Connection getConnection(Properties props) throws SQLException {

            String url = props.getProperty("url");
            String dbName = props.getProperty("dbName");
            String user = props.getProperty("user");
            String pswd = props.getProperty("password");

            Connection conn = null;
            if (url.matches("^jdbc:mysql:.*")) {
                conn = getMySQLConnection(dbName, user, pswd);
            } else if (url.matches("^jdbc:derby:.*")){
                conn = getDerbyConnection(dbName, user, pswd);
            }

            return conn;
        }

        private static Connection getMySQLConnection(String dbName, String user, String pswd) throws SQLException {
            Connection conn = null;
            MysqlDataSource ds = null;
            if (!dsMap.containsKey(KEY_MYSQL)) {
                ds = new MysqlDataSource();
                ds.setDatabaseName(dbName);
            } else {
                ds = (MysqlDataSource)dsMap.get(KEY_MYSQL);
            }
            conn = ds.getConnection(user, pswd);

            if (!dsMap.containsKey(KEY_MYSQL)) {
                dsMap.put(KEY_MYSQL, ds);
            }
            return conn;
        }

        private static Connection getDerbyConnection(String dbName, String user, String pswd) throws SQLException {
            Connection conn = null;
            ClientDataSource ds = null;
            if (!dsMap.containsKey(KEY_DERBY)) {
                ds = new ClientDataSource();
                ds.setDatabaseName(dbName);
            } else {
                ds = (ClientDataSource)dsMap.get(KEY_DERBY);
            }
            if (user != null && user.length() > 0) {
                conn = ds.getConnection(user, pswd);
            } else {
                conn = ds.getConnection();
            }

            if (!dsMap.containsKey(KEY_DERBY)) {
                dsMap.put(KEY_DERBY, ds);
            }
            return conn;
        }

        public static void InsertNewPost() throws SQLException, IOException {}

        public static void InsertNewTag() throws SQLException, IOException {}

        public static void main(String[] args) throws IOException, SQLException {}
    }

可以看出:

  • 呼叫 DataSource.getConnection() 的時候, 我們不用傳入 URL.
  • 需要自己管理 DataSource, 我們在 getConnection(Properties props) 中解析url只是為了解析DataSource的型別, 為不同的型別返回不同連線. 這樣可以適用和 DriverManager 例子 相同的配置檔案.

在 EE 中使用 DataSource

在 EE 中, DataSource 不需要手動去 new 出來, 一般是修改配置新增資料來源, 然後通過 JNDI 在程式中獲取對 DataSource的引用:

以 Tomcat 和 Mysql為例:

  1. 首先要在 WEB-INF/web.xml 中 新增一個資源, 型別為javax.sql.DataSource, 並修改 JNDI 名字為 jdbc/testDB:

    <resource-ref>
        <description>
            Resource reference to a factory for java.sql.Connection
            instances that may be used for talking to a particular
            database that is configured in the
            configurartion for the web application.
        </description>
        <res-ref-name>
            jdbc/testDB
        </res-ref-name>
        <res-type>
            javax.sql.DataSource
        </res-type>
        <res-auth>
            Container
        </res-auth>
    </resource-ref>
    
  2. 其次要在 META-INF/context.xml 中新增 DataSource 相關配置:

    <Resource name="jdbc/testDB"
              auth="Container"
              type="javax.sql.DataSource"
              username="root"
              password="5858982Znx"
              driverClassName="com.mysql.jdbc.Driver"
              url="jdbc:mysql://localhost:3306/testDB"
              maxActive="100"
              maxIdle="30"
              maxWait="10000"
              removeAbandoned="true"
              removeAbandonedTimeout="60"
              logAbandoned="true"/>
    
  3. 在程式中, 我們就可以這樣獲取連線了:

    public Connection getConnection() throws NamingException, SQLException {
        Context initCtx = new InitialContext();
        Context envCtx = (Context) initCtx.lookup("java:comp/env");
        DataSource ds = (DataSource)
                envCtx.lookup("jdbc/testDB");
        Connection conn = ds.getConnection();
        conn.setAutoCommit(false);
        return conn;
    }
    

相關文章