当前的状况
一般做数据库相关开发, 除非学习, 否则很少有人愿意直接使用JDBC。本来Java代码就比较啰嗦了,而直接用JDBC写代码之啰嗦简直有些令人发狂!所以在实际开发过程中,我们通常都会使用一些框架/库来帮助我们操作数据库。而且开源市场上的选择也比较多,就我个人接触到的有:Hibernate,MyBatis,JdbcTemplate,DbUtils,ActiveRecord,JavaLite等等。 这些框架都能大幅的提高开发效率,对于一些基本CRUD操作来说,虽然各有差异,但总的来说基本是够用了。
然而对于稍微复杂点的数据查询来说,总免不了需要手工编写SQL代码,甚至还需要根据参数来动态拼接SQL。各种框架基本上都有一套自己拼接动态SQL的方案,也都能很轻松的将查询出来的数据转为对象(DTO)。
不过到目前为止,这些框架虽然能够很轻松的帮助我们完成数据的映射,但是这些DTO还得需要我们手工一个个的去编写。
存在的问题
通常我们在写完SQL的查询代码后, 需要有一个对应的DTO,将数据库中查询出的数据映射到DTO,以便于调用的程序能够更好的使用这些数据。当然,为了省事,有时也会把数据直接存储在像Map这样的数据结构中。不过, Map这种方式虽然很轻便,但是会带来几个比重要的潜在问题:
-
调用者需要记住Map里面每个key的名称,这就会给程序员带来一些所谓的记忆负担
-
过重的记忆负担,就会导致系统的逻辑复杂,理解困难,维护更困难
-
SQL更改导致Key发生变化后,很难发现问题,需要程序员非常小心的处理这些更改
如果想要避免Map带来的这些问题,我们需要为每个SQL查询都单独编写DTO。尽管书写这些DTO并没有什么难度,但是非常枯燥乏味,特别是字段很多的时候更是如此;并且,如果SQL查询的字段出现更改,也还是要记得回来修改这个DTO。单独编写DTO虽然减轻了Map带来的部分问题,同时也额外增加了新的工作量。
如果有一种方法能够在SQL代码(包括动态拼接的SQL)编写完成后,就自动的做到下面2点就非常完美了:
-
根据SQL代码,直接生成对应的DTO
-
变更SQL代码,自动修改对应的DTO
这样,一方面解决了手工书写DTO的麻烦; 另一方面,当修改SQL导致某个字段发生更改时, 由于自动生成的DTO也会同步修改,在那些引用到这个字段的地方,编译器就会立即给出错误提示! 使得问题一产生就能立即被发现,这样可以避免了很多潜在的问题。
本文正是试图要解决如何根据SQL代码自动生成DTO的问题,省去手工编写的麻烦,提高程序员的开发效率。
解决的思路
理想总是很美好,现实总是很残酷!
那么,到底能否实现这个想法呢,我们首先来初步分析一下自动产生DTO的可行性:
要实现自动产生DTO,其核心就是要拿到SQL查询所对应的每个列名及其数据类型。有了列名和数据类型,就能很容易写一个方法来产生DTO了。
我们知道,在一般情况下,SQL查询写完之后,包括调用存储过程和那些根据调用参数来动态拼接的SQL,虽然最终运行的SQL可能不尽相同,但是其查询结果的字段部分都是相对固定的。
当然,也有极少情况下会碰到字段都不确定的查询,不过在这种极端情况下,即使手工也没法写DTO了,反倒是用Map更合适, 我们这里不做讨论。
那么,怎么才能拿到列名和类型呢?
一种方案是分析SQL代码中SELECT部分的字段,不过其局限性比较大:
-
对于拼接的SQL代码,分析难度比较大
-
字段的类型也难以判断
-
SELECT * ...; CALL statement 这样常见的查询方式分析起来难度也很大
上述方案对像Mybatis这种采用配置文件(xml)来写SQL的方式,似乎有些可行性,我没有具体试验过,但估计面临的困难不会少。
另一种方案是想办法直接运行包含SQL的这些代码:
我们知道JDBC执行一个SQL查询,会返回ResultSet对象,通过该对象中的方法getMetaData(),能够得到这次查询的一些元数据:如列名称,列类型,以及该列所在的表名等,这些信息就已经足够我们来产生需要的那个类了。
那么,怎么才能够运行这些包含SQL的代码呢?
对于那些固定的SQL语句还稍微好说点,我们拿到这个固定的SQL,调用JDBC就能拿到MetaData,然后就可以很容易的根据这些信息来生成DTO。但是,对于那些复杂的需要根据一系列参数来动态产生的SQL查询,在参数设置好前是无法直接运行的,也就无法得到MetaData,得不到MetaData我们就无法生成DTO。
怎么办?
前面已经讨论了,即便是动态SQL,无论输入什么样的参数,虽然执行的SQL语句可能不一样,但是最终产生结果列却是固定的。 我们当前需要解决的问题不正是要获取这些列信息吗? 既然如此,那我们就构造一系列默认的参数值。这些参数并没有实际用处,仅仅是为了让我们正在编辑SQL代码得以正常运行,以便拿到需要的MetaData,至于能否查询到数据并不紧要。
通常我们编写的SQL代码,有2种存在形式:一是直接在Java代码中, 另外一种是放在配置文件中。这里不讨论哪种形式更好,以后我会单独再找地方来讨论。这里主要讨论的是在Java代码中拼接的SQL, 如何实现一个代码生成器来自动生成这些DTO:
要全自动化的解决这个问题,我们先来看看这个代码生成器所要面临的一些挑战及应对的思路:
-
如何标识一段需要生成DTO的SQL代码
首先,我们需要标识出这段代码,以便于代码生成器可以运行这段需要生成DTO代码。而通常情况下,我们的数据接口都是方法级别的,因此我们可以通过对方法进行注解,用注解来标识这个方法要返回一个DTO对象是个不错的选择。
-
如何定义DTO的类名
一种很容易想到的方法就是通过SQL代码所在的类名+方法名自动组合出一个名称, 当然有时为了灵活控制,应该允许程序员指定一个名字。
-
如何执行代码
执行代码的关键是构造一批能够调用注解方法的合适参数。当然首先需要对注解的方法进行代码分析,提取方法参数名及类型。代码分析可以用类似JavaCC这样的工具,或者一些语法分析器,这里不做细究。下面主要探讨下默认参数的构造:
为了简化问题,默认情况下我们可以按如下规则进行构造:
数字型参数,默认为:0, 例如:public Object find(int arg){...} 构造 int arg=0;
字符串参数,默认为:"", 构造 String arg="";
布尔型参数,默认为:false, 构造 boolean arg=false;
数组型参数,默认为:类型[0], 构造 int[] arg=new int[0];
对象型参数,默认为:new 类型(), 例如:public Object find(User arg){...} 构造 User arg=new User();
当然,对于一些简单参数的情况下,上面构造规则基本上都能够奏效。 但是,对于有些参数:比如参数是一个接口,或者是一个需要动态连接的表名,又或者是SQL拼接代码的逻辑要求参数必须是某些特殊值等等,默认构造出的参数就会导致程序无法执行。
但是,怎么才能够让我们的代码生成器能够继续执行下去呢? 好像确实没有什么能自动处理的办法,只好把这个问题交给程序员来处理了,让程序员来帮助代码生成器完成参数的初始化。
我们可以在注解上提供一个参数, 该参数主要完成对默认规则下无法初始化的参数进行设置。 当然,这个参数中的初始化代码也可以覆盖默认规则,以便于我们在编辑阶段就可以测试执行不同的SQL流程。
-
如何生成DTO
经过以上一系列的处理,我们终于能自动的把包含SQL查询代码的方法运行起来了。不过,现在我们还没得到想要的MetaData,还无法生成DTO。
一种可能的方式是包装一个JDBC,截获本次方法调用时执行的SQL查询, 但面临的问题是,如果方法中有多次查询就比较麻烦了。
另一种方式依赖于框架的支持,可以截获到方法的return语句,获取其执行的SQL语句, 有了SQL语句,生成DTO就没有什么难度了。
-
如何修改代码
为了尽量减少程序员的工作,我们的代码生成器在生成完DTO后, 还需要将方法的返回值自动修改成这个DTO类。
-
如何处理SQL的变更
简单的做法是:一旦有某个SQL代码发生变化,就把所有的DTO都按照前面的方法重新生成一遍。 不过,很显然当查询方法很多的时候,DTO代码生成的过程将缓慢到难以忍受。
另外一种更合理的做法是:我们在生成DTO时增加一个指纹字段,其值可以用SQL代码中所包含的信息来产生,例如:代码长度+代码的hashCode.代码生成器在决定是否需要处理这个方法前,先计算该方法的指纹和存在于DTO里面的指纹进行比较,如果相同就跳过,否则就认为本方法的SQL发生了变更,需要更新DTO。
具体的实现
到此为止,基本上DTO代码生成器的主要障碍都有了相应的处理办法。最后,我们用一个具体的实现来做个简单示例。
这里需要引入2个项目:
-
monalisa-db: https://github.com/11039850/monalisa-db
这是一个非常简单的ORM框架,通过@DB(jdbc_url,username,password)注解来引入数据库,同时也实现了对数据库的一些基本操作。
-
monalisa-eclipse: https://github.com/11039850/monalisa-eclipse
这是一个相应的Eclipse插件,它可以:
-
@DB注解的接口,在文件保存时 ,自动生成表的CRUD操作
-
@Select注解的方法,在文件保存时 ,自动生成DTO
-
很轻松的书写多行字符串
插件安装和设置可以参考: https://github.com/11039850/monalisa-db/wiki/Code-Generator
下面是一个根据动态SQL自动生成DTO示例,完整的例子工程可以参考: https://github.com/11039850/monalisa-example
package test.dao;
public class UserBlogDao {
//@Select 注解指示该方法需自动生成DTO
//默认类名: Result + 方法名, 默认包名:数据访问类的包名+"."+数据访问类的名称(小写)
//可选参数:name 指定生成结果类的名称,如果未指定该参数,则采用默认类名
//可选参数:build 初始化调用参数的Java代码,替换默认的参数构造规则
@Select(name="test.result.UserBlogs")
//!!! 保存后会自动修改该函数的返回值为: List -> List<UserBlogs>
//第一次编写时,由于结果类还不存在, 为了保证能够编译正常,
//函数的返回值 和 查询结果要用 泛值 替代, 保存后,插件会自动修改.
//函数的返回值 和 查询结果 泛值的对应关系分三类如下:
//1. List查询
//public DataTable method_name(...){... return Query.getList(); } 或
//public List method_name(...){... return Query.getList(); }
//
//2. Page查询
//public Page method_name(...){... return Query.Page(); }
//
//3. 单条记录
//public Object method_name(...){... return Query.getResult(); }
//
public List selectUserBlogs(int user_id){
Query q=TestDB.DB.createQuery();
q.add(""/**~{
SELECT a.id,a.name,b.title, b.content,b.create_time
FROM user a, blog b
WHERE a.id=b.user_id AND a.id=?
}*/, user_id);
return q.getList();
}
}
上述代码保存后,插件就会自动生成一个DTO类:test.result.UserBlogs, 并自动将方法修改成如下的声明:
public List<UserBlogs> selectUserBlogs(int user_id){
...
return q.getList(UserBlogs.class);
}
当然,如果对selectUserBlogs方法做了任何的修改(包括只是加了一个空格),保存文件后,插件也会自动更新UserBlogs。
同时,为了方便我们调试,插件也会在Eclipse的控制台窗口输出类似下面的信息:
2016-06-27 17:00:31 [I] ****** Starting generate result classes from: test.dao.UserBlogDao ******
2016-06-27 17:00:31 [I] Create class: test.result.UserBlogs, from: [selectUserBlogs(int)]
SELECT a.id,a.name,b.title, b.content,b.create_time
FROM user a, blog b
WHERE a.id=b.user_id AND a.id=0
顺便补充一下:
在Java代码中书写SQL,非常令人讨厌的一件事情就是Java语言中字符串的连接问题。使得大段的SQL代码中间要插很多的换行/转义符号,写起来很麻烦,看着也不舒服。monalisa-eclipse插件顺便也解决了多行字符串的书写问题。
例如:
System.out.println(""/**~{
SELECT *
FROM user
WHERE name="zzg"
}*/);
将会输出:
SELECT *
FROM user
WHERE name="zzg"
当然,为了快速书写,可以在Eclipse中把多行字符串的语法设置为一个代码模板。关于多行语法的更多细节可以参考:https://github.com/11039850/monalisa-db/wiki/Multiple-line-syntax
到这里,动态SQL代码自动生成DTO的思路和实现例子基本上就介绍完了, 欢迎大家提出各种有理无理的意见,一起讨论、进步,谢谢!
相关推荐
本主题涉及的核心技术是使用Freemarker模板引擎来生成DTO(Data Transfer Object)、DAO(Data Access Object)、RowMapper、BO(Business Object)和服务层代码。这些组件在Spring框架中扮演着重要角色。 1. **...
因此,"eclipse插件,根据数据库表自动生成DTO(pojo)插件"应运而生,它极大地提高了开发效率。 该插件的核心功能是根据数据库中的表结构自动生成对应的DTO和POJO类。这样,开发者无需手动编写这些类,只需专注于...
DTO(Data Transfer Object)代码自动生成器是一种工具,旨在帮助开发者快速、高效地根据数据库模型创建对应的Java实体类。在软件开发过程中,DTO通常用于在系统不同层之间传递数据,避免了直接暴露业务对象,降低了...
2. 执行生成:运行生成器,它会根据配置文件连接到PostgreSQL数据库,获取表的信息,然后自动生成相应的Java源代码。 3. 结果检查:生成的代码包括了基于表字段的实体类、Mapper接口和XML配置文件,以及对应的DAO和...
MyBatis Generator(MBG)是一款强大的自动化代码生成工具,主要针对MyBatis框架,能够自动生成DTO(Data Transfer Object)、DAO(Data Access Object)以及Mapper接口和XML映射文件,极大地提高了开发效率。...
这个自动生成工具能够根据数据库表结构,自动生成对应的Entity类,包括注解(如`@Entity`、`@Table`等),以及各个字段的`@Column`注解,自动匹配数据类型和字段名。 接着是DAO层。DAO是数据库操作的接口,它提供了...
不过,自动生成的代码虽然提高了效率,但可能需要根据实际需求进行调整和优化,以确保代码的可维护性和性能。总的来说,"Spring Boot Mybatis 自动生成Entity,controller、serviceImpl,Dao"是一个实用的开发辅助...
一直以来根据数据库表结构自动生成JavaBean、自动生成MyBaits的Mapper映射配置文件、自动生成数据库设计文档都是一件让人很头痛的事情,既浪费时间又很繁琐,看着几十上百个表的成千上万个字段,真是一件让人痛苦的...
1. **代码生成**:EasyCode可以根据数据库表信息自动生成对应的Java实体类、Mapper接口和XML配置文件,大大减少了手动编写的工作量。同时,它支持多种数据库,包括MySQL、Oracle、SQL Server等。 2. **模板定制**:...
使用实体类代码生成器,开发者可以根据数据库表结构自动生成对应的C#类,这些类通常包含属性,对应数据库中的字段,以及一些基本的增删改查方法。这样,当数据库结构发生变化时,只需更新数据库,生成器会自动更新...
### Abator自动生成ibatis代码知识点详解 #### 一、Abator概述 - **定义**:Abator是一款针对iBATIS框架的专业代码生成工具,它能够显著减少开发人员在使用iBATIS时需要手动编写的代码量,尤其是针对数据访问对象...
5. 自动化代码生成:该工具的核心功能是自动生成这些层的代码,包括数据访问接口、业务逻辑类以及可能的DTO(数据传输对象)等。这显著提高了开发效率,降低了出错概率。 6. 设计模式:SQL三层结构生成器遵循了分层...
执行代码生成器时,会根据配置文件中的设置连接到数据库,扫描表信息,并根据表结构自动生成对应的实体类、Mapper接口、Mapper XML文件以及Service层的接口和实现类。生成的代码会遵循预先定义的命名规则和目录结构...
一直以来根据数据库表结构自动生成JavaBean、自动生成MyBaits的Mapper映射配置文件、自动生成数据库设计文档都是一件让人很头痛的事情,既浪费时间又很繁琐,看着几十上百个表的成千上万个字段,真是一件让人痛苦的...
一直以来根据数据库表结构自动生成JavaBean、自动生成MyBaits的Mapper映射配置文件、自动生成数据库设计文档都是一件让人很头痛的事情,既浪费时间又很繁琐,看着几十上百个表的成千上万个字段,真是一件让人痛苦的...
4. **生成代码**:执行代码生成器,它会根据上述配置自动生成对应的Java源代码,并将其放入指定的目录下。 5. **整合到项目**:将生成的代码导入到项目中,根据项目的实际情况进行适当的调整,然后就可以直接使用...
MyBatis Generator(MBG)是一个强大的工具,用于自动生成Java源代码,SQL地图和配置文件,基于数据库表。在本例中,我们关注的是如何使用MyBatis Generator逆向工程来自动创建与数据库相关的注解,特别是针对实体类...
本主题聚焦于如何使用CodeSmith自动生成DTO(Data Transfer Object)、DataAccess和BizObject相关的代码。以下是对相关知识点的详细说明: 1. **DTO(Data Transfer Object)模式**: DTO是一种设计模式,用于在...
它基于MyBatisPlus框架,能够自动根据数据库中的表结构生成对应的Java实体类、Mapper接口及XML配置文件、Service层以及Controller层代码,极大地减少了手动编写这些基础代码的工作量。 首先,我们要理解MyBatisPlus...
使用CodeSmith自动生成这些层的代码,可以显著减少手动编写重复代码的工作量,使开发者能更专注于业务逻辑和用户体验的设计。通过调整和定制模板,可以适应不同的开发需求和规范,确保代码的一致性和可维护性。 在...