天天看點

JFinal 極速開發架構--5.ActiveRecord

5.1 概述

  ActiveRecord 是 JFinal 最核心的組成部分之一,通過 ActiveRecord 來操作資料庫,将極大地減少代碼量,極大地提升開發效率。

5.2 ActiveRecordPlugin

   ActiveRecord 是作為 JFinal 的 Plugin 而存在的,是以使用時需要在 JFinalConfig 中配置ActiveRecordPlugin。

以下是 Plugin 配置示例代碼:

public class DemoConfig extends JFinalConfig {

public void configPlugin(Plugins me) {

C3p0Plugin cp = new C3p0Plugin("jdbc:mysql://localhost/db_name", "userName", "password");

me.add(cp);

ActiveRecordPlugin arp = new ActiveRecordPlugin(cp);

me.add(arp);

arp.addMapping("user", User.class);

arp.addMapping("article", "article_id", Article.class);

}

}

  以上代碼配置了兩個插件:C3p0Plugin 與 ActiveRecordPlugin,前者是 c3p0 資料源插件, 後者是 ActiveRecrod 支援插件。ActiveReceord 中定義了 addMapping(String tableName, Class<? extends Model> modelClass>)方法,該方法建立了資料庫表名到 Model 的映射關系。

  另外,以上代碼中 arp.addMapping(“user”, User.class),表的主鍵名為預設為“id”,如果主 鍵名稱為 “user_id”則需要手動指定,如:arp.addMapping(“user”, “user_id”, User.class)。

5.3 Model

Model 是 ActiveRecord 中最重要的元件之一,它充當 MVC 模式中的 Model 部分。以下是

Model 定義示例代碼:

public class User extends Model<User> {

     public static final User dao = new User();

}

以上代碼中的 User 通過繼承 Model,便立即擁有的衆多友善的操作資料庫的方法。在 User 中聲明的 dao 靜态對象是為了友善查詢操作而定義的,該對象并不是必須的。基于 ActiveRecord 的 Model 無需定義屬性,無需定義 getter、setter 方法,無需 XML 配置,無需 Annotation 配置, 極大降低了代碼量。

以下為 Model 的一些常見用法:

// 建立name屬性為James,age屬性為25的User對象并添加到資料庫

new User().set("name", "James").set("age", 25).save();

// 删除id值為25的User

User.dao.deleteById(25);

// 查詢id值為25的User将其name屬性改為James并更新到資料庫

User.dao.findByIdLoadColumns (25).set("name", "James").update();

// 查詢id值為25的user, 且僅僅取name與age兩個字段的值

User user = User.dao.findByIdLoadColumns (25, "name, age");

// 擷取user的name屬性

String userName = user.getStr("name");

// 擷取user的age屬性

Integer userAge = user.getInt("age");

// 查詢所有年齡大于18歲的user

List<User> users = User.dao.find("select * from user where age>18");

// 分頁查詢年齡大于18的user,目前頁号為1,每頁10個user

Page<User> userPage = User.dao.paginate(1, 10, "select *", "from user where age > ?", 18);

特别注意:User 中定義的 public static final User dao 對象是全局共享的,隻能用于資料庫查詢, 不能用于資料承載對象。資料承載需要使用 new User().set(…)來實作。

5.4 JavaBean 與 Model 合體

    JFinal 2.1 版本提供了 ModelGenerator 、 BaseModelGenerator 、 MappingKitGernator 、 DataDictionaryGenerator,分别生成 Model、BaseModel、MappingKit、DataDictionary 四類檔案。 可根據資料表自動化生成這四類檔案。

    相對于 JFinal 2.1 之前的版本,生成後的 Model 繼承自 BaseModel 而非繼承自 Model, BaseModel 中擁有 getter、setter 方法遵守傳統 java bean 規範,Model 繼承自 BaseModel 即完成了 JavaBean 與 Model 合體,擁有了傳統 JavaBean 所有的優勢, 并且所有的 getter、setter 方法完全無需人工幹預,資料表有任何變動一鍵重新生成即可。

    具體用法可在 jfinal 官網下載下傳相關 GeneratorDemo,用法極度簡單。

5.5 JFinal 獨創 Db + Record 模式(無需映射、Record相當于通用Model、Db操作)

Db 類及其配套的 Record 類,提供了在 Model 類之外更為豐富的資料庫操作功能。使用 Db 與 Record 類時,無需對資料庫表進行映射,Record 相當于一個通用的 Model。以下為 Db + Record 模式的一些常見用法:

// 建立name屬性為James,age屬性為25的record對象并添加到資料庫

Record user = new Record().set("name", "James").set("age", 25);

