背景

由于工作的一些原因,需要從C#轉(zhuǎn)成JAVA。之前PetaPoco用得真是非常舒服,在學(xué)習(xí)JAVA的過程中熟悉了一下JAVA的數(shù)據(jù)組件:

  • MyBatis 非常流行,代碼生成也很成熟,性能也很好。但是DEBUG的時候不方便,且XML寫SQL也不是很適應(yīng),尤其是團(tuán)隊比較小沒有專職DBA的情況下。

  • Hibernate 使用過NHibernate,做企業(yè)應(yīng)用倒是挺適合的。掌握并用好它不是一件很容易的事情,尤其是團(tuán)隊水平不夠,目標(biāo)項目為互聯(lián)網(wǎng)游戲平臺的時候。

  • sql2o 開源項目,輕量級的ORM,與Dapper,PetaPoco非常類似,感覺上還是沒有PetaPoco好用。

基于以上的理解,便打算造一個JAVA版的PetaPoco —— PataPojo

  • 處于學(xué)習(xí)JAVA階段,能夠?qū)W習(xí)并使用一些高級語法

  • 理解JAVA的基礎(chǔ)數(shù)據(jù)庫組件

  • 若開發(fā)成功,則原有的團(tuán)隊與項目基本上能夠很好地遷移至JAVA開發(fā)環(huán)境,提升開發(fā)效率

功能概述

  • 輕量級

  • 用于簡單的POJO

  • 泛型的增刪改查的幫助方法提供

  • 自動分頁,自動計算出總記錄數(shù)及頁數(shù)據(jù)

  • 簡單的事務(wù)支持

  • 暫時僅支持MYSQL

  • 更好的參數(shù)支持

  • 仍然使用SQL語法,并提供強(qiáng)大的Sql生成器類

  • 開源

下載

怎么使用

引用jar包

下載petapojo.jar包后,在項目中進(jìn)行引用

執(zhí)行查詢

實體Mapping

// 對應(yīng)表 user_info@TableName("user_info")// 主鍵映射,字段名,是否自增@PrimaryKey(value = "id",autoIncrement = true)public class UserInfo {    private int id;    private String userName;    // 列名與字段名的映射 (若無注解,則按字段名映射為列名)
    @Column("password")    private String password;    // 該字段不進(jìn)行映射
    @Ignore
    private String none;    //... getter and setter}

下一步,使用database.java進(jìn)行查詢:

DruidDataSource dataSource = new DruidDataSource();// 配置dataSource// ...Database database = new Database(dataSource);
List<UserInfo> userList = database.query(UserInfo.class, "SELECT * FROM user_info");for (UserInfo userInfo : userList) {
       System.out.println(userInfo.getUserName());
}

查詢第一行第一列:

Integer number = database.executeScalar(Integer.class,        "SELECT COUNT(1) FROM user_info");

或者,查找第一條記錄:

UserInfo userInfo = database.firstOrDefault(UserInfo.class,        "SELECT * FROM user_info WHERE id = ?",123);

分頁

// 參數(shù)說明:// 1  - 頁索引// 10 - 頁大小// 20 - 是指 age > ? 的參數(shù)值PageInfo<UserInfo> pagedList = database.pagedList(UserInfo.class, 1,10,                "SELECT * FROM user_info WHERE age > ? ORDER BY createDate DESC",20);

返回的PageInfo描述:

