1. 程式人生 > >[轉]Spring JdbcTemplate實現通用的超級dao,比泛型dao更加強大

[轉]Spring JdbcTemplate實現通用的超級dao,比泛型dao更加強大

這兩天比較忙,沒抽出什麼時間,這一停頓居然已經有2個評論了,無疑增添了我的不少動力。

在開始之前,先說下前面實現的通用泛型dao,在第四篇 通用自定義轉換到JavaBean的RowMapper實現中,把獲取屬性的操作也放到了mapRow的方法中,這會導致在每一行資料轉換的時候都會獲取一遍這個類的所有屬性資訊,雖然有快取但總還是隻獲取一次的好,之前有參考這個實現的朋友可以自行優化一下。

順便提一下,這個實現的dao名,我命名為SuperDao,之前的泛型實現都是以BaseDao的方式來命名,因為它需要有子類來繼承它並指定具體的泛型實體類才行,這次的封裝不需要任何子類的繼承即可使用(即使繼承它,子類得到的所有方法都可以直接呼叫,根本沒有必要,完全解耦),又想著換個名字,就取了SuperDao。

前段時間,寫了一個基於Spring JdbcTemplate的通用dao,有興趣的可以看看這個系列,記錄了實現過程。

最初的目的只是為了方便自己,沒想到發出來之後關注的人還挺多,看來這方面還是有實際需求的。

之前的通用dao在使用過程中,雖然省掉了我很多基礎的增刪改查程式碼操作,但是操作還是麻煩,功能也不夠強大,碰到一些不大通用但是又經常碰到的情況,完全無力或不怎麼使的上力,比如以下幾個情況:

1、查詢時,需要某個欄位不等於(column != value)某個值的時候。

2、查詢時,需要某個欄位等於多個值(column = value1 or column = value2)的時候。

3、需要以某個或多個欄位特定排序的時候。

4、當表中有個欄位比較大(clob等大欄位及text等),查詢時又不需要用到不想返回,節省效能時。

5、分頁查詢很不方便,當有以某個欄位排序等要求時無法實現通用。

6、有時只需要更新某一個欄位時,也需要new整個物件。

7、雖然通用,但是每個實體都需要建立一個號稱通用的泛型dao,有些簡單的dao裡面就是空的,感覺很多餘。

感受中最主要的就是這幾個,其它就不列舉了。如果你用過或封裝過一些常規的通用dao,相信也會有這些感受。

鑑於以上原因,決定再封裝一個更加強大的通用dao出來,可能是我實在太懶了吧,實在不喜歡寫這些重複又沒技術性的程式碼。

閱讀和參考了網上一些比較優秀的框架思路,發現還是可以實現的。有時候不是做不到,而是根本沒想到往這個思路上走。目前已經封裝的差不多了,在封裝完成之前,連我自己都沒想到可以這麼方便。個人感覺比之前的通用dao方便和強大的太多了,主要有以下方面的改進:

1、解決了上面列出的所有問題,並且程式碼量反而更少更簡潔了。

2、增加查詢欄位的黑白名單功能,要查哪些欄位由你說了算。

3、單個欄位的更新或查詢可以直接set、where,不必再new整個物件,並且支援一個欄位匹配多個值。

4、排序功能增強,多欄位、升序降序自由組合。

5、方便強大的分頁功能,無須額外操作,二三行程式碼搞定分頁,自動判斷資料庫,無須指定。

6、不必再每個實體類對應建立一個繼承於通用dao的dao了,一個dao自動判斷操作所有表。

先來看一下我設想中,理想的結構圖:

這裡寫圖片描述

SuperDao是各資料庫通用操作的實現,其它一些資料庫特有的操作可以由具體的子類來實現,這些特有的操作指的是該資料庫獨有的一些特性,比如Oracle的XMLTYPE欄位型別操作。

目前我用的比較多的資料庫是Mysql,在使用過程中並沒有碰到特有的需要封裝的操作(分頁在其它地方完成,下面會介紹),所以下面的介紹都是以SuperDao類為主,畢竟那麼多資料庫也列不完全。

先來看一下SuperDao的主要方法及實現的功能,

SuperDao介面:

import java.util.List;
/**
 * 通用dao
 *
 * Created by liyd on 6/26/14.
 */