Db.save("user", user);

// 删除id值為25的user表中的記錄

Db.deleteById("user", 25);

// 查詢id值為25的Record将其name屬性改為James并更新到資料庫

user = Db.findById("user", 25).set("name", "James");

Db.update("user", user);

// 擷取user的name屬性

String userName = user.getStr("name");

// 擷取user的age屬性

Integer userAge = user.getInt("age");

// 查詢所有年齡大于18歲的user

List<Record> users = Db.find("select * from user where age > 18");

// 分頁查詢年齡大于18的user,目前頁号為1,每頁10個user

Page<Record> userPage = Db.paginate(1, 10, "select *", "from user where age > ?", 18);

以下為事務處理示例:

boolean succeed = Db.tx(new IAtom(){

public boolean run() throws SQLException {

int count = Db.update("update account set cash = cash - ? where id = ?", 100, 123);

int count2 = Db.update("update account set cash = cash + ? where id = ?", 100, 456);

return count == 1 && count2 == 1;

        }

    }

);

以上兩次資料庫更新操作在一個事務中執行,如果執行過程中發生異常或者 invoke()方法 傳回 false,則自動復原事務。

5.6 聲明式事務

ActiveRecord 支援聲名式事務,聲明式事務需要使用 ActiveRecordPlugin 提供的攔截器來 實作,攔截器的配置方法見 Interceptor 有關章節。以下代碼是聲明式事務示例:

// 本例僅為示例, 并未嚴格考慮賬戶狀态等業務邏輯

@Before(Tx.class)

public void trans_demo() {

// 擷取轉賬金額

Integer transAmount = getParaToInt("transAmount");

// 擷取轉出賬戶id

Integer fromAccountId = getParaToInt("fromAccountId");

// 擷取轉入賬戶id

Integer toAccountId = getParaToInt("toAccountId");

// 轉出操作

Db.update("update account set cash = cash - ? where id = ?", transAmount, fromAccountId);

// 轉入操作

Db.update("update account set cash = cash + ? where id = ?", transAmount, toAccountId);

}

以上代碼中,僅聲明了一個 Tx 攔截器即為 action 添加了事務支援。除此之外 ActiveRecord 還配備了 TxByActionKeys、TxByActionKeyRegex、TxByMethods、TxByMethodRegex,分别 支援 actionKeys、actionKey 正則、actionMethods、actionMethod 正則聲明式事務,以下是示例代碼:

public void configInterceptor(Interceptors me) {

me.add(new TxByMethodRegex("(.*save.*|.*update.*)"));

me.add(new TxByMethods("save", "update"));

me.add(new TxByActionKeyRegex("/trans.*"));

me.add(new TxByActionKeys("/tx/save", "/tx/update"));

上例中的 TxByRegex 攔截器可通過傳入正規表達式對 action 進行攔截,當 actionKey 被正 則比對上将開啟事務。TxByActionKeys 可以對指定的 actionKey 進行攔截并開啟事務, TxByMethods 可以對指定的 method 進行攔截并開啟事務。

注意:MySql 資料庫表必須設定為 InnoDB 引擎時才支援事務,MyISAM 并不支援事務。

5.7 Cache

ActiveRecord 可以使用緩存以大大提高性能,以下代碼是 Cache 使用示例:

public void list() {

List<Blog> blogList = Blog.dao.findByCache("cacheName", "key", "select * from blog");

setAttr("blogList", blogList).render("list.html");

}

上例 findByCache 方 法 中 的 cacheName 需 要 在 ehcache.xml 中配置 如: <cache name="cacheName" …> 。 此 外 Model.paginateByCache(…) 、 Db.findByCache(…) 、 Db.paginateByCache(…)方法都提供了 cache 支援。在使用時,隻需傳入 cacheName、key 以及 在 ehccache.xml 中配置相對應的 cacheName 就可以了。

5.8 Dialect 多資料庫支援

目前 ActiveRecordPlugin 提供了 MysqlDialect、OracleDialect、AnsiSqlDialect 實作類。 MysqlDialect 與 OracleDialect 分别實作對 Mysql 與 Oracle 的支援,AnsiSqlDialect 實作對遵守 ANSI SQL 資料庫的支援。以下是資料庫 Dialect 的配置代碼:

public class DemoConfig extends JFinalConfig {

public void configPlugin(Plugins me) {

ActiveRecordPlugin arp = new ActiveRecordPlugin(…); me.add(arp);

// 配置Postgresql方言

arp.setDialect(new PostgresqlDialect());

}

}

5.9 表關聯操作

JFinal ActiveRecord 天然支援表關聯操作,并不需要學習新的東西,此為無招勝有招。表 關聯操作主要有兩種方式:一是直接使用 sql 得到關聯資料;二是在 Model 中添加擷取關聯資料的方法。

假定現有兩張資料庫表:user、blog,并且 user 到 blog 是一對多關系,blog 表中使用 user_id關聯到 user 表。如下代碼示範使用第一種方式得到 user_name:

public void relation() {

String sql = "select b.*, u.user_name from blog b inner join user u on b.user_id=u.id where b.id=?";

Blog blog = Blog.dao.findFirst(sql, 123); String name = blog.getStr("user_name");

}

以下代碼示範第二種方式在 Blog 中擷取相關聯的 User 以及在 User 中擷取相關聯的Blog:

public class Blog extends Model<Blog>{

public static final Blog dao = new Blog();

public User getUser() {

return User.dao.findById(get("user_id"));

}

}

public class User extends Model<User>{

public static final User dao = new User();

public List<Blog> getBlogs() {

return Blog.dao.find("select * from blog where user_id=?", get("id"));

}

}

5.10 複合主鍵

JFinal ActiveRecord 從 2.0 版本開始,采用極簡設計支援複合主鍵,對于 Model 來說需要 在映射時指定複合主鍵名稱,以下是具體例子:

ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);

