BeetlSQL,簡單和強大數據庫訪問工具(更新)

 



beetlsql 特色

BeetSql是一個全功能DAO工具, 同時具備Hibernate 優勢 & Mybatis優勢功能,適用於認可以SQL爲中心,同時又需求工具能自動能生成大量經常使用的SQL的應用。 java

  • 無需註解,自動生成大量內置SQL,輕易完成增刪改查功能
  • 數據模型支持Pojo,也支持Map/List這種快速模型,也支持混合模型
  • SQL 以更簡潔的方式,Markdown方式集中管理,同時方便程序開發和數據庫SQL調試。
  • SQL 模板基於Beetl實現,更容易寫和調試,以及擴展
  • 簡單支持關係映射而不引入複雜的OR Mapping概念和技術。
  • 具有Interceptor功能,能夠調試,性能診斷SQL,以及擴展其餘功能
  • 內置支持主從數據庫,經過擴展,能夠支持更復雜的分庫分表邏輯
  • 支持跨數據庫平臺,開發者所需工做減小到最小

5 分鐘例子

準備工做

爲了快速嘗試BeetlSQL,須要準備一個Mysql數據庫,而後執行以下sql腳本 mysql

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(64) DEFAULT NULL,
  `age` int(4) DEFAULT NULL,
  `userName` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

編寫一個Pojo類,與數據庫表對應 git

public class User {
    Integer id;
    String name;
    Integer age;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

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

}

代碼例子

寫一個java的Main方法,內容以下 spring

// 建立一個簡單的ConnectionSource,只有一個master
ConnectionSource source = ConnectionSourceHelper.simple(driver,url,userName,password);
// 採用mysql 習俗
DBStyle mysql = new MysqlStyle();
// sql語句放在classpagth的/sql 目錄下
SQLLoader loader = new ClasspathLoader("/sql"); 
// 數據庫命名跟java命名採用駝峯轉化
NameConversion nc = new  HumpNameConversion();
// 最後,建立一個SQLManager
SqlManager sqlManager = new SqlManager(source,mysql,loader); 

//使用內置的生成的sql 新增用戶
User user = new User();
user.setAge(19);
user.setName("xiandafu");
sqlManager.insert(user);
//使用內置sql查詢用戶
int id = 1;
user = sqlManager.unque(User.class,id);

//使用user.md 文件裏的select語句,參考下一節
User query = new User();
query.setName("xiandafu");
List<User> list = sqlManager.select("user.select",User.class,query)

SQL例子

爲了能執行user.select,須要在classpath裏創建一個user.md 文件,內容以下 sql

select
===
select * from user where 1=1
@if(!isEmpty(age)){
and age = #age#
@}          
@if(!isEmpty(name)){
and name = #name#
@}

關於如何寫sql模板,會稍後章節說明,以下是一些簡單說明。 數據庫

  • @ 和回車符號是定界符號,能夠在裏面寫beetl語句。 json

  • "#" 是站位符號,生成sql語句得時候,將輸出?,若是你想輸出表達式值,須要用text函數,或者任何以db開頭的函數,引擎則認爲是直接輸出文本。 api

  • isEmpty是beetl的一個函數,用來判斷變量是否爲空或者是否不存在. 數組

sql模板採用beetl緣由是由於beetl 語法相似js,且對模板渲染作了特定優化,相比於mybatis,更加容易掌握和功能強大。 瀏覽器

BeetlSQL 說明

得到SQLManager

SQLManager 是系統的核心,他提供了全部的dao方法。得到SQLManager,能夠直接構造SQLManager.並經過過單例獲取如:

ConnectionSource source = ConnectionSourceHelper.simple(driver,url,userName,password);  
// 採用mysql 習俗
DBStyle mysql = new MysqlStyle();
// sql語句放在classpagth的/sql 目錄下
SQLLoader loader = new ClasspathLoader("/sql"); 
// 數據庫命名跟java命名採用駝峯轉化
NameConversion nc = new  HumpNameConversion();
// 最後,建立一個SQLManager
SqlManager sqlManager = new SqlManager(source,mysql,loader);

