文档章节

像写SQL一样编写Java数据应用-TinySqlDsl

悠悠然然
 悠悠然然
发布于 2015/05/04 17:41
字数 2578
阅读 7253
收藏 161

前言

话说企业应用,一般离不开数据库。要做数据库,可以有N种方案,比如:直接采用JDBC层自己封装下使用的,采用一些框架的,如:iBatis,Hiberate,Spring JDBC Template等等(这个太多了,因此不一一列举)的,这些方案也都在各自的领域展示了自己的特点,解决了相当部分的技术问题,并取得了相当好的应用效果。

但是不管是哪种方案,其优点和缺点往往也是连在一起的,究其原因是因为SQL和Java编程之间是割裂的,如果封装得不到位,做Java的人太难使用;如果封装得太多,在做一些用复杂SQL的时候又非常麻烦。比如:Hibernate就采用了封装HQL的方式来解决这方面的问题。iBatis对于SQL支持比较好,但是又会有一些割裂感,同时在解决时还要引入动态SQL来解决需要根据一些运行时条件来处理的问题,一定程度上又增加了使用的复杂度。

那么问题就来了,有没有更好的方式来解决数据库应用开发过程中的问题呢?究其根本原因是要如何解决数据库开发中的SQL与Java代码之间的割裂问题,如果能把这个问题解决掉,理论上会有一个不错的解。

我们知道SQL实际是是一种数据为领域的DSL语言,如果我们能直接在Java中编写SQL,然后执行结果就可以直接返回Java对象,这个问题不就有了良好的解决方案么?

TinySqlDsl解决方案

实际上这方面已经有一些现成的解决方案,但是有的不是开源的,有的支持的还不是非常到位,因此悠然就决定尝试着写一下,写了半天时间看了看效果,详见RESTful风格的支持实践一文,内部讨论了一下,感觉还不错,于是正式决定正式花时间来编写一个TinySqlDsl,当然实际编写的时候,还是有许多的问题点的,以至于最终的风格与上面的文章还有一些不一致,当然这也是正常的,容易理解的,否则那什么也太神了。

我们常见的SQL语句有Select、Insert、Update、Delete,因此我们的方案中也实现了这几个语句的编写方式。

首先来看看看TinySqlDsl版的Dao是怎么写的。

第一步:定义POJO

public class Custom {
	
	private String id;
	
	private String name;
	
	private int age;

	public String getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public int getAge() {
		return age;
	}

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

第二步:定义表结构定义文件

public class CustomTable extends Table {
    public static final CustomTable CUSTOM = new CustomTable();
    public final Column ID = new Column(this, "id");
    public final Column NAME = new Column(this, "name");
    public final Column AGE = new Column(this, "age");

    private CustomTable() {
        super("custom");
    }
}

第三步:编写DAO类

public class CustomDao {
	private DslSession dslSession;

	public DslSession getDslSession() {
		return dslSession;
	}

	public void setDslSession(DslSession dslSession) {
		this.dslSession = dslSession;
	}

	public void insertCustom(Custom custom) {
		dslSession.execute(
			insertInto(CUSTOM).values(
				CUSTOM.ID.value(custom.getId()),
				CUSTOM.NAME.value(custom.getName()),
				CUSTOM.AGE.value(custom.getAge())
			)
		);
	}

	public void updateCustom(Custom custom) {
		dslSession.execute(
			update(CUSTOM).set(
				CUSTOM.NAME.value(custom.getName()),
				CUSTOM.AGE.value(custom.getAge())).where(
				CUSTOM.ID.eq(custom.getId())
			)
		);
	}

	public void deleteCustom(String id) {
		dslSession.execute(
				delete(CUSTOM).where(
						CUSTOM.ID.eq(id)
				)
		);
	}

	public Custom getCustomById(String id) {
		return dslSession.fetchOneResult(
			selectFrom(CUSTOM).where(
					CUSTOM.ID.eq(id)
			)
		, Custom.class);
	}

