寫一個ORM框架的第一步(Apache Commons DbUtils)

新一次的內部提高開始了,若是您想寫一個框架從Apache Commons DbUtils開始學習是一種不錯的選擇,咱們先學習應用這個小「框架」再把源代碼理解,而後寫一個屬於本身的ORM框架不是夢。html

1、簡介

DbUtils是Apache下commons工具集中的一個小工具,它主要是對JDBC封裝的ORM小工具,簡化了JDBC的操做。之因此把它稱之爲工具而不是框架,是由於它和其餘的ORM框架仍是由很大的區別(例如Hibernate)。DbUtils並不支持所謂的聚合關聯映射、緩存機制、實體狀態的管理、延遲加載技術等等,它純粹只是對JDBC的API進行封裝。但也因爲它的這種簡單,所以性能高也是它的特色。DbUtils的源代碼並很少,也很容易讀得懂,很是適合於初學者閱讀和學習。mysql

2、ORM概要

對象關係映射(Object Relational Mapping),簡稱ORM。網上有不少專業的解釋,但對於初學者來講這些專業的術語也許不太好理解。因此咱們仍是經過一些實際例子來講明。git

在平常的開發中咱們常常用到實體或者DTO對象,這彷佛對每個程序員來講都是再熟悉不過的了。但這些所謂的實體或者DTO對象按照領域驅動設計的說法,它們只有本身的屬性,卻沒有屬於本身的業務行爲(get和set那不叫業務行爲)。所以咱們把他們稱之爲貧血模型。那用這些貧血模型來作什麼呢?沒錯,就是封裝數據。咱們常常會將一些不一樣類型的數據封裝到這些對象中。程序員

Users user = new Users();
user.setUserName(「張三」);
user.setAge(20);

給對象屬性賦完值之後,便把這個實體傳遞給Dao層執行保存操做。最終將數據持久化到數據庫的某張表中。sql

public int persist(Users user) {
String sql = 「INSERT INTO USERS_INFO(U_NAME, U_AGE) VALUES(?,?)」;
Connection conn = null;
PreparedStatment ps = null;
int row = 0;
try {
       conn = ConnUtil.getConnection();
       ps = conn.preparedStatment(sql);
       ps.setString(1, user.getUserName);
       ps.setInt(2, user.getAge());
       row = ps.executeUpdate();
} catch(SQLException e){
       e.printStackTrace();
} finally {
       ConnUtil.close(null, ps, conn);
}
return row;
}

在這個過程咱們發現一點,數據在Java中是以對象的形式存儲,而最終持久化到數據庫的時候是以關係型表格的形式存儲,也就是說,咱們把一個對象化結構的數據映射到了關係型數據庫中的這個過程,就是對象關係映射。反之,當咱們從關係型數據庫中查詢出的數據,又轉換成一個對象模型的數據結構,這也是對象關係映射。數據庫

public Users findUserById(int id) {
String sql = 「SELECT * FROM USERS_INFO WHERE U_ID = ?」;
Connection conn = null;
PreparedStatment ps = null;
ResultSet rs = null;
Users user = null;

try {
       conn = ConnUtil.getConnection();
       ps = conn.preparedStatment(sql);
       ps.setString(1, id);
       rs = ps.executeQuery();

       if(rs.next()) {
            user = new Users();
            user.setId(rs.getInt(1));
            user.setUserName(rs.getString(2));
            user.setAge(rs.getInt(3));
       }
} catch(SQLException e){
       e.printStackTrace();
} finally {
       ConnUtil.close(rs, ps, conn);
}
return user;
}

所以,咱們能夠將對象關係映射理解爲它是一種對象模型和關係型數據庫之間相互轉換的過程。在實際開發中,咱們會遇到大量的ORM操做,然而你會發現,這種操做其實大部分都是重複勞動,頻繁的給PreparedStatment設置參數,又或者是頻繁的從ResultSet中讀取數據保存到實體中,這些操做讓咱們在開發中下降了效率。咱們可否將這些繁瑣的操做封裝起來,我給你一個實體,你會自動幫我保存到數據庫。我告訴你一個對象的類型,你會自動將結果集中的數據封裝到這個對象中返回給我。這樣就大大簡化的JDBC的操做,提升了開發效率。接下來咱們所學習的DbUtils就幫咱們完成了這些事情。apache