更常見的是,已經有了DataSource,建立ConnectionSource 能夠採用以下代碼

ConnectionSource source = ConnectionSourceHelper.single(datasource);

若是是主從Datasource

ConnectionSource source = ConnectionSourceHelper.getMasterSlave(master,slaves)

Spring集成

<bean id="sqlManager" class="org.beetl.sql.ext.SpringBeetlSql">
    <property name="cs" >
        <bean  class="org.beetl.sql.ext.SpringConnectionSource">
            <property name="master" ref="dataSource"></property>
        </bean>
    </property>
    <property name="dbStyle">
        <bean class="org.beetl.sql.core.db.MySqlStyle"> </bean>
    </property>
    <property name="sqlLoader">
        <bean class="org.beetl.sql.core.ClasspathLoader"> 
            <property name="sqlRoot" value="/sql"></property>
        </bean>
    </property>
    <property name="nc">
        <bean class="org.beetl.sql.core.HumpNameConversion">
        </bean>
    </property>
    <property name="interceptors">
        <list>
            <bean class="org.beetl.sql.ext.DebugInterceptor"></bean>
        </list>
    </property>
</bean>
  • cs: 指定ConnectionSource,能夠用系統提供的DefaultConnectionSource,支持按照CRUD決定主從。例子裏只有一個master庫

  • dbStyle: 數據庫類型,目前只支持org.beetl.sql.core.db.MySqlStyle

  • sqlLoader: sql語句加載來源

  • nc: 命名轉化,有駝峯的HumpNameConversion,有數據庫下劃線的UnderlinedNameConversion

  • interceptors:DebugInterceptor 用來打印sql語句,參數和執行時間

注意: 任何使用了Transactional 註解的,將統一使用Master數據源,例外的是@Transactional(readOnly=true),這將讓Beetsql選擇從數據庫。

public class MyServiceImpl implements MyService {

    @Autowired
    SpringBeetlSql beetlsql ;

    @Override
    @Transactional()
    public int total(User user) {

        SQLManager dao = beetlsql.getSQLMananger();
        List<User> list = dao.all(User.class);
        int total = list .size();
        dao.deleteById(User.class, 3);
        User u =new User();
        u.id = 3;
        u.name="hello";
        u.age = 12;
        dao.insert(User.class, u);

        return total;

    }

}

能夠參考demo https://git.oschina.net/xiandafu/springbeetlsql

JFinal集成

在configPlugin 裏配置BeetlSql

JFinalBeetlSql.init();

默認會採用c3p0 做爲數據源,其配置來源於jfinal 配置,若是你本身提供數據源或者主從,能夠以下

JFinalBeetlSql.init(master,slaves);

因爲使用了Beetlsql,所以你無需再配置 數據庫鏈接池插件,和ActiveRecordPlugin,能夠刪除相關配置。

在controller裏,能夠經過JFinalBeetlSql.dao 方法獲取到SQLManager

SQLManager dao = JFinalBeetlSql.dao();
    BigBlog blog = getModel(BigBlog.class);     
    dao.insert(BigBlog.class, blog);

若是想控制事物,還須要註冊Trans

public void configInterceptor(Interceptors me) {
        me.addGlobalActionInterceptor(new Trans());
    }

而後業務方法使用