	public List<Custom> queryCustom(Custom custom) {
		return dslSession.fetchList(
			selectFrom(CUSTOM).where(
				and(
						CUSTOM.ID.eq(custom.getId()),
						CUSTOM.NAME.equal(custom.getName()),
						CUSTOM.AGE.equal(custom.getAge())
				)
			)
		, Custom.class);
	}
}
看了上面的示例,会不会感觉有点奇怪,怎么可以这么写?呵呵,先别着急了解实际的实现机理,我们先品味一下这种DSL风格的数据库编写方式,嗯嗯,具体的来说就是像写SQL一样的方式来写SQL。

代码说明

每个数据表都要有两个类进行映射,一个是POJO类,这个大家都非常熟悉就不再花时间进行说明了,用于构建Dao代码的时候使用。另一个是表结构,用于在Java中定义数据库的表结构。

public class CustomTable extends Table {
    public static final CustomTable CUSTOM = new CustomTable();
    public final Column ID = new Column(this, "id");
    public final Column NAME = new Column(this, "name");
    public final Column AGE = new Column(this, "age");

    private CustomTable() {
        super("custom");
    }
}
这个类主要由如下几部分组成:

CustomTable对应于一个表结构类型,它继承自Table类。

构造函数,中的super("custom")使之与数据库的表名进行映射。

public static final CustomTable CUSTOM = new CustomTable();这句定义了一个常量CUSTOM,对应于具有的表,它的用得中在DSL语法用要用到表的时候使用

这个类里定义了3个public成员变量,这些成员变量和具体的字段数相对应,表里有几个字段,这里就定义几个字段,这个实例化自Column。

OK,这样表结构的定义就做好了。

正因为有了上面的定义,才可以在Dao中用Java代码像SQL一样的编写程序,但是这些语句是怎么才能执行出结果的呢?这就要看DslSession的了。

DslSesssion

DslSession是与数据库打交道的类,说白了,它就是一个SQL执行器。

public interface DslSession {
    /**
     * 执行Insert语句关返回
     *
     * @param insert
     * @return
     */
    int execute(Insert insert);

    /**
     * 执行更新语句
     *
     * @param update
     * @return
     */
    int execute(Update update);

    /**
     * 执行删除语句
     *
     * @param delete
     * @return
     */
    int execute(Delete delete);

    /**
     * 返回一个结果,既然是有多个结果也只返回第一个结果
     *
     * @param select
     * @param requiredType
     * @param <T>
     * @return
     */
    <T> T fetchOneResult(Select select, Class<T> requiredType);

    /**
     * 把所有的结果变成一个对象数组返回
     *
     * @param select
     * @param requiredType
     * @param <T>
     * @return
     */
    <T> T[] fetchArray(Select select, Class<T> requiredType);

    /**
     * 把所有的结果变成一个对象列表返回
     *
     * @param select
     * @param requiredType
     * @param <T>
     * @return
     */
    <T> List<T> fetchList(Select select, Class<T> requiredType);
    
    /**
     * 返回一个结果,既然是有多个结果也只返回第一个结果
     *
     * @param complexSelect
     * @param requiredType
     * @param <T>
     * @return
     */
    <T> T fetchOneResult(ComplexSelect complexSelect, Class<T> requiredType);

    
    /**
     * 把所有的结果变成一个对象数组返回
     *
     * @param complexSelect
     * @param requiredType
     * @param <T>
     * @return
     */
    <T> T[] fetchArray(ComplexSelect complexSelect, Class<T> requiredType);