3、下載與安裝

下載:數組

http://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi緩存

安裝:數據結構

教程中使用的是1.6的版本,下載的壓縮包是 commons-dbutils-1.6-bin.zip。解壓後將commons-dbutils-1.6.jar導入工程便可。

4、DML操做

首先,咱們在數據中建立USERS_INFO表。(mysql數據庫)

CREATE TABLE USERS_INFO (
         ID INT PRIMARY KEY AUTO_INCREMENT,  -- 主鍵
         U_NAME VARCHAR(50) NOT NULL,  --姓名
         U_AGE  INT NOT NULL  --年齡
) CHARSET=UTF8 

這裏咱們使用DBCP鏈接池做爲數據源。DBCP也是commons工具集中一個小工具。簡單點說,它主要用於監聽和管理JDBC的Connection對象,達到鏈接複用的效果(鏈接池的原理及好處能夠在JDBC教程的章節中進行查閱)。

DBCP鏈接池須要的jar文件:

  1. commons-dbcp2-2.1.1-bin.zip

下載地址: http://commons.apache.org/proper/commons-dbcp/download_dbcp.cgi

  1. commons-pool2-2.4.2-bin.zip

下載地址: http://commons.apache.org/proper/commons-pool/download_pool.cgi

  1. commons-logging-1.2-bin.zip

下載地址: http://commons.apache.org/proper/commons-logging/download_logging.cgi

解壓後將commons-dbcp2-2.1.1.jar、commons-pool2-2.4.2.jar、commons-logging-1.2.jar這三個jar文件導入工程。

接下來編寫一個DBCP鏈接池的工具類,用於獲取DataSource

