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

简介:

前言

话说企业应用,一般离不开数据库。要做数据库,可以有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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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;
     }
     
}

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

?
1
2
3
4
5
6
7
8
9
10
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类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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中定义数据库的表结构。

?
1
2
3
4
5
6
7
8
9
10
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执行器。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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编写注意事项

?
1
2
3
4
5
6
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

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

相关文章
|
11天前
|
Java 程序员 容器
Java中的变量和常量:数据的‘小盒子’和‘铁盒子’有啥不一样?
在Java中,变量是一个可以随时改变的数据容器,类似于一个可以反复打开的小盒子。定义变量时需指定数据类型和名称。例如:`int age = 25;` 表示定义一个整数类型的变量 `age`,初始值为25。 常量则是不可改变的数据容器,类似于一个锁死的铁盒子,定义时使用 `final` 关键字。例如:`final int MAX_SPEED = 120;` 表示定义一个名为 `MAX_SPEED` 的常量,值为120,且不能修改。 变量和常量的主要区别在于变量的数据可以随时修改,而常量的数据一旦确定就不能改变。常量主要用于防止意外修改、提高代码可读性和便于维护。
|
11天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
20天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
11天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
29 2
|
11天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
21 2
|
19天前
|
SQL 数据库
如何应用SQL约束条件?
【10月更文挑战第28天】如何应用SQL约束条件?
36 11
|
13天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
28 3
|
18天前
|
SQL 存储 缓存
SQL Server 数据太多如何优化
11种优化方案供你参考,优化 SQL Server 数据库性能得从多个方面着手,包括硬件配置、数据库结构、查询优化、索引管理、分区分表、并行处理等。通过合理的索引、查询优化、数据分区等技术,可以在数据量增大时保持较好的性能。同时,定期进行数据库维护和清理,保证数据库高效运行。
|
16天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
38 2
|
21天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
35 3
下一篇
无影云桌面