/**
 * 分頁泛型
 */public class PageInfo<T> {    // 當(dāng)前頁索引
    private int currentPage;    // 總頁數(shù)
    private int totalPages;    // 總記錄數(shù)
    private int totalItems;    // 每頁記錄數(shù)
    private int itemsPrePage;    // 當(dāng)前頁列表
    private List<T> items;    public PageInfo() {
        items = new ArrayList<>();
    }    // ...
    // getter and setter}

執(zhí)行沒有返回的SQL:

// 返回影響行數(shù)int row = database.executeUpdate("DELETE FROM user_info WHERE id = ?",123);

實體的增刪改
新增:

UserInfo userInfo = new UserInfo();
userInfo.setUserName("PetaPojo");
userInfo.setPassword("123123");

database.insert(userInfo);

修改:

UserInfo userInfo = database.firstOrDefault(UserInfo.class,            "SELECT * FROM user_info WHERE id = ?", 1);
userInfo.setPassword("123456");

database.update(userInfo);

刪除:

UserInfo userInfo = database.firstOrDefault(UserInfo.class,            "SELECT * FROM user_info WHERE id = ?", 1);
database.delete(userInfo);// 或者 根據(jù)主鍵刪除database.delete(UserInfo.class,1);// 或者 根據(jù)條件進(jìn)行刪除database.delete(UserInfo.class,"WHERE id = ?",1);

自動添加查詢列

當(dāng)我們在使用ORM時,我們常常需要先編寫查詢列名及表名的SQL語句SELECT * FROM user_info,其實是非常影響開發(fā)效率的。
因此,PetaPojo增加了自動添加查詢列與表名的自動匹配功能。
例如:

UserInfo userInfo = database.firstOrDefault(UserInfo.class,        "SELECT * FROM user_info WHERE id = ?", 1);

PetaPojo可以允許簡化為:

UserInfo userInfo = database.firstOrDefault(UserInfo.class,"WHERE id = ?",1);

查詢組裝器 Sql Builder

在我們查詢數(shù)據(jù)庫時,經(jīng)常需要添加一些條件或排序之類的。總之,盡可能地讓SQL語句動態(tài)化或更靈活,以應(yīng)對復(fù)雜的業(yè)務(wù)需要。
與此同時,如果我們只是進(jìn)行單純的SQL硬編寫,開發(fā)效率將會是一個很大的問題,維護(hù)亦比較復(fù)雜費時。

在此基礎(chǔ)上,PetaPojo提供了一個非常便捷的SQL查詢組裝器。

基礎(chǔ)模式:

int id = 1;
Sql sql = Sql.create()
        .append("SELECT * FROM user_info")
        .append("WHERE id = ?",id);
UserInfo userInfo = database.firstOrDefault(UserInfo.class,sql);

或者:

int id = 1;
Sql sql = Sql.create()
        .append("SELECT * FROM user_info")
        .append("WHERE id = ?", id)
        .append("AND createDate >= ?", DateTime.now());
UserInfo userInfo = database.firstOrDefault(UserInfo.class, sql);

同樣,可以根據(jù)不同的條件來進(jìn)行組裝:

int id = 1;
Sql sql = Sql.create()
        .append("SELECT * FROM user_info")
        .append("WHERE id <> ?", id)if(age != null)
     sql.append("AND age > ?", age);if(startDate != null)
    sql.append("AND createDate >= ?", startDate);

List<UserInfo> userList = database.query(UserInfo.class,sql);

在SQL組裝時,參數(shù)列表是無限的,只需要與SQL語句中的參數(shù)替代符?相匹配即可,如:

int id = 1;
DateTime now = DateTime.now();

Sql sql = Sql.create()
        .append("SELECT * FROM user_info")
        .append("WHERE id <> ? AND createDate >= ?", id, now);

List<UserInfo> userList = database.query(UserInfo.class,sql);

Sql.append的基礎(chǔ)上,PetaPojo提供了更為便捷的鏈?zhǔn)胶瘮?shù):

Sql sql = Sql.create()
        .select("*")
        .from("user_info")
        .where("id = ?", 1)
        .where("createDate >= ?", DateTime.now().toString())
        .where("age >= ? AND age <= ?", 10, 20)
        .orderBy("createDate DESC");

PageInfo<UserInfo> pagedList = database.pagedList(UserInfo.class,1,10,sql);

基于自動添加查詢列的功能,上述語句可變改為:

Sql sql = Sql.create() 
        .where("id = ?", 1)
        .where("createDate >= ?", DateTime.now())
        .where("age >= ? AND age <= ?", 10, 20)
        .orderBy("createDate DESC");

PageInfo<UserInfo> pagedList = database.pagedList(UserInfo.class,1,10,sql);

因此,在一些復(fù)雜的查詢中,我們可這樣用:

int id = 1;
Sql sql = Sql.create() 
        .where("id <> ?", id)if(age != null)
     sql.where("age > ?", age);if(startDate != null)
    sql.where("createDate >= ?", startDate);

sql.orderBy("createDate DESC");

List<UserInfo> userList = database.query(UserInfo.class,sql);

枚舉支持