public class DBCPUtil {
private static Properties prop = new Properties();
private static DataSource dataSource;
/**
* 初始化鏈接池
*/
static {

        //驅動
        prop.setProperty("driverClassName", "com.mysql.jdbc.Driver");

        //鏈接url
        prop.setProperty("url", "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8");

        //用戶名
        prop.setProperty("username", "root");

        //密碼
        prop.setProperty("password", "root");

        //初始鏈接數
        prop.setProperty("initialSize", "5");

        //最大活動鏈接數
        prop.setProperty("maxTotal", "20");

        //最小空閒鏈接

        prop.setProperty("minIdle", "5");

        //最大空閒鏈接
        prop.setProperty("maxIdle", "10");

        //等待鏈接的最大超時時間(單位:毫秒)
        prop.setProperty("maxWaitMillis", "1000");

        //鏈接未使用時是否回收
        prop.setProperty("removeAbandonedOnMaintenance", "true");
        prop.setProperty("removeAbandonedOnBorrow", "true");
        try {
            dataSource = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
/** * 獲取DataSource * @return */ public static DataSource getDataSource(){ return dataSource; } }

4.一、QueryRunner

這個類用於發送執行SQL語句並返回相應的結果。其實現當中對Connection以及PreparedStatment接口的API進行了封裝。QueryRunner有兩種方式來管理鏈接,一種是在構建QueryRunner實例時經過構造方法傳遞一個數據源DataSource實例;另外一種則是在調用相應的操做方法,如query、update、batch等這些方法時傳入一個Connection對象。這兩種方式有什麼區別呢?經過源碼的閱讀,咱們不難發現,其實對於DataSource的管理,在每次執行完相應操做後,DbUtils會自動關閉數據源的鏈接對象。而在調用相應的操做方法時傳入的Connection對象,在使用完以後是須要咱們手動去關閉這個資源的。在如下全部的例子中,咱們都將使用DataSouce的方式進行操做。

4.二、Insert操做

/**
*  添加操做
* @param userName 姓名
* @param age 年齡
* @return int 影響的行數
* @throws SQLException
*/
    public int persist(String userName, int age) throws SQLException{
        String sql = "INSERT INTO USERS_INFO(U_NAME,U_AGE) VALUES(?,?)";
        //建立Query執行器,經過構造方法傳入一個DataSource對象
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        // 執行update方法,方法的第一個和第二個參數分別是Connection對象和要執行的sql語句
        // 第三個參數開始是一個可變參數,分別是sql語句中所需的參數,對應上面語句中問號的順序
        // 執行完成後會返回影響的行數
        return qr.update(sql, userName, age);
    }

4.三、Update操做

 /**

*  更新操做,用的是一樣的方法,僅是sql語句的不一樣
* @param userName 姓名
* @param age 年齡
* @param id 主鍵
* @return int 影響的行數
* @throws SQLException
*/

    public int update(String userName, int age, int id) throws SQLException{
        String sql = "UPDATE USERS_INFO SET U_NAME = ?, U_AGE = ? WHERE ID = ?";
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        return qr.update(sql, userName, age, id);
    }

4.四、Delete操做

 /**
*  刪除操做,用的是一樣的update方法,僅是sql語句的不一樣
 * @param userName 姓名
 * @param id 主鍵
* @return int 影響的行數
* @throws SQLException
*/

    public int delete(int id) throws SQLException{
        String sql = "DELETE FROM USERS_INFO WHERE ID = ?";
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        return qr.update(sql, id);
    } 

4.五、批量操做

/**
* 批量操做
* @param params 批量執行SQL所需的參數,必須是一個二維數組
* @return int[] 影響的行數
* @throws SQLException
*/

    public int[] betch(Object[][] params) throws SQLException{
        String sql = "INSERT INTO USERS_INFO(U_NAME,U_AGE) VALUES(?,?)";
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());
        //使用batch方法,第三個參數是一個二維數組,數組中的每一個元素對應每次sql執行所需的參數
        //返回影響的行數的是一個int類型的數組
        return qr.batch(sql, params);
    }

5、DQL操做

5.一、ResultSetHandler接口

這個接口的核心做用是將查詢結果進行封裝(O/R Mapping)。它有許多不一樣的實現類,每個實現類都將ResultSet中的結果封裝成不一樣類型的數據對象。以下圖:

 

在ResultSetHandler衆多的處理器實現類中主要分爲兩類,一類是處理單條結果集的,一類是處理多條結果集的。

單條數據處理器:BeanHandler、ArrayHandler、MapHandler、ScalarHandler

多條數據處理器:AbstractKeyedHandler(KeyedHandler、BeanMapHandler)、AbstractListHandler(ColumnListHandler、ArrayListHandler、MapListHandler)

5.一、BeanHandler

將單條查詢結果封裝爲Bean對象

/**
* Users實體
*/

public class Users {

    private String userName;
    private int age;
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

使用BeanHandler查詢單條記錄:

/**
* 使用BeanHandler查詢單條記錄
* @param id 主鍵
* @return  Users
*/

public Users findUserById(int uid) throws SQLException{
        //當表的列名和實體的屬性名不一致時,在sql中使用as關鍵字給當前列指定別名,
        //別名和實體的屬性名對應便可
        String sql = "SELECT U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U WHERE U.ID = ?";

        //建立QueryRunner實例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用BeanHandler類,泛型參數指定實體名稱。構造方法指定實體的Class對象
        BeanHandler<Users> handler = new BeanHandler<>(Users.class);

        //調用query方法執行查詢,該方法的參數一和參數二爲鏈接對象和sql語句,
        //參數三爲ResultSetHandler接口的實現類對象,這裏是BeanHandler,
        //方法的第四個參數爲可變參數,是sql查詢時所需的條件參數
        //返回值則是一個封裝好的實體對象
        Users user = qr.query(sql, handler, uid);

        return user;
}

將多條查詢結果封裝爲List集合,集合中的每一個元素都是一個Bean對象

/**
* 使用BeanListHandler查詢多條記錄
* @return  List<Users>
*/

public List<Users> findUsers() throws SQLException{
        //當表的列名和實體的屬性名不一致時,在sql中使用as關鍵字給當前列指定別名,
        //別名和實體的屬性名對應便可
        String sql = "SELECT U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U";

        //建立QueryRunner實例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用BeanListHandler類
        BeanListHandler<Users> handler = new BeanListHandler<>(Users.class);

        //一樣調用query方法執行查詢,返回值則是一個List對象,List的泛型參數爲實體類型
        List<Users> list = qr.query(sql, handler);

        return list;
    }

5.三、ArrayHandler 

將單條查詢結果封裝爲一個Object數組

/**
* 使用ArrayHandler查詢單條記錄
* @param id 主鍵
* @return  Object []
*/