@Before(Trans.class)
    public void doXXX(){....+

這樣,方法執行完畢纔會提交事物,任何RuntimeException將回滾,若是想手工控制回滾.也能夠經過

Trans.commit()
Trans.rollback()

若是習慣了JFinal Record模式,建議用戶建立一個BaseBean,封裝SQLManager CRUD 方法便可。而後其餘模型繼承此BaseBean

能夠參考demo https://git.oschina.net/xiandafu/jfinal_beet_beetsql_btjson

SQLManager API

查詢API

模板類查詢(自動生成sql)

  • public List template(T t) 根據模板查詢,返回全部符合這個模板的數據庫
  • public List template(T t,RowMapper mapper) 同上,mapper能夠提供額外的映射,如處理一對多,一對一
  • public List template(T t,int start,int size) 同上,能夠翻頁
  • public List template(T t,RowMapper mapper,int start,int size) 翻頁,並增長額外的映射
  • public long templateCount(T t) 獲取符合條件的個數

經過sqlid查詢,sql語句在md文件裏

  • public List select(String sqlId, Class clazz, Map paras) 根據sqlid來查詢,參數是個map
  • public List select(String sqlId, Class clazz, Object paras) 根據sqlid來查詢,參數是個pojo
  • public List select(String sqlId, Class clazz, Map paras, int start, int size), 增長翻頁
  • public List select(String sqlId, Class clazz, Object paras, int start, int size) ,增長翻頁
  • public T selectSingle(String id,Object paras, Class target) 根據sqlid查詢,將對應的惟一值映射成指定的taget對象,RowMapper mapper 也隨着這些api提供,不在此列出了

  • public T selectSingle(String id,Map paras, Class target) 同上,參數是map

  • public Integer intValue(String id,Object paras) 查詢結果映射成Integer,輸入是objct

  • public Integer intValue(String id,Map paras) 查詢結果映射成Integer,輸入是map, 其餘還有 longValue,bigDecimalValue

更新API

自動生成sql

  • public void insert(Class<?> clazz,Object paras) 插入paras到paras關聯的表
  • public void insert(Class<?> clazz,Object paras,KeyHolder holder),插入paras到paras關聯的表,若是須要主鍵,能夠經過holder的getKey來獲取
  • public int updateById(Object obj) 根據主鍵更新,組件經過annotation表示,若是沒有,則認爲屬性id是主鍵
  • public int[] updateByIdBatch(List<?> list) 批量更新

經過sqlid更新

  • public int update(String sqlId, Object obj) 根據sqlid更新
  • public int update(String sqlId, Map paras) 根據sqlid更新,輸出參數是map
  • public int[] updateBatch(String sqlId,List<?> list) 批量更新
  • public int[] updateBatch(String sqlId,Map[] maps) 批量更新,參數是個數組,元素類型是map

BeetlSQL Annotation

對於自動生成的sql,默認不須要任何annotaton,類名對應於表名(經過NameConverstion類),getter方法的屬性名對應於列明(也是經過NameConverstion類),但有些狀況仍是須要anntation。

  • @<table>(name="xxxx") 告訴beetlsql,此類對應xxxx表。好比數據庫有User表,User類對應於User表,也能夠建立一個UserQuery對象,也對應於User表

    @<table>(name="user") public class QueryUser ..

  • @AutoID,做用於getter方法,告訴beetlsql,這是自增主鍵

  • @AssignID,做用於getter方法,告訴beetlsql,這是主鍵,且由代碼設定主鍵

  • @SeqID(name="xx_seq",做用於getter方法,告訴beetlsql,這是序列主鍵。

(注,若是想要獲取自增主鍵或者序列主鍵,須要在SQLManager.insert中傳入一個KeyHolder)

BeetlSQL 模型

BeetlSQL是一個全功能DAO工具,支持的模型也很全面,包括

  • Pojo, 也就是面向對象Java Object

  • Map/List, 對於一些敏捷開發,能夠直接使用Map/List 做爲輸入輸出參數

  • 混合模型,推薦使用混合模型。兼具靈活性和更好的維護性。Pojo能夠實現QueryResult,或者繼承QueryResultBean,這樣查詢出的ResultSet 除了按照pojo進行映射外,沒法映射的值將按照列表/值保存。以下一個混合模型:

    /*混合模型*/ public User extends QueryResultBean{ private int id ; pirvate String name; private int roleId; /*如下是getter和setter 方法*/ }

對於sql語句:

selectUser
===
select u.*,r.name r_name from user u left join role r on u.roleId=r.id .....

執行查詢的時候

List<User> list = sqlManager.select("user.selectUser",User.class,paras);
for(User user:list){
    System.out.println(user.getId());
    System.out.println(user.get("rName"));

}

程序能夠經過get方法獲取到未被映射到pojo的值,也能夠在模板裏直接 ${user.rName} 顯示(對於大多數模板引擎都支持)

Markdown方式管理


BeetlSQL集中管理SQL語句,SQL 能夠按照業務邏輯放到一個文件裏,如User對象放到user.md 裏,文件能夠按照模塊邏輯放到一個目錄下。文件格式拋棄了XML格式,採用了Markdown,緣由是

  • XML格式過於複雜,書寫不方便
  • XML 格式有保留符號,寫SQL的時候也不方便,如經常使用的< 符號 必須轉義
  • MD 格式自己就是一個文檔格式,也容易經過瀏覽器閱讀和維護

目前SQL文件格式很是簡單,僅僅是sqlId 和sql語句自己,以下

文件一些說明,放在頭部無關緊要,若是有說明,能夠是任意文字
        SQL標示
        ===
        SQL語句 

        SQL標示2
        ===
        SQL語句 2

全部SQL文件建議放到一個sql目錄,sql目錄有多個子目錄,表示數據庫類型,這是公共SQL語句放到sql目錄下,特定數據庫的sql語句放到各自自目錄下 當程序獲取SQL語句得時候,先會根據數據庫找特定數據庫下的sql語句,若是未找到,會尋找sql下的。以下代碼

List<User> list = sqlManager.select("user.select",User.class);

SqlManager 會根據當前使用的數據庫,先找sql/mysql/user.md 文件,確認是否有select語句,若是沒有,則會尋找sql/user.md

(注:默認的ClasspathLoader採用了這種方法,你能夠實現SQLLoader來實現本身的格式和sql存儲方式,如數據庫存儲)

SQL 模板基於Beetl實現,更容易寫和調試,以及擴展

SQL語句能夠動態生成,基於Beetl語言,這是由於

  • beetl執行效率高效 ,所以對於基於模板的動態sql語句,採用beetl很是合適

  • beetl 語法簡單易用,能夠經過半猜半式的方式實現,杜絕myBatis這樣難懂難記得語法。BeetlSql學習曲線幾乎沒有

  • 利用beetl能夠定製定界符號,徹底能夠將sql模板定界符好定義爲數據庫sql註釋符號,這樣容易在數據庫中測試,以下也是sql模板(定義定界符爲"--" 和 "null",null是回車意思);

    selectByCond
        ===
        select * form user where 1=1
        --if(age!=null)
        age=#age#
        --}
  • beetl 錯誤提示很是友好,減小寫SQL腳本編寫維護時間

  • beetl 能容易與本地類交互(直接訪問Java類),能執行一些具體的業務邏輯 ,也能夠直接在sql模板中寫入模型常量,即便sql重構,也會提早解析報錯

  • beetl語句易於擴展,提供各類函數,好比分表邏輯函數,跨數據庫的公共函數等

若是不瞭解beetl,可先本身嘗試按照js語法來寫sql模板,若是還有疑問,能夠查閱官網 http://ibeetl.com

Interceptor功能

BeetlSql能夠在執行sql先後執行一系列的Intercetor,從而有機會執行各類擴展和監控,這比已知的經過數據庫鏈接池作Interceptor更加容易。以下Interceptor都是有可能的

  • 監控sql執行較長時間語句,打印並收集。TimeStatInterceptor 類完成
  • 對每一條sql語句執行後輸出其sql和參數,也能夠根據條件只輸出特定sql集合的sql。便於用戶調試。DebugInterceptor完成
  • 對sql預計解析,彙總sql執行狀況(未完成,須要集成第三方sql分析工具)

你也能夠自行擴展Interceptor類,來完成特定需求。 以下,在執行數據庫操做前會執行befor,經過ctx能夠獲取執行的上下文參數,數據庫成功執行後,會執行after方法

public interface Interceptor {
    public void before(InterceptorContext ctx);
    public void after(InterceptorContext ctx);
}

InterceptorContext 以下,包含了sqlId,實際得sql,和實際得參數

public class InterceptorContext {
    private String sqlId;
    private String sql;
    private  List<Object> paras;
    private Map<String,Object> env  = null;
}

內置支持主從數據庫

BeetlSql管理數據源,若是隻提供一個數據源,則認爲讀寫均操做此數據源,若是提供多個,則默認第一個爲寫庫,其餘爲讀庫。用戶在開發代碼的時候,無需關心操做的是哪一個數據庫,由於調用sqlScrip 的 select相關api的時候,老是去讀取從庫,add/update/delete 的時候,老是讀取主庫。

sqlManager.insert(User.class,user) // 操做主庫,若是隻配置了一個數據源,則無所謂主從
    sqlManager.unique(id,User.class) //讀取從庫

主從庫的邏輯是由ConnectionSource來決定的,以下DefaultConnectionSource 的邏輯

@Override
public Connection getConn(String sqlId,boolean isUpdate,String sql,List<?> paras){
    if(this.slaves==null||this.slaves.length==0) return this.getWriteConn(sqlId,sql,paras);     
    if(isUpdate) return this.getWriteConn(sqlId,sql,paras);
    boolean onlyMaster = localMaster.get();
    if(onlyMaster) return this.getMaster(); 
    return this.getReadConn(sqlId, sql, paras);
}
  • localMaster 能夠強制SQLManager 使用主數據庫。參考api SQLManager. useMaster(MasterRunner f)

對於於不一樣的ConnectionSource 完成邏輯不同,對於spring,jfinal這樣的框架,若是sqlManager在事務環境裏,老是操做主數據庫,若是是隻讀事務環境 則操做從數據庫。若是沒有事務環境,則根據sql是查詢仍是更新來決定。

以下是SpringConnectionSource 提供的主從邏輯

@Override
public Connection getConn(String sqlId,boolean isUpdate,String sql,List paras){
    //只有一個數據源
    if(this.slaves==null||this.slaves.length==0) return this.getWriteConn(sqlId,sql,paras);
    //若是是更新語句,也得走master
    if(isUpdate) return this.getWriteConn(sqlId,sql,paras);
    //若是api強制使用master
    boolean onlyMaster = localMaster.get();
    if(onlyMaster) return this.getMaster();
    //在事物裏都用master,除了readonly事物
    boolean inTrans = TransactionSynchronizationManager.isActualTransactionActive();
    if(inTrans){
        boolean  isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        if(!isReadOnly){
            return this.getMaster();
        }
    }

     return this.getReadConn(sqlId, sql, paras);
}

能夠支持更復雜的分庫分表邏輯

開發者也能夠經過在Sql 模板裏完成分表邏輯而對使用者透明,以下sql語句

insert into 
    #text("log_"+ getMonth(date())#
    values () ...

注:text函數直接輸出表達式到sql語句,而不是輸出?。

log表示按照必定規則分表,table能夠根據輸入的時間去肯定是哪一個表

select * from 
    #text("log"+log.date)#
    where

注:text函數直接輸出表達式到sql語句,而不是輸出?。

一樣,根據輸入條件決定去哪一個表,或者查詢全部表

@ var tables = getLogTables();
    @ for(table in tables){
    select * from #text(table)# 
    @       if(!tableLP.isLast) print("union");
    @}      
    where name = #name#

跨數據庫平臺

如前所述,BeetlSql 能夠經過sql文件的管理和搜索來支持跨數據庫開發,如前所述,先搜索特定數據庫,而後再查找common。另外BeetlSql也提供了一些誇數據庫解決方案

  • DbStyle 描述了數據庫特性,注入insert語句,翻頁語句都經過其子類完成,用戶無需操心
  • 提供一些默認的函數擴展,代替各個數據庫的函數,如時間和時間操做函數date等

添加自定義方法

使用方式同Beetl,能夠在btsql-ext.properties裏添加自定義的函數. 須要注意的是,beetlsql在**站位符裏**老是輸出 ?,除非你的函數名是以db開頭,如db.ifNull,dbLog等。 或者使用內置的text 函數。對於以下sql語句

select * from ${dbLog()} where id = ${id} and status = "${text(@Constants.RUNNING)}"

會生成以下語句

select * from xxxLog where id = ? and status = "on".

問號對應的的值是變量id

相關文章
相關標籤/搜索