在開發(fā)中,經(jīng)常會碰到一些需要使用枚舉的地方,如訂單狀態(tài),用戶類型等。
JAVA默認(rèn)枚舉類型不是很好用,最主要的問題在于:用戶類型,訂單狀態(tài)這些枚舉在系統(tǒng)中我們可以理解為一個鍵值對的列表,而JAVA默認(rèn)枚舉的index是不可自定義的。

因此,PetaPojo在枚舉的支持上做了一些擴(kuò)展。

首先,PetaPojo定義了一個枚舉接口:

/**
 * 枚舉必須要實現(xiàn)的接口
 */public interface IEnumMessage {    int getValue();    String getName();
}

并提供了一個枚舉幫助類:

public class EnumUtils {    private static final ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();    private static final ReentrantReadWriteLock.ReadLock rl = rrwl.readLock();    private static final ReentrantReadWriteLock.WriteLock wl = rrwl.writeLock();    private static Map<Class<? extends IEnumMessage>, Map<Integer, IEnumMessage>> ENUM_MAPS = new HashMap<>();    /**
      * 根據(jù)key以及枚舉類型,得到具體的枚舉對象
      */
    public static <T extends IEnumMessage> T getEnum(Class<T> type, int value) {        return (T) getEnumValues(type).get(value);
    }     /**
      * 將枚舉類型轉(zhuǎn)化為一個鍵值對的Map
      */
    public static <T extends IEnumMessage> Map<Integer, String> getEnumItems(Class<T> type) {
        Map<Integer, IEnumMessage> map = getEnumValues(type);
        Map<Integer, String> resultMap = new HashMap<>();
        map.forEach((k, v) -> {
            resultMap.put(k, v.getName());
        });        return resultMap;
    }    private static <T extends IEnumMessage> Map<Integer, IEnumMessage> getEnumValues(Class<T> clazz) {
        rl.lock();        try {            if (ENUM_MAPS.containsKey(clazz))                return ENUM_MAPS.get(clazz);
        } finally {
            rl.unlock();
        }

        wl.lock();        try {            if (ENUM_MAPS.containsKey(clazz))                return ENUM_MAPS.get(clazz);

            Map<Integer, IEnumMessage> map = new HashMap<>();            try {                for (IEnumMessage enumMessage : clazz.getEnumConstants()) {
                    map.put(enumMessage.getValue(), enumMessage);
                }
            } catch (Exception e) {                throw new RuntimeException("getEnumValues error", e);
            }
            ENUM_MAPS.put(clazz, map);            return map;
        } finally {
            wl.unlock();
        }
    }
}

進(jìn)而,在項目使用枚舉(如UserType)時,實現(xiàn)IEnumMessage接口即可:

/**
 * 用戶類型
 */public enum UserType implements IEnumMessage {

    Student(1,"學(xué)生"),
    Teacher(2,"老師"),
    Coder(4,"碼農(nóng)");    private int value;    private String name;

    UserType(int value,String name){        this.value = value;        this.name = name;
    }    @Override
    public int getValue() {        return this.value;
    }    @Override
    public String getName() {        return this.name;
    }
}

對應(yīng)的實體:

@TableName("user_info")@PrimaryKey(value = "id",autoIncrement = true)public class UserInfo {    private int id;    private String userName;    private String password;    // 直接使用枚舉,數(shù)據(jù)庫中作為int型進(jìn)行存儲
    private UserType userType;    // ...  getter setter}
UserInfo userInfo = database.firstOrDefault(UserInfo.class,        "SELECT * FROM user_info WHERE id = ?",1);// 設(shè)置用戶類型為碼農(nóng)userInfo.setUserType(UserType.Coder);

database.update(userInfo);

時間類型支持 org.joda.time.DateTime

PetaPojo支持org.joda.time.DateTime類型,映射的數(shù)據(jù)庫表字段類型為datetime

@TableName("user_info")@PrimaryKey(value = "id",autoIncrement = true)public class UserInfo {    private int id;    private String userName;    private String password;    // 直接使用枚舉,數(shù)據(jù)庫中作為int型進(jìn)行存儲
    private UserType userType;    private DateTime createDate;    // ...  getter setter}

當(dāng)SQL查詢中的參數(shù)值為org.joda.time.DateTime時,這樣使用:

Sql sql = Sql.create()
            .where("createDate >= ?",DateTime.now().toString());// 將DateTime類型toString即可

以上便是整體PetaPojo的主要功能。