    public Object[] findUserById(int uid) throws SQLException{
        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";
//建立QueryRunner實例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ArrayHandler類,因爲ArrayHandler將結果集封裝爲Object數組,所以這個handler是不須要指定泛型的 ArrayHandler handler = new ArrayHandler(); //調用query方法執行查詢,返回值則是一個Object數組 Object[] objects = qr.query(sql, handler, uid); return objects; }

5.四、ArrayListHandler

將多條查詢結果封裝爲List集合,集合中的每一個元素都是一個Object數組

/**
* 使用ArrayListHandler查詢多條記錄
* @return  List<Object[]>
*/
    public List<Object[]> findUsers() throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";
//建立QueryRunner實例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用ArrayListHandler類 ArrayListHandler handler = new ArrayListHandler(); //一樣調用query方法執行查詢,返回值則是一個List對象,List的泛型參數爲Object數組類型 List<Object[]> list = qr.query(sql, handler); return list; }

5.五、MapHandler

將單條查詢結果封裝爲一個Map對象, Key保存的是查詢的列名,Value保存的是列的值

/**
* 使用MapHandler查詢單條記錄
* @param id 主鍵
* @return  Map<String, Object>
*/

    public Map<String, Object> findUserById(int id) throws SQLException{

        //當表的列名和實體的屬性名不一致時,在sql中使用as關鍵字給當前列指定別名,
        //別名和實體的屬性名對應便可
        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";

        //建立QueryRunner實例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用MapHandler類,因爲返回的是一個Map,所以這個handler也是是不須要指定泛型的
        MapHandler handler = new MapHandler();

        //調用query方法執行查詢,返回值則是Map對象
        Map<String, Object> map = qr.query(sql, handler, uid);

        return map;
    }

5.六、MapListHandler

將多條查詢結果封裝爲一個List集合,集合中的每一個元素都是一個Map對象

/**

* 使用MapListHandler查詢多條記錄
* @return  List<Map<String, Object>>
*/

    public List<Map<String, Object>> findUsers() throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";

        //建立QueryRunner實例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用MapListHandler類
        MapListHandler handler = new MapListHandler();

        //一樣調用query方法執行查詢,返回值則是一個List對象,List的泛型參數爲Map類型
        List<Map<String, Object>> list = qr.query(sql, handler);

        return list;
    }

5.七、ScalarHandler

將單條查詢結果中的某一列轉換爲指定的類型

/**
* 使用ScalarHandler查單條詢記錄中某一列
* @param id 主鍵
* @return String
*/

    public String findUserNameById(int id) throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U WHERE U.ID = ?";

        //建立QueryRunner實例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用ScalarHandler類,泛型參數指定要返回的數據類型,構造方法指定查詢結果中的某一列的下標
        ScalarHandler<String> handler = new ScalarHandler<>(1);

        //調用query方法執行查詢,返回值則是String類型
        String userName = qr.query(sql, handler, id);

