介绍JOOQ简单实用,以及相对于传统ORM框架的不一样点。java
(图片来自http://www.jooq.org/)mysql
JOOQ 是基于Java访问关系型数据库的工具包,轻量,简单,而且足够灵活,能够轻松的使用Java面向对象语法来实现各类复杂的sql。对于写Java的码农来讲ORMS再也熟悉不过了,不论是Hibernate或者Mybatis,都能简单的使用实体映射来访问数据库。但有时候这些 ‘智能’的对象关系映射又显得笨拙,没有直接使用原生sql来的灵活和简单,并且对于一些如:joins,union, nested selects等复杂的操做支持的不友好。JOOQ 既吸收了传统ORM操做数据的简单性和安全性,又保留了原生sql的灵活性,它更像是介于 ORMS和JDBC的中间层。对于喜欢写sql的码农来讲,JOOQ能够彻底知足你控制欲,能够是用Java代码写出sql的感受来。就像官网说的那样 :git
get back in control of your sqlgithub
JOOQ 目前在国内仍是很小众,第一次据说这玩意仍是经过stream 大神的推荐。对于从SSH成长起来的猿类来讲,内心也会质疑 “这玩意用的人那么少,靠不靠谱” ,“会不会有不少坑要踩”。经过对着官方文档写了几个demo,顿时心生敬畏,一个念头冲到脑壳 " 这东西必定会火",因而果断在项目中使用。在使用过程当中也会遇到各类小问题,经过帮助手册和DEMO都能最终解决。相对于Hibernate或者其余ORMS的,JOOQ的编程模式有很大不一样,强大的Fluent API使用起来很是方便和流畅。如今咱们的项目(MaxWon)使用JOOQ已经在生产环境运行了很长的一段时间,历来没花太多时间折腾在数据访问层上面。对于开发来讲感觉最深的就是这货真的很简单很灵活,正如文章标题那样,这是一个‘杀器’。下面是我总结的几点,我的愚见。web
DSL(Domain Specific Language )风格,代码够简单和清晰。遇到不会写的sql能够充分利用IDEA代码提示功能轻松完成。spring
保留了传统ORM 的优势,简单操做性,安全性,类型安全等。不须要复杂的配置,而且能够利用Java 8 Stream API 作更加复杂的数据转换。sql
支持主流的RDMS和更多的特性,如self-joins,union,存储过程,复杂的子查询等等。shell
丰富的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依赖配置以下:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq</artifactId> <version>${jooq.version}</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq-meta</artifactId> <version>${jooq.version}</version> </dependency> <dependency> <groupId>org.jooq</groupId> <artifactId>jooq-codegen</artifactId> <version>${jooq.version}</version> </dependency>
目前官方提供了经过 java org.jooq.util.GenerationTool
来生成映射代码,但过程仍是有点繁琐,这里就不演示了。还好万能的maven插件帮助咱们解决了这个问题。
<profiles> <profile> <id>jooq</id> <properties /> <activation> <property> <name>jooq</name> </property> </activation> <build> <plugins> <plugin> <groupId>org.jooq</groupId> <artifactId>jooq-codegen-maven</artifactId> <version>${jooq.version}</version> <executions> <execution> <goals> <goal>generate</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> </dependencies> <configuration> <jdbc> <driver>${jdbc.driver}</driver> <url>${jdbc.url}</url> <user>${jdbc.user}</user> <password>${jdbc.password}</password> </jdbc> <generator> <database> <name>org.jooq.util.mysql.MySQLDatabase</name> <includes>.*</includes> <excludes /> <inputSchema>${jdbc.database.name}</inputSchema> <forcedTypes> <forcedType> <name>BOOLEAN</name> <types>(?i:TINYINT(\s*\(\d+\))?(\s*UNSIGNED)?)</types> </forcedType> </forcedTypes> </database> <generate> <deprecated>false</deprecated> </generate> <target> <packageName>com.maxleap.jooq.data.jooq</packageName> <directory>src/main/java</directory> </target> <generate> <pojos>false</pojos> <daos>false</daos> </generate> </generator> </configuration> </plugin> </plugins> </build> </profile> </profiles>
配置目标数据库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<MyProduct> 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依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.20</version> </dependency>
仍是上面的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事务管理器为例。添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.1.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.1.2.RELEASE</version> </dependency>
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("\n"); } }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 致力于让移动应用开发更快速简单。