    /**
     * 把所有的结果变成一个对象列表返回
     *
     * @param complexSelect
     * @param requiredType
     * @param <T>
     * @return
     */
    <T> List<T> fetchList(ComplexSelect complexSelect, Class<T> requiredType);

}
它的方法也比较简单,主要功能就是执行这几个语句。正是由于把复杂的SQL都封装到了Insert、Select、Update、Delete当中,因此这个执行器的接口方法反而是非常的简单,正因为它太简单了,因此根本就不需要介绍。仅仅要说明的是,当Select的时候,需要指定返回的类型,以便于告诉DslSession要返回的类型是什么。

Q&A

Q:是不是支持复杂的SQL?

A:必须支持,不管是Union,子查询,各种连接都可以支持

Q:是不是支持分页?

A:必须支持,不管是拼SQL语句分页的还是SQL默认就支持分页的,都可以支持

Q:你这个SQL条件一路写下来,是不是需要所有的条件都必须存在?

A:不用,对于没有给值的条件,框架会自动忽略此条件,所以你只要写一个大而全的就可以了。

Q:是不是支持数据库中的函数?

A:必须支持,所有的函数都可以使用,只是如果写了与某种数据库相关的函数,跨数据库时将不再有兼容性。

Q:是不是支持多表关联查询?

A:必须支持,不管是多表联合查询还是子查询啥的,全都支持。

Q:有啥不支持的不?

好像没有啥不支持的,只有写得漂亮不漂亮的,没有支持不支持的。由于支持自已编写SQL片断,因此理论上你可以用SQL片断完成所有的事情,只是看起来不够漂亮而已。

应用实践

支持类编写

使用Tiny元数据开发

如果使用Tiny元数据管理数据表,那么只要在工具中如下操作,即可自动生成POJO、表定义、及Dao层代码实现:

也就是只要选中表定义文件,选择右键->TinyStudio->生成DSL JAVA类,就可以自动生成Dao层的所有代码,如果需要可以对生成的类进行修改或扩展,但是一般情况下都足够使用了。

自行编写或生成

如果没有使用Tiny的元数据,那么可以自己写个工具类来生成这几个类,也可以手工编写,也可以分分钟编写出来。

DAO编写注意事项

import static org.tinygroup.tinysqldsl.CustomTable.CUSTOM;
import static org.tinygroup.tinysqldsl.Delete.delete;
import static org.tinygroup.tinysqldsl.Insert.insertInto;
import static org.tinygroup.tinysqldsl.Select.selectFrom;
import static org.tinygroup.tinysqldsl.base.StatementSqlBuilder.and;
import static org.tinygroup.tinysqldsl.Update.update;



这里用到一个技巧,就是通过静态引入这些要用到的语句或表定义,这样才可以方便的编写DSL格式的语句。

优缺点对比

任意一个方案都有它的优点,也有它的缺点,TinySqlDsl也不例外,这里简单的分析一下,如果不全面,请同学们下面补充,先谢谢了。

优点

  1. 熟悉SQL的同学,上手非常方便,可以说熟悉SQL的同学,可以非常快的上手,甚至不会Java都可以快速编写
  2. 即时提示效果非常好,所有的IDE都提供的语法提示,使得编写SQL时,对于表结构不必再记得一清二楚,第一编写速度快许多,第二不用担心拼写错误而花费大量的调试时间
  3. SQL的构建和Java的处理一体化完成,开发过程不必两个部分先分开再分离
  4. 完美的解决动态SQL方面的问题,不需要复杂的配置,不需要复杂的处理,一切浑然天成
  5. 像写SQL一样写Java数据库业务代码

缺点