        return userName;

}

5.八、ColumnListHandler

將多條查詢結果中的某一列封裝爲List集合

/**
* 使用ColumnListHandler查單多詢記錄中某一列
* @return  List<String>
*/

    public List<String> findUserNames() throws SQLException{

        String sql = "SELECT U.U_NAME, U.U_AGE FROM USERS_INFO U";

        //建立QueryRunner實例

        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用ColumnListHandler類, 泛型參數指定要返回的數據類型,構造方法指定查詢結果中的某一列的下標

        ColumnListHandler<String> handler = new ColumnListHandler<>(1);

        //一樣調用query方法執行查詢,返回值則是一個List對象,List的泛型參數指定爲查詢結果轉換的類型

        List<String> list = qr.query(sql, handler);

        return list;

    } 

5.九、KeyedHandler

將多條查詢結果轉換爲Map,並將某列保存爲Key,而Value則與MapHandler的查詢結果同樣,封裝的是一個Map集合

/**
* 使用KeyedHandler查詢結果轉換爲Map,並將某列的值保存爲Key
* @return  Map<Integer, Map<String, Object>>
*/

    public Map<Integer, Map<String, Object>> findUsers() throws SQLException{
        String sql = "SELECT * FROM USERS_INFO U";
//建立QueryRunner實例 QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource()); //使用KeyedHandler類,泛型參數指定key的類型,構造方法中的參數指定哪一列的值做爲key保存 //構造方法的參數能夠是查詢結果中某列的下標,也能夠是列的名稱 //KeyedHandler<Integer> handler = new KeyedHandler<>("ID"); KeyedHandler<Integer> handler = new KeyedHandler<>(1);
//一樣調用query方法執行查詢,返回值則是一個Map集合,key爲查詢某列的值,value爲封裝當前行的Map對象 Map<Integer, Map<String, Object>> map = qr.query(sql, handler); return map; }

5.10. BeanMapHandler

將多條查詢結果轉換爲Map,並將某列保存爲Key,而Value則與BeanHandler的查詢結果同樣,封裝的是一個Bean對象

/**
* 使用MapBeanHandler查詢結果轉換爲Map,並將某列的值保存爲Key
* @return  Map<Integer, Users>
*/

    public Map<Integer, Users> findUsers() throws SQLException{

        String sql = "SELECT U.ID, U.U_NAME AS userName, U.U_AGE AS age FROM USERS_INFO U";
        //建立QueryRunner實例
        QueryRunner qr = new QueryRunner(DBCPUtil.getDataSource());

        //使用KeyedHandler類,泛型第一個參數指定key的類型,第二個參數指定查詢結果轉換的Bean類型,
        //構造方法中的第一個參數指定Bean的Class對象,第二個參數指定將查詢結果的哪一列的值做爲key保存
        //構造方法的參數能夠是查詢結果中某列的下標,也能夠是列的名稱
        //BeanMapHandler<Integer, Users> handler = new BeanMapHandler<>(Users.class, "ID");
        BeanMapHandler<Integer, Users> handler = new BeanMapHandler<>(Users.class, 1);
//一樣調用query方法執行查詢,返回值是一個Map集合,key爲查詢某列的值,value爲封裝當前行的Bean對象 Map<Integer, Users> map = qr.query(sql, handler);
return map; }

6、做業

6.一、做業要求

1. 任意數據庫,創建一個表student,字段有id,name(id是自增加的,name是char類型)
2. 利用DbUtils完成CRUD操做
3. insert操做要能獲得返回的自增加值
4. Connection對象要能正確處理

6.二、提交內容

1. 整個項目的源代碼打包發到個人qq或者直接把你的項目的git地址告訴我便可

6.三、提交時間

2017-11-2號 星期四 中午12:00前

7、資料下載與說明

7.一、參考

7.二、資料下載

 連接: https://pan.baidu.com/s/1mhK2d5Y 密碼: d78g

7.三、說明

文章內容由「王亮」老師提供,內部學習時由「陳軍」老師講授

相關文章
相關標籤/搜索