// 多資料源的配置僅僅是如下第二個參數指定一次複合主鍵名稱

arp.addMapping("user_role", "userId, roleId", UserRole.class);

//同時指定複合主鍵值即可查找記錄 UserRole.dao.findById(123, 456);

//同時指定複合主鍵值即可删除記錄 UserRole.dao.deleteById(123, 456);

如上代碼所示,對于 Model 來說,隻需要在添加 Model 映射時指定複合主鍵名稱即可開 始使用複合主鍵,在後續的操作中 JFinal 會對複合主鍵支援的個數進行檢測,當複合主鍵數量 不正确時會報異常,尤其是複合主鍵數量不夠時能夠確定資料安全。複合主鍵不限定隻能有兩 個,可以是資料庫支援下的任意多個。

對于 Db + Record 模式來說,複合主鍵的使用不需要配置,直接用即可:

Db.findById("user_role", "roleId, userId", 123, 456);

Db.deleteById("user_role", "roleId, userId", 123, 456);

5.11 Oracle 支援

    Oracle 資料庫具有一定的特殊性,JFinal 針對這些特殊性進行了一些額外的支援以友善廣 大的 Oracle 使用者。以下是一個完整的 Oracle 配置示例:

public class DemoConfig extends JFinalConfig {

public void configPlugin(Plugins me) { C3p0Plugin cp = new C3p0Plugin(……);

//配置Oracle驅動

cp. setDriverClass("oracle.jdbc.driver.OracleDriver"); me.add(cp);

ActiveRecordPlugin arp = new ActiveRecordPlugin(cp); me.add(arp);

// 配置Oracle方言

arp.setDialect(new OracleDialect());

//  配置屬性名(字段名)大小寫不敏感容器工廠 arp.setContainerFactory(new CaseInsensitiveContainerFactory()); arp.addMapping("user", "user_id", User.class);

}

由于 Oracle 資料庫會自動将屬性名(字段名)轉換成大寫,是以需要手動指定主鍵名為大寫, 如:arp.addMaping(“user”, “ID”, User.class)。如果想讓 ActiveRecord 對屬性名(字段名)的大 小 寫 不 敏 感 可 以 通 過 設 置 CaseInsensitiveContainerFactory 來達到 , 有 了 這 個 設 置 , 則 arp.addMaping(“user”, “ID”, User.class)不再需要了。

另外,Oracle 并未直接支援自增主鍵,JFinal 為此提供了便捷的解決方案。要讓 Oracle 支 持自動主鍵主要分為兩步:一是建立序列,二是在 model 中使用這個序列,具體辦法如下:

1:通過如下辦法建立序列,本例中序列名為:MY_SEQ

CREATE SEQUENCE MY_SEQ INCREMENT BY 1

MINVALUE 1

MAXVALUE 9999999999999999

START WITH 1

CACHE 20;

2:在 YourModel.set(…)中使用上面建立的序列

// 建立User并使用序列

User user = new User().set("id", "MY_SEQ.nextval").set("age", 18); user.save();

// 擷取id值

Integer id = user.get("id");

序列的使用很簡單,隻需要 yourModel.set(主鍵名,  序列名 + “.nextval”)就可以了。特别注意這裡的  “.nextval”  字尾一定要是小寫,OracleDialect 對該值的大小寫敏感。

5.12 多資料源支援

ActiveRecordPlugin 可同時支援多資料源、多方言、多緩存、多事務級别等特性,對每個 ActiveRecordPlugin 可進行彼此獨立的配置。簡言之 JFinal 可以同時使用多資料源,并且可 以針對這多個資料源配置獨立的方言、緩存、事務級别等。

當使用多資料源時,隻需要對每個 ActiveRecordPlugin 指定一個 configName 即可,如下是代碼示例:

public void configPlugin(Plugins me) {

// mysql 資料源

C3p0Plugin dsMysql = new C3p0Plugin(…); me.add(dsMysql);

// mysql ActiveRecrodPlugin 執行個體,并指定configName為 mysql ActiveRecordPlugin arpMysql = new ActiveRecordPlugin("mysql", dsMysql); me.add(arpMysql);

arpMysql.setCache(new EhCache()); arpMysql.addMapping("user", User.class);

// oracle 資料源

C3p0Plugin dsOracle = new C3p0Plugin(…); me.add(dsOracle);

// oracle ActiveRecrodPlugin 執行個體,并指定configName為 oracle ActiveRecordPlugin arpOracle = new ActiveRecordPlugin("oracle", dsOracle); me.add(arpOracle);

arpOracle.setDialect(new OracleDialect()); arpOracle.setTransactionLevel(8); arpOracle.addMapping("blog", Blog.class);

}

以上代碼建立了創了兩個 ActiveRecordPlugin 執行個體 arpMysql 與 arpOrace,特别注意建立實 例的同時指定其 configName 分别為 mysql 與 oracle。arpMysql 與 arpOracle 分别映射了不同的Model,配置了不同的方言。

對于 Model 的使用,不同的 Model 會自動找到其所屬的 ActiveRecrodPlugin 執行個體以及相關 配置進行資料庫操作。假如希望同一個 Model 能夠切換到不同的資料源上使用,也極度友善, 這種用法非常适合不同資料源中的 table 擁有相同表結構的情況,開發者希望用同一個 Model 來操作這些相同表結構的 table,以下是示例代碼:

public void multiDsModel() {

// 預設使用arp.addMapping(...)時關聯起來的資料源

Blog blog = Blog.me.findById(123);

// 隻需調用一次use方法即可切換到另一資料源上去

blog.use("backupDatabase").save();

}

上例中的代碼,blog.use(“backupDatabase”)方法切換資料源到 backupDatabase 并直接将數 據儲存起來。

特别注意:隻有在同一個 Model 希望對應到多個資料源的 table 時才需要使用 use 方法,如果 同一個 Model 唯一對應一個資料源的一個 table,那麼資料源的切換是自動的,無需使用 use 方法。

對于 Db + Record 的使用,資料源的切換需要使用 Db.use(cnfigName)方法得到資料庫操作 對象,然後就可以進行資料庫操作了,以下是代碼示例:

// 查詢 dsMysql資料源中的 user

List<Record> users = Db.use("mysql").find("select * from user");

// 查詢 dsOracle資料源中的 blog

List<Record> blogs = Db.use("oracle").find("select * from blog");

以上兩行代碼,分别通過 configName 為 mysql、oracle 得到各自的資料庫操作對象,然後 就可以如同單資料完全一樣的方式來使用資料庫操作 API 了。簡言之,對于 Db + Record 來 說,多資料源相比單資料源僅需多調用一下 Db.use(configName),随後的 API 使用方式完全一 樣。

注意最先建立的 ActiveRecrodPlugin 執行個體将會成為主資料源,可以省略 configName。最先建立的 ActiveRecrodPlugin 執行個體中的配置将預設成為主配置,此外還可以通過設定 configName為 DbKit.MAIN_CONFIG_NAME 常量來設定主配置。

5.13 非 web 環境下使用 ActiveRecord

ActiveRecordPlugin 可以獨立于 java web 環境運作在任何普通的 java 程式中,使用方式極 度簡單,相對于 web 項目隻需要手動調用一下其 start() 方法即可立即使用。以下是代碼示例:

public class ActiveRecordTest {

public static void main(String[] args) {

DruidPlugin dp = new DruidPlugin("localhost", "userName", "password"); ActiveRecordPlugin arp = new ActiveRecordPlugin(dp); arp.addMapping("blog", Blog.class);

// 與web環境唯一的不同是要手動調用一次相關插件的start()方法

dp.start();

arp.start();

// 通過上面簡單的幾行代碼,即可立即開始使用

new Blog().set("title", "title").set("content", "cxt text").save(); Blog.me.findById(123);

}

}

注意:ActiveRecordPlugin 所依賴的其它插件也必須手動調用一下 start()方法,如上例中的 dp.start()。