  1. 这种方式毕竟和写SQL还是有一点区别,需要花一点时间熟悉
  2. 更多的亲们在下面补充

总结

目前,我们内部进行了试用,整体运行效果良好,后面准备主力推这种方式。

关心代码的同学,可以查看下面的URL:http://git.oschina.net/tinyframework/tiny/tree/master/db/org.tinygroup.tinysqldsl

亲,你有什么意见、建议,请告诉我们吧!

© 著作权归作者所有

悠悠然然

悠悠然然

粉丝 2471
博文 185
码字总数 363071
作品 14
杭州
架构师
私信 提问
加载中

评论(59)

马进举
马进举
是一种新思路
悠悠然然
悠悠然然 博主

引用来自“淡茗”的评论

和querydsl的用法很像,喜欢querydsl

7979
悠悠然然
悠悠然然 博主

引用来自“TuWei”的评论

#Nutz# 框架dao操作就这么写的

56
淡茗
淡茗
和querydsl的用法很像,喜欢querydsl
TuWei
TuWei
#Nutz# 框架dao操作就这么写的
悠悠然然
悠悠然然 博主

引用来自“蛋蛋娃”的评论

这个与JOOQ有相似..相似度85%..感觉是参考了JOOQ..
用JAVA 方法拼接SQL 代替字符串. 感觉很"恶心"
与其大量的的JAVA代码 拼接SQL。不如直接写字符串拼接.SQL.
JdbcTemplate做的就很简洁.返回Map 或 实体都OK.
可以脑部一下JOIN多张表的情况。用JAVA拼接很恶心
还不如:
String sql = "SELECT * FROM a "
+ " INNER JOIN b ON (b.a = a.id)"
+ " INNER JOIN c ON (c.id = b.id) "
+ " WHERE a.name = ? AND a.id > ?"
;
这个方式也方便直接在 SQL工具上回切换..写好SQL调试OK到直接COPY到JAVA。 JAVA出错后也可以直接COPY 替换+" 到SQL工具调试。
用JAVA方式拼接。在SQL工具上写了。还要转换成JAVA方式拼接。感觉恶心。

如果想把JAVA 拼接直接转化成SQL.又需要运行JAVA代码。在用“format方法”格式化SQL 才能调试、
这个与hibernate SQL难调试的问题如出一辙。。


假如SQL查询比较复杂。。看见大量JAVA"拼接方法",简直就是密集恐惧症

引用来自“生如秋叶”的评论

这素质就让人不想与之好好讨论了。不是客观去比较,去阐述观点,张嘴就喷,还喷的毫无营养

引用来自“蛋蛋娃”的评论

我不知道你说的营养是什么...我只是阐述了观点。当然是作为“反对方”阐述的。 关于“喷”里面字眼我估计可能就是用到了 “恶心”吧? 使用SQL不方便。调试SQL不方便。我想第一般需要用到的SQL的地方都是写好SQL后。在SQL工具上调试OK了。在写到JAVA上的把??如果用JAVA方式拼接.我把这个“过程”归结为恶心。不为过吧? 怎么就牵扯素质问题了??动不动就谈素质?? BTW.你查看作者写的测试代码没有??

引用来自“悠悠然然”的评论

亲,那动态SQL的问题怎么处理?

引用来自“蛋蛋娃”的评论

笨一点可以。直接IF 拼接条件。 好一点可以在字符上做手脚 如 "SELECT * FROM a WHERE 1=1 { AND a.name = :name} " 参数传MAP 判断map中 name是否存在。如果不存在。直接删除 {a.name = :name} 这样SQL污染少。基本上原滋原味。。对于JAVA拼接SQL。基本上都会出现 数据库扩展(native sql 该数据库独有功能或语法.).支持不全的情况..JOOQ也用类似“方言”来处理..效果也不理想

引用来自“生如秋叶”的评论

本质上说,这就是把SQL语句结构化,并转化为OO语言中的结构和对象。在动态SQL方面,甚至可以实现类似AOP的处理方式,动态注入过滤条件
对的,方言化太严重就会导致不方便使用和兼容。 怎么优雅的处理这些条件还是非常有讲究的。
悠悠然然
悠悠然然 博主

引用来自“生如秋叶”的评论

首先声明,本人是做IT咨询的,开发属于业余爱好,所以也不太懂什么DSL、DAO,就贴一下我的数据操作层代码吧。
var cmd=Host.ORM.DB.Begin().Select<SoTask>().As("TT");
cmd.Where("Status").Uneq(BizObjLifeStatus.Completed)
.And("IsDeleted").Eq(false)
.AscBy("Deadline")
.DescBy("Importance")
.Join<BORelation>("").As("TR")
.Where("SrcID").Eq("TT.ID".AsRef())
.And("RelationType").Eq("Charger")
.Join<Account>("TA.UserName AS Charger").As("TA")
.And("ID").Eq (uid)
.Join<BORelation>("").As("TR2")
.Where("SrcID").Eq("TT.ID".AsRef())
.And("RelationType").Eq("Creater")
.Join<Account>("TA2.UserName AS Creater").As("TA2")
.Where("ID").Eq("TR2.ObjID".AsRef());

引用来自“悠悠然然”的评论

嗯嗯,大致的思想是差不多的,就是用字符串做字段名和表名有点难用,毕竟不是每个人都熟练记忆的

引用来自“生如秋叶”的评论

恩,我认为只要在灵活与效率间达到一个平衡就好了。基于‘约定大于配置’的思想,数据库表字段名就是代码中的类字段名。表名则是在配置文件中定义类与表名的对应。
嗯嗯,我一直喜欢说的一点是品字,所以把几种写法放一起,对比,品味,可能慢慢就会理解其中的不同。
生如秋叶
生如秋叶

引用来自“蛋蛋娃”的评论

这个与JOOQ有相似..相似度85%..感觉是参考了JOOQ..
用JAVA 方法拼接SQL 代替字符串. 感觉很"恶心"
与其大量的的JAVA代码 拼接SQL。不如直接写字符串拼接.SQL.
JdbcTemplate做的就很简洁.返回Map 或 实体都OK.
可以脑部一下JOIN多张表的情况。用JAVA拼接很恶心
还不如:
String sql = "SELECT * FROM a "
+ " INNER JOIN b ON (b.a = a.id)"
+ " INNER JOIN c ON (c.id = b.id) "
+ " WHERE a.name = ? AND a.id > ?"
;
这个方式也方便直接在 SQL工具上回切换..写好SQL调试OK到直接COPY到JAVA。 JAVA出错后也可以直接COPY 替换+" 到SQL工具调试。
用JAVA方式拼接。在SQL工具上写了。还要转换成JAVA方式拼接。感觉恶心。

如果想把JAVA 拼接直接转化成SQL.又需要运行JAVA代码。在用“format方法”格式化SQL 才能调试、
这个与hibernate SQL难调试的问题如出一辙。。


假如SQL查询比较复杂。。看见大量JAVA"拼接方法",简直就是密集恐惧症

引用来自“生如秋叶”的评论

这素质就让人不想与之好好讨论了。不是客观去比较,去阐述观点,张嘴就喷,还喷的毫无营养

引用来自“蛋蛋娃”的评论

我不知道你说的营养是什么...我只是阐述了观点。当然是作为“反对方”阐述的。 关于“喷”里面字眼我估计可能就是用到了 “恶心”吧? 使用SQL不方便。调试SQL不方便。我想第一般需要用到的SQL的地方都是写好SQL后。在SQL工具上调试OK了。在写到JAVA上的把??如果用JAVA方式拼接.我把这个“过程”归结为恶心。不为过吧? 怎么就牵扯素质问题了??动不动就谈素质?? BTW.你查看作者写的测试代码没有??

引用来自“悠悠然然”的评论

亲,那动态SQL的问题怎么处理?

引用来自“蛋蛋娃”的评论

笨一点可以。直接IF 拼接条件。 好一点可以在字符上做手脚 如 "SELECT * FROM a WHERE 1=1 { AND a.name = :name} " 参数传MAP 判断map中 name是否存在。如果不存在。直接删除 {a.name = :name} 这样SQL污染少。基本上原滋原味。。对于JAVA拼接SQL。基本上都会出现 数据库扩展(native sql 该数据库独有功能或语法.).支持不全的情况..JOOQ也用类似“方言”来处理..效果也不理想
本质上说,这就是把SQL语句结构化,并转化为OO语言中的结构和对象。在动态SQL方面,甚至可以实现类似AOP的处理方式,动态注入过滤条件
悠悠然然
悠悠然然 博主

引用来自“247687009”的评论

意义何在,哗众取宠!
建议给出结论之后,再来点论点,论据啥的就更有说服力了。 实际上,DSL方式的数据开发方式,我这里不是新创,也不是独创,只是看到类似的有实现,而且觉得自己可以在某些方面实现得更好一些,因此尝试着做自己的一个实践。 你这一棒子打下去,打倒一大批sqlDSL相关的实践,也打倒了一大船对这种方式有一定认可的同学,杀伤力有点太大,慎之慎之。
生如秋叶
生如秋叶

引用来自“生如秋叶”的评论

首先声明,本人是做IT咨询的,开发属于业余爱好,所以也不太懂什么DSL、DAO,就贴一下我的数据操作层代码吧。
var cmd=Host.ORM.DB.Begin().Select<SoTask>().As("TT");
cmd.Where("Status").Uneq(BizObjLifeStatus.Completed)
.And("IsDeleted").Eq(false)
.AscBy("Deadline")
.DescBy("Importance")
.Join<BORelation>("").As("TR")
.Where("SrcID").Eq("TT.ID".AsRef())
.And("RelationType").Eq("Charger")
.Join<Account>("TA.UserName AS Charger").As("TA")
.And("ID").Eq (uid)
.Join<BORelation>("").As("TR2")
.Where("SrcID").Eq("TT.ID".AsRef())
.And("RelationType").Eq("Creater")
.Join<Account>("TA2.UserName AS Creater").As("TA2")
.Where("ID").Eq("TR2.ObjID".AsRef());

引用来自“凡行”的评论

和直接写SQL比较,这样做是基于什么样的考虑?

引用来自“生如秋叶”的评论

最主要是不喜欢代码里出现大段的字符串,调试太痛苦了。其次,这样我可以根据其他条件灵活决定是否添加某些关联、或者查询条件;再次,类似链式调用的风格,编写起来很爽;

引用来自“悠悠然然”的评论

我觉得只要道理上讲得通,使用者不反感,最起码就是一个可用的解。
确实,我也完全是出于自己的需要。因为从来没搞过数据库开发,对SQL也很不熟练,而C#曾用的熟练很多
像写SQL一样编写Java数据应用-TinySqlDsl

mark一下,一种新思路 像写SQL一样编写Java数据应用-TinySqlDsl

z_jordon
2015/05/04
104
0
C# vs Java:C# 五个不可替代的特性瞬间秒杀 Java

如果我们可以同时拥有 C# 和 Java 世界的最好特性,那会是什么样呢? 完美的编程语言并不存在,我希望我们可以在这一点上达成一致。开发新语言往往是为了克服另一种语言的弊端,又不可避免的...

oschina
2017/08/10
2.6K
2
Gradle入门(一)--Groovy常用语法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 https://blog.csdn.net/zly921112/article/details/90479841 概述 Groovy是一种可以用于构建...

zhuliyuan丶
05/23
0
0
介绍Hrorm:一个简单的,声明式的,经过类型检查的ORM

一个问题 关于将Java代码中的模型与关系(SQL)数据库中的模型连接的问题,已经有很多网络墨水泄露了。对象关系映射(ORM)的主题确实很丰富,您可能想要在应用程序和数据库之间传输信息的全...

萤火的萤
01/10
0
0
好程序员Java教程解读JDBC是什么