public interface SuperDao {
    /**
     * 新增白名單
     *
     * @param field
     * @return
     */
    public SuperDao include(String... field);
    /**
     * 新增黑名單
     *
     * @param field
     * @return
     */
    public SuperDao exclude(String... field);
    /**
     * asc 排序屬性
     *
     * @param field the field
     * @return the super dao
     */
    public SuperDao asc(String... field);
    /**
     * desc 排序屬性
     *
     * @param field the field
     * @return the super dao
     */
    public SuperDao desc(String... field);
    /**
     * 設定操作屬性
     *
     * @param fieldName the field name
     * @param value the value
     * @return super dao
     */
    public SuperDao set(String fieldName, Object value);
    /**
     * 設定where條件屬性
     *
     * @param fieldName
     * @param values
     * @return
     */
    public SuperDao where(String fieldName, Object... values);
    /**
     * 設定where條件屬性
     *
     * @param fieldName the field name
     * @param operator the operator
     * @param values the values
     * @return super dao
     */
    public SuperDao whereAssign(String fieldName, String operator, Object... values);
    /**
     * 按設定的引數及條件更新
     *
     * @param clazz
     */
    public void update(Class<?> clazz);
    /**
     * 按設定的條件刪除
     * 
     * @param clazz
     */
    public void delete(Class<?> clazz);
    /**
     * 按設定的條件查詢
     * 
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> query(Class<T> clazz);
    /**
     * 插入一條記錄
     *
     * @param entity
     * @return
     */
    public Long insert(Object entity);
    /**
     * 插入一條記錄,不生成主鍵,主要用於oracle這類非自增長主鍵型別
     *
     * @param entity
     */
    public void add(Object entity);
    /**
     * 更新一條記錄
     *
     * @param entity
     */
    public void update(Object entity);
    /**
     * 刪除記錄
     *
     * @param clazz the clazz
     * @param id the id
     */
    public void delete(Class<?> clazz, Long id);
    /**
     * 刪除記錄 此方法會以實體中不為空的欄位為條件
     *
     * @param entity
     */
    public void delete(Object entity);
    /**
     * 刪除所有記錄
     * @param clazz the clazz
     */
    public void deleteAll(Class<?> clazz);
    /**
     * 得到記錄
     *
     * @param clazz
     * @param id
     * @return
     */
    public <T> T getById(Class<T> clazz, Long id);
    /**
     * 查詢單個記錄
     *
     * @param <T>   the type parameter
     * @param entity the entity
     * @return t t
     */
    public <T> T querySingleResult(T entity);
    /**
     * 查詢記錄數
     *
     * @param entity
     * @return
     */
    public int queryCount(Object entity);
    /**
     * 查詢列表
     *
     * @param entity the entity
     * @return the list
     */
    public <T> List<T> queryList(T entity);
    /**
     * 查詢所有列表
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> findAll(Class<T> clazz);
}

下面來說明一下各個方法的作用,首先我們來建立一下測試表和對應實體,

建表語句:

CREATE TABLE `BOOK` (
  `BOOK_ID` BIGINT NOT NULL AUTO_INCREMENT,
  `BOOK_NAME` VARCHAR(45) NULL,
  `BOOK_AUTHOR` VARCHAR(45) NULL,
  `GMT_CREATE` DATETIME NULL,
  `GMT_MODIFY` DATETIME NULL,
  PRIMARY KEY (`BOOK_ID`));
CREATE TABLE `USER` (
  `USER_ID` INT NOT NULL AUTO_INCREMENT,
  `USER_NAME` VARCHAR(45) NULL,
  `USER_AGE` INT NULL,
  `GMT_CREATE` DATETIME NULL,
  `GMT_MODIFY` DATETIME NULL,
  PRIMARY KEY (`USER_ID`));

對應實體:

/**
 * Created by liyd on 7/30/14.
 */
public class User extends Page {
    private Long    userId;
    private String  userName;
    private Integer userAge;
    private Date    gmtCreate;
    private Date    gmtModify;
    //getter and setter...
}
/**
 * Created by liyd on 7/30/14.
 */
public class Book extends Page {
    private Long   bookId;
    private String bookName;
    private String bookAuthor;
    private Date   gmtCreate;
    private Date   gmtModify;
    //getter and setter...
}

兩個實體類都繼承於Page,Page類主要就是為了儲存頁碼等資訊,分頁時會用到,程式碼如下:

public class Page implements Serializable {
    /** serialVersionUID */
    private static final long serialVersionUID = 4060766214127186912L;
    /** 每頁顯示條數 */
    protected int             itemsPerPage     = 20;
    /** 當前頁碼 */
    protected int             curPage          = 1;
    /** 關鍵字 */
    protected String          keywords;
    //getter and setter...
}

下面來說說SuperDao具體的使用,它不需要再有所謂通用的泛型類繼承於它,直接使用即可。

插入資料:

@Test
public void testInsert() {
    User user = new User();
    user.setUserName("liyd");
    user.setUserAge(18);
    user.setGmtCreate(new Date());
    Long userId = superDao.insert(user);
    System.out.println("insert userId:" + userId);
    Book book = new Book();
    book.setBookName("Java教程");
    book.setBookAuthor("liyd");
    book.setGmtCreate(new Date());
    Long bookId = superDao.insert(book);
    System.out.println("insert bookId:" + bookId);
}

插入資料,統一用superDao.insert(Object entity)方法,可以傳入任何的實體物件,方法會自動判斷,以上呼叫將會輸出:

insert userId:1
insert bookId:1
//查詢資料庫,發現數據已經成功插入
MariaDB [test]> select * from USER;
+---------+-----------+----------+---------------------+------------+
| USER_ID | USER_NAME | USER_AGE | GMT_CREATE          | GMT_MODIFY |
+---------+-----------+----------+---------------------+------------+
|       1 | liyd      |       18 | 2014-07-30 21:17:05 | NULL       |
+---------+-----------+----------+---------------------+------------+
MariaDB [test]> select * from BOOK;
+---------+------------+-------------+---------------------+------------+
| BOOK_ID | BOOK_NAME  | BOOK_AUTHOR | GMT_CREATE          | GMT_MODIFY |
+---------+------------+-------------+---------------------+------------+
|       1 | Java教程   | liyd        | 2014-07-30 21:17:05 | NULL       |
+---------+------------+-------------+---------------------+------------+

與insert方法對應的,還有一個add方法,add方法不會自動處理主鍵id,即insert方法適用於mysql、sql server等這類主鍵自增的資料庫,add方法適用於Oracle這類沒有自增主鍵的資料庫。
更新資料

@Test
public void testUpdate() {
    User user = new User();
    user.setUserId(1L);
    user.setUserName("liyd22");
    user.setUserAge(28);
    user.setGmtModify(new Date());
    superDao.update(user);
}

之前的通用dao保持一致,USER表的主鍵為USER_ID,當然你可以實現自己的nameHandler來改變它。

刪除資料

@Test
public void testDeleteById() {
    superDao.delete(User.class, 1L);
}
@Test
public void testDeleteByEntity() {
    User user = new User();
    user.setUserId(1L);
    superDao.delete(user);
}

刪除資料有兩個delete方法,直接傳入實體class物件和主鍵值,或者new一個實體設定主鍵值後傳入,個人比較喜歡前一個。

刪除所有資料

@Test
public void testDeleteAll() {
    superDao.deleteAll(User.class);
}

該呼叫將會刪除USER表的所有資料,慎用。刪除所有資料採用的是TRUNCATE,因此刪除後自增的主鍵id也會從新開始計算。

根據id查詢

@Test
public void testGetById() {
    User user = superDao.getById(User.class, 1L);
    System.out.println(user.getUserId());
    System.out.println(user.getUserName());
    System.out.println(user.getUserAge());
}

查詢單個結果

@Test
public void testQuerySingleResult() {
    User user = new User();
    user.setUserName("liyd");
    user = superDao.querySingleResult(user);
    System.out.println(user.getUserId());
    System.out.println(user.getUserName());
    System.out.println(user.getUserAge());
}

這個方法適合只有一個結果的情況。因為jdbcTemplate自帶的queryForObject等方法在沒有結果時會丟擲異常,所以這個底層仍使用了查詢列表的方式,當有多個結果時取第一個。

查詢結果數量

@Test
public void testQueryCount() {
    User user = new User();
    user.setUserName("liyd");
    int count = superDao.queryCount(user);
    System.out.println(count);
}

同樣,可以傳入任何的實體物件。

查詢列表

@Test
public void testQueryList() {
    User user = new User();
    user.setUserName("liyd");
    List<User> userList = superDao.queryList(user);
}

傳入的,仍然可以是任何的實體物件。

以上這些基本的操作方法雖然有所改變,但是變化並不大,方便的也有限。接下來要講的就是如何對一些方法組合使用了,還有分頁功能等。如果你夠細心的話,可能早就發現了怎麼沒有封裝分頁的方法。別急,不需要特地的封裝分頁方法,在查詢列表時直接就可以實現,這也是方便的地方,同樣它還可以和別的方法組合使用!