话说企业应用,一般离不开数据库。要做数据库,可以有N种方案,比如:直接采用JDBC层自己封装下使用的,采用一些框架的,如:iBatis,Hiberate,Spring JDBC Template等等(这个太多了,因此不一一列举)的,这些方案也都在各自的领域展示了自己的特点,解决了相当部分的技术问题,并取得了相当好的应用效果。
但是不管是哪种方案,其优点和缺点往往也是连在一起的,究其原因是因为SQL和Java编程之间是割裂的,如果封装得不到位,做Java的人太难使用;如果封装得太多,在做一些用复杂SQL的时候又非常麻烦。比如:Hibernate就采用了封装HQL的方式来解决这方面的问题。iBatis对于SQL支持比较好,但是又会有一些割裂感,同时在解决时还要引入动态SQL来解决需要根据一些运行时条件来处理的问题,一定程度上又增加了使用的复杂度。
那么问题就来了,有没有更好的方式来解决数据库应用开发过程中的问题呢?究其根本原因是要如何解决数据库开发中的SQL与Java代码之间的割裂问题,如果能把这个问题解决掉,理论上会有一个不错的解。
我们知道SQL实际是是一种数据为领域的DSL语言,如果我们能直接在Java中编写SQL,然后执行结果就可以直接返回Java对象,这个问题不就有了良好的解决方案么?
实际上这方面已经有一些现成的解决方案,但是有的不是开源的,有的支持的还不是非常到位,因此悠然就决定尝试着写一下,写了半天时间看了看效果,详见RESTful风格的支持实践一文,内部讨论了一下,感觉还不错,于是正式决定正式花时间来编写一个TinySqlDsl,当然实际编写的时候,还是有许多的问题点的,以至于最终的风格与上面的文章还有一些不一致,当然这也是正常的,容易理解的,否则那什么也太神了。
我们常见的SQL语句有Select、Insert、Update、Delete,因此我们的方案中也实现了这几个语句的编写方式。
首先来看看看TinySqlDsl版的Dao是怎么写的。
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"); } }
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的了。
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要返回的类型是什么。
A:必须支持,不管是Union,子查询,各种连接都可以支持
A:必须支持,不管是拼SQL语句分页的还是SQL默认就支持分页的,都可以支持
A:不用,对于没有给值的条件,框架会自动忽略此条件,所以你只要写一个大而全的就可以了。
A:必须支持,所有的函数都可以使用,只是如果写了与某种数据库相关的函数,跨数据库时将不再有兼容性。
A:必须支持,不管是多表联合查询还是子查询啥的,全都支持。
好像没有啥不支持的,只有写得漂亮不漂亮的,没有支持不支持的。由于支持自已编写SQL片断,因此理论上你可以用SQL片断完成所有的事情,只是看起来不够漂亮而已。
如果使用Tiny元数据管理数据表,那么只要在工具中如下操作,即可自动生成POJO、表定义、及Dao层代码实现:
也就是只要选中表定义文件,选择右键->TinyStudio->生成DSL JAVA类,就可以自动生成Dao层的所有代码,如果需要可以对生成的类进行修改或扩展,但是一般情况下都足够使用了。
如果没有使用Tiny的元数据,那么可以自己写个工具类来生成这几个类,也可以手工编写,也可以分分钟编写出来。
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;
任意一个方案都有它的优点,也有它的缺点,TinySqlDsl也不例外,这里简单的分析一下,如果不全面,请同学们下面补充,先谢谢了。
目前,我们内部进行了试用,整体运行效果良好,后面准备主力推这种方式。
关心代码的同学,可以查看下面的URL:http://git.oschina.net/tinyframework/tiny/tree/master/db/org.tinygroup.tinysqldsl
亲,你有什么意见、建议,请告诉我们吧!