  好程序员解读JDBC是什么,JDBC简介- JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的...

好程序员IT
07/25
47
0

没有更多内容

加载失败,请刷新页面

加载更多

【0918】正则介绍_grep

【0918】正则介绍_grep 9.1 正则介绍_grep上 9.2 grep中 9.3 grep下 一、正则介绍 正则是一串有规律的字符串,它使用单个字符串来描述或匹配一系列符合某个语法规则的字符串。 二、grep工具 ...

飞翔的竹蜻蜓
12分钟前
4
0
为什么要在网站中应用CDN加速?

1. 网页加载速度更快 在网站中使用CDN技术最直接的一个好处就是它可以加快网页的加载速度。首先,CDN加速的内容分发是基于服务器缓存的,由于CDN中缓存了不少数据,它能够给用户提供更快的页...

云漫网络Ruan
50分钟前
8
0
亚玛芬体育(Amer Sports)和信必优正式启动合作开发Movesense创新

亚玛芬体育和信必优正式启动合作开发Movesense创新,作为亚玛芬体育的完美技术搭档,信必优利用Movesense传感器技术为第三方开发移动应用和服务。 Movesense基于传感器技术和开放的API,测量...

symbiochina88
今天
4
0
创龙TI AM437x ARM Cortex-A9 + Xilinx Spartan-6 FPGA核心板规格书

SOM-TL437xF是一款广州创龙基于TI AM437x ARM Cortex-A9 + Xilinx Spartan-6 FPGA芯片设计的核心板,采用沉金无铅工艺的10层板设计,适用于高速数据采集和处理系统、汽车导航、工业自动化等领...

Tronlong创龙
今天
4
0
好程序员Java学习路线分享MyBatis之线程优化

  好程序员Java学习路线分享MyBatis之线程优化,我们的项目存在大量用户同时访问的情况,那么就会出现大量线程并发访问数据库,这样会带来线程同步问题,本章我们将讨论MyBatis的线程同步问...

好程序员官方
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部