摘要:摘要介紹簡單實用,以及相對于傳統框架的不同點。最令人滿意的就是在實際使用過程中解決問題的靈活性。當前在數據服務組擔任開發工程師,主要負責服務器開發。
摘要
介紹JOOQ簡單實用,以及相對于傳統ORM框架的不同點。
(圖片來自http://www.jooq.org/)
正文 JOOQ是啥?JOOQ 是基于Java訪問關系型數據庫的工具包,輕量,簡單,并且足夠靈活,可以輕松的使用Java面向對象語法來實現各種復雜的sql。對于寫Java的碼農來說ORMS再也熟悉不過了,不管是Hibernate或者Mybatis,都能簡單的使用實體映射來訪問數據庫。但有時候這些 ‘智能’的對象關系映射又顯得笨拙,沒有直接使用原生sql來的靈活和簡單,而且對于一些如:joins,union, nested selects等復雜的操作支持的不友好。JOOQ 既吸取了傳統ORM操作數據的簡單性和安全性,又保留了原生sql的靈活性,它更像是介于 ORMS和JDBC的中間層。對于喜歡寫sql的碼農來說,JOOQ可以完全滿足你控制欲,可以是用Java代碼寫出sql的感覺來。就像官網說的那樣 :
</>復制代碼
get back in control of your sql
這貨有啥優點
JOOQ 目前在國內還是很小眾,第一次聽說這玩意還是通過stream 大神的推薦。對于從SSH成長起來的猿類來說,心里也會質疑 “這玩意用的人那么少,靠不靠譜” ,“會不會有很多坑要踩”。通過對著官方文檔寫了幾個demo,頓時心生敬畏,一個念頭沖到腦袋 " 這東西一定會火",于是果斷在項目中使用。在使用過程中也會遇到各種小問題,通過幫助手冊和DEMO都能最終解決。相對于Hibernate或者其他ORMS的,JOOQ的編程模式有很大不同,強大的Fluent API使用起來非常方便和流暢。現在我們的項目(MaxWon)使用JOOQ已經在生產環境運行了很長的一段時間,從來沒花太多時間折騰在數據訪問層上面。對于開發來說感受最深的就是這貨真的很簡單很靈活,正如文章標題那樣,這是一個‘殺器’。下面是我總結的幾點,個人愚見。
DSL(Domain Specific Language )風格,代碼夠簡單和清晰。遇到不會寫的sql可以充分利用IDEA代碼提示功能輕松完成。
保留了傳統ORM 的優點,簡單操作性,安全性,類型安全等。不需要復雜的配置,并且可以利用Java 8 Stream API 做更加復雜的數據轉換。
支持主流的RDMS和更多的特性,如self-joins,union,存儲過程,復雜的子查詢等等。
豐富的Fluent API和完善文檔。
runtime schema mapping 可以支持多個數據庫schema訪問。簡單來說使用一個連接池可以訪問N個DB schema,使用比較多的就是SaaS應用的多租戶場景。
如何使用具體怎么使用官網文檔說的其實已經很詳細了,愛學習的同學可以參閱一下。下面我根據實際項目中使用的過程講述JOOQ的入門使用方法。
描述 | 名稱 |
---|---|
平臺 | JDK 1.8 |
maven | 3.3.9 |
JOOQ | 3.7.3 |
RDS | Mysql 5.7 |
mysql-connector | 5.1.39 |
maven依賴配置如下:
</>復制代碼
mysql
mysql-connector-java
${mysql.version}
org.jooq
jooq
${jooq.version}
org.jooq
jooq-meta
${jooq.version}
org.jooq
jooq-codegen
${jooq.version}
目前官方提供了通過 java org.jooq.util.GenerationTool 來生成映射代碼,但過程還是有點繁瑣,這里就不演示了。還好萬能的maven插件幫助我們解決了這個問題。
</>復制代碼
jooq
jooq
org.jooq
jooq-codegen-maven
${jooq.version}
generate
mysql
mysql-connector-java
${mysql.version}
${jdbc.driver}
${jdbc.url}
${jdbc.user}
${jdbc.password}
org.jooq.util.mysql.MySQLDatabase
.*
${jdbc.database.name}
BOOLEAN
(?i:TINYINT(s*(d+))?(s*UNSIGNED)?)
false
com.maxleap.jooq.data.jooq
src/main/java
false
false
配置目標數據庫schema信息后運行
</>復制代碼
$ mvn clean install -Djooq
如果一切順利的話,在項目目錄下會看到JOOQ自動生成的代碼
使用數據庫的schema信息,JOOQ會自動生成對應的Java Record,這樣就可以使用Record來操作對應的數據庫和表,不需任何其他的關系映射配置。
下面展示使用JOOQ 增刪改查的例子
</>復制代碼
public class JOOQTest {
private DSLContext dslContext;
@Before
public void before() {
this.dslContext = getDSLContext();
}
@Test
public void insert() {
MyStore store = new MyStore();
store.setName("foo");
store.setAddress("mars No. 1989");
StoreRecord storeRecord = dslContext.newRecord(Tables.STORE, store);
storeRecord.insert();
dslContext.insertInto(Tables.STORE)
.set(Store.STORE.NAME, "bar")
.set(Store.STORE.ADDRESS, "eclipse No.1891")
.execute();
}
@Test
public void find() {
dslContext.selectFrom(Tables.STORE)
.where(Store.STORE.NAME.eq("foo"))
.fetchInto(MyStore.class)
.stream()
.forEach(myStore -> System.out.println(myStore.getName()));
}
@Test
public void update() {
dslContext.update(Tables.STORE)
.set(Store.STORE.ADDRESS, "sun No.1988")
.where(Store.STORE.ID.eq(UInteger.valueOf(1)))
.execute();
}
@After
public void after() {
dslContext.delete(Tables.STORE);
}
private DSLContext getDSLContext() {
try {
Connection connection =
DriverManager.getConnection("jdbc:mysql://2.mysql.myself:3306/app_maker", "mars","mars");
return DSL.using(connection, SQLDialect.MYSQL)
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static class MyStore {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}
首先根據mysql connection 信息構造DSLContext,然后使用它來對數據庫進行增刪改查操作。對于具體方法我就不解釋了,懂一點sql我相信都應該能看懂。
上面例子可以窺探出JOOQ DSL 語法風格以及JOOQ的基本使用方法,通過代碼可以so easy 的在腦子里映射出對應的sql語句,感覺就像直接寫sql一樣。但JOOQ和sql不同之處在于它保證了你寫的sql語法正確性和類型安全,如果配上IDEA代碼提示功能,那就更加完美了,再難寫的sql只要 . 一下就會有完整的代碼提示。
查看DSL類源碼看以看到里面大概有14000多行代碼,都是靜態方法,里面包含JOOQ支持的各種DB操作。對于常用的的場景使用DSLContext一般都能滿足需求,但是對于是一些復雜的需求,如創建一個臨時表,column別名,table別名,schema 動態設置,就必須使用DSL來進行操作。
JOOQ最令人滿意的就是在實際使用過程中解決問題的靈活性。下面將展示獲取商品(prodcut)和商品評論(comment)總量邏輯。product 和comment 是通過product_id 關聯。
直接上碼
</>復制代碼
List products = dslContext.select()
.from(Tables.PRODUCT)
.leftJoin(DSL.table(
DSL.select(Comment.COMMENT.PRODUCT_ID, DSL.count().as("comment_num"))
.from(Tables.COMMENT)
.where(Comment.COMMENT.PRODUCT_ID.in(ids))
.groupBy(Comment.COMMENT.PRODUCT_ID)
).as("c1")
)
.on(Product.PRODUCT.ID.eq(DSL.field(DSL.name("c1",
Comment.COMMENT.PRODUCT_ID.getName()),UInteger.class)))
.where(Product.PRODUCT.ID.in(ids))
.fetch()
.map(record -> {
MyProduct product = record.into(MyProduct.class);
return product;
});
下面是原生sql的版本
</>復制代碼
select * from `product` as `prod`
left outer join
(select `comment`.`product_id`,count(*) as `comment_num` from `comment`
where `commment`.`product_id`=?
group by `comment`.`product_id`
)
as `c1`
on `prod`.`id`=`c1`.`product_id`
where `prod`.`id`=?;
通過上面代碼的對比可以看出JOOQ既享受了Java封裝帶來的便捷又保留了原生sql的靈活。
目前流行的數據源DHCP和c3p0大家都很熟悉了,沒啥講的。我們的項目使用的是阿里的 Druid,它是一個用于實時查詢和分析的高容錯、高性能開源分布式系統,旨在快速處理大規模的數據,并能夠實現快速查詢和分析。下面就以Druid為例演示把數據源綁定到JOOQ中
添加maven依賴
</>復制代碼
com.alibaba
druid
1.0.20
還是上面的JOOQTest demo,只需要重寫getDSLContext 方法
</>復制代碼
private DSLContext getDSLContext() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/app_maker");
dataSource.setUsername("mars");
dataSource.setPassword("mars");
dataSource.setMaxActive(20);
dataSource.setMaxWait(20_000);
dataSource.setMinIdle(0);
dataSource.setTestOnBorrow(true);
dataSource.setTestWhileIdle(true);
dataSource.setInitialSize(1);
dataSource.setMinEvictableIdleTimeMillis(1000*60*10);
dataSource.setTimeBetweenEvictionRunsMillis(60*1000);
dataSource.setPoolPreparedStatements(true);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
dataSource.setValidConnectionChecker(new MySqlValidConnectionChecker());
ConnectionProvider connectionProvider = new DataSourceConnectionProvider(dataSource)
Configuration configuration = new DefaultConfiguration()
.set(connectionProvider)
.set(SQLDialect.MYSQL);
return DSL.using(configuration);
}
具體Druid配置可以參考官方文檔。
JOOQ 官方提供了 TransactionProvider 對事務的支持,只需要在創建DSLContext的時候設置一下。代碼如下:
</>復制代碼
ConnectionProvider connectionProvider = new DataSourceConnectionProvider(dataSource)
TransactionProvider transactionProvider = new DefaultTransactionProvider(connectionProvider, false);
Configuration configuration = new DefaultConfiguration()
.set(connectionProvider)
.set(transactionProvider)
.set(SQLDialect.MYSQL);
return DSL.using(configuration);
下面展示事務的使用
</>復制代碼
@Test
public void transaction() {
dslContext.transaction(configuration -> {
DSL.using(configuration).update(Tables.STORE)
.set(Store.STORE.ADDRESS, "transaction test1")
.where(Store.STORE.ID.eq(UInteger.valueOf(1)))
.execute();
DSL.using(configuration).update(Tables.STORE)
.set(Store.STORE.ADDRESS, "transaction test1")
.where(Store.STORE.ID.eq(UInteger.valueOf(2)))
.execute();
int i = 1/0;
});
}
沒錯就這么簡單,只需要把需要用事務的代碼包在transaction里面,假如有異常發生,業務會自動回滾。需要注意一點的是必須使用configuration 重新構建context,要不然不會生效,這也是我為什么沒有使用官方提供的事務管理器。正常的項目中一個業務需要組合若干個service 方法來完成,而官方提供的默認事務管理器就需要把所有業務寫在一個方法中,這在實際應用中顯然是不合理的。幸好JOOQ抽象了事務管理,這樣我們就可以集成第三方的事務管理器。
以大家都熟悉的Spring事務管理器為例。添加依賴
</>復制代碼
org.springframework
spring-context
4.1.2.RELEASE
org.springframework
spring-jdbc
4.1.2.RELEASE
</>復制代碼
TransactionAwareDataSourceProxy proxy = new TransactionAwareDataSourceProxy(druidDataSource);
DataSourceTransactionManager txMgr = new DataSourceTransactionManager(druidDataSource);
Configuration configuration = new DefaultConfiguration()
.set(new DataSourceConnectionProvider(proxy))
.set(new SpringTransactionProvider(txMgr))
.set(SQLDialect.MYSQL);
return DSL.using(configuration);
</>復制代碼
public class SpringTransactionProvider implements TransactionProvider {
private static final JooqLogger log = JooqLogger.getLogger(SpringTransactionProvider.class);
DataSourceTransactionManager txMgr;
public SpringTransactionProvider(DataSourceTransactionManager txMgr){
this.txMgr = txMgr;
}
@Override
public void begin(TransactionContext ctx) {
log.debug("Begin transaction");
TransactionStatus tx = txMgr.getTransaction(new DefaultTransactionDefinition());
ctx.transaction(new SpringTransaction(tx));
}
@Override
public void commit(TransactionContext ctx) {
log.debug("commit transaction");
txMgr.commit(((SpringTransaction) ctx.transaction()).tx);
}
@Override
public void rollback(TransactionContext ctx) {
log.debug("rollback transaction");
txMgr.rollback(((SpringTransaction) ctx.transaction()).tx);
}
}
public class SpringTransaction implements Transaction {
final TransactionStatus tx;
SpringTransaction(TransactionStatus tx) {
this.tx = tx;
}
}
集成完后 transaction 測試方法就可以這樣寫了
</>復制代碼
@Test
public void transaction(){
dslContext.transaction(configuration -> {
dslContext.update(Tables.STORE) //共用同一個context
.set(Store.STORE.ADDRESS, "transaction test3")
.where(Store.STORE.ID.eq(UInteger.valueOf(1)))
.execute();
dslContext.update(Tables.STORE)
.set(Store.STORE.ADDRESS, "transaction test4")
.where(Store.STORE.ID.eq(UInteger.valueOf(2)))
.execute();
int i = 1/0;
});
}
JOOQ還有很多其他有意思的特性 如對其他語言的支持,數據導出,存儲過程,JPA支持等等,感興趣的可以參閱一下文檔。說到文檔,不得不說開發者對JOOQ的用心,簡單、詳細、美觀是最直接的感受,并且還有豐富的demo示例,對于編程新手來說上手使用也是手到擒來。
下面我就抱磚引玉,通過demo簡單介紹一下ExecuteListener 的使用。ExecuteListener 可以看作是一個JOOQ執行的觀察者,它可以監控SQL執行的整個生命周期。并且可以通過執行上下文,做一些個性化的操作。下面SlowQueryListener類的作用就是收集sql執行過程的慢查詢日志。
</>復制代碼
class SlowQueryListener extends DefaultExecuteListener {
private Logger logger = LoggerFactory.getLogger(SlowQueryListener.class);
StopWatch watch;
@Override
public void executeStart(ExecuteContext ctx) {
super.executeStart(ctx);
watch = new StopWatch();
}
@Override
public void executeEnd(ExecuteContext ctx) {
try{
super.executeEnd(ctx);
if (watch.split() > 1_000_000_000L) {//記錄執行時間超過1s的操作
ExecuteType type = ctx.type();
StringBuffer sqlBuffer = new StringBuffer();
if(type == ExecuteType.BATCH) {
for(Query query:ctx.batchQueries()) {
sqlBuffer.append(query.toString()).append("
");
}
}else {
sqlBuffer.append(ctx.query() == null ? "blank query ":ctx.query().toString());
}
watch.splitInfo(String.format("Slow SQL query meta executed : [ %s ]",
sqlBuffer.toString() ));
}
}catch (Exception e) {
logger.error(" SlowQueryListener has occur,fix bug ",e);
}
}
}
在初始化DSLContext 的時候把SlowQueryListener配置進去 代碼如下:
</>復制代碼
Configuration configuration = new DefaultConfiguration()
.set(new DataSourceConnectionProvider(proxy))
.set(new SpringTransactionProvider(txMgr))
.set(SQLDialect.MYSQL)
.set(DefaultExecuteListenerProvider.providers(new SlowQueryListener()));//配置執行監聽器
執行時間超過1s的sql,會打印如下日志
</>復制代碼
Slow SQL query meta executed : [ call ama_procedure.ama_app("57a013edaa150a000101ffca") ]: Total: 3.644s
寫在最后
對于在國內占了大半邊天的Hibernate/Mybatis,JOOQ還是一個小清新,很多人對它都還陌生。通過上面的簡單介紹,也許對你有一點幫助。無論是強大的數據轉換能力還是處理業務的靈活性,簡潔性,都會帶來一些不一樣的體驗。如果你已經厭倦了ORMS的開發模式,正好又接手一個新的項目,JOOQ也許是一個不錯的選擇。
作者信息
本文系力譜宿云 LeapCloud旗下MaxLeap團隊_數據服務組 成員:馬傳林【原創】
力譜宿云首發:https://blog.maxleap.cn/archi...
馬傳林,從事開發工作已經有多年。當前在MaxLeap數據服務組擔任開發工程師,主要負責MaxWon服務器開發。
作者往期佳作
移動云平臺的基礎架構之旅(一):云應用
歡迎關注微信公眾號:MaxLeap_yidongyanfa
關于 MaxLeap
官網:https://maxleap.cn/
簡介:MaxLeap 移動業務研發的云服務平臺,為企業提供包括應用開發所需的后端云數據庫、云數據源、云代碼、云容器、 IM、移動支付、應用內社交、第三方登錄、社交分享、數據分析、推送營銷,用戶支持等服務, MaxLeap 致力于讓移動應用開發更快速簡單。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65074.html
摘要:摘要介紹簡單實用,以及相對于傳統框架的不同點。最令人滿意的就是在實際使用過程中解決問題的靈活性。當前在數據服務組擔任開發工程師,主要負責服務器開發。 摘要 介紹JOOQ簡單實用,以及相對于傳統ORM框架的不同點。 showImg(https://segmentfault.com/img/remote/1460000006763840); (圖片來自http://www.jooq.org...
摘要:摘要介紹簡單實用,以及相對于傳統框架的不同點。最令人滿意的就是在實際使用過程中解決問題的靈活性。當前在數據服務組擔任開發工程師,主要負責服務器開發。 摘要 介紹JOOQ簡單實用,以及相對于傳統ORM框架的不同點。 showImg(https://segmentfault.com/img/remote/1460000006763840); (圖片來自http://www.jooq.org...
摘要:不管是還是,表之間的連接查詢,被映射為實體類之間的關聯關系,這樣,如果兩個實體類之間沒有實現關聯關系,你就不能把兩個實體或者表起來查詢。 因為項目需要選擇數據持久化框架,看了一下主要幾個流行的和不流行的框架,對于復雜業務系統,最終的結論是,JOOQ是總體上最好的,可惜不是完全免費,最終選擇JDBC Template。 Hibernate和Mybatis是使用最多的兩個主流框架,而JOO...
摘要:在這個例子中,我們將整合但您也可以使用其他連接池,如,,等。作為構建和執行。 jOOQ和Spring很容易整合。 在這個例子中,我們將整合: Alibaba Druid(但您也可以使用其他連接池,如BoneCP,C3P0,DBCP等)。 Spring TX作為事物管理library。 jOOQ作為SQL構建和執行library。 一、準備數據庫 DROP TABLE IF EXIS...
閱讀 1467·2021-09-02 13:57
閱讀 1883·2019-08-30 15:55
閱讀 2420·2019-08-30 15:54
閱讀 2260·2019-08-30 15:44
閱讀 2742·2019-08-30 13:18
閱讀 491·2019-08-30 13:02
閱讀 661·2019-08-29 18:46
閱讀 1673·2019-08-29 11:25
极致性价比!云服务器续费无忧!
Tesla A100/A800、Tesla V100S等多种GPU云主机特惠2折起,不限台数,续费同价。
NVIDIA RTX 40系,高性价比推理显卡,满足AI应用场景需要。
乌兰察布+上海青浦,满足东推西训AI场景需要