`
TSC9526
  • 浏览: 5484 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

如何根据动态SQL代码自动生成DTO

阅读更多

当前的状况

一般做数据库相关开发, 除非学习, 否则很少有人愿意直接使用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点就非常完美了:

  1. 根据SQL代码,直接生成对应的DTO

  2. 变更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个项目:

这是一个非常简单的ORM框架,通过@DB(jdbc_url,username,password)注解来引入数据库,同时也实现了对数据库的一些基本操作。

这是一个相应的Eclipse插件,它可以:

  1. @DB注解的接口,在文件保存时 ,自动生成表的CRUD操作

  2. @Select注解的方法,在文件保存时 ,自动生成DTO

  3. 很轻松的书写多行字符串

插件安装和设置可以参考: 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的思路和实现例子基本上就介绍完了, 欢迎大家提出各种有理无理的意见,一起讨论、进步,谢谢!

分享到:
评论

相关推荐

    Java利用Freemarker模板自动生成dto、dao、rowmapper、bo、service代码

    本主题涉及的核心技术是使用Freemarker模板引擎来生成DTO(Data Transfer Object)、DAO(Data Access Object)、RowMapper、BO(Business Object)和服务层代码。这些组件在Spring框架中扮演着重要角色。 1. **...

    eclipse插件,根据数据库表自动生成DTO(pojo)插件

    因此,"eclipse插件,根据数据库表自动生成DTO(pojo)插件"应运而生,它极大地提高了开发效率。 该插件的核心功能是根据数据库中的表结构自动生成对应的DTO和POJO类。这样,开发者无需手动编写这些类,只需专注于...

    DTO代码自动生成器

    DTO(Data Transfer Object)代码自动生成器是一种工具,旨在帮助开发者快速、高效地根据数据库模型创建对应的Java实体类。在软件开发过程中,DTO通常用于在系统不同层之间传递数据,避免了直接暴露业务对象,降低了...

    postgre自动生成代码

    2. 执行生成:运行生成器,它会根据配置文件连接到PostgreSQL数据库,获取表的信息,然后自动生成相应的Java源代码。 3. 结果检查:生成的代码包括了基于表字段的实体类、Mapper接口和XML配置文件,以及对应的DAO和...

    mybatis-generator 生成Dto,Dao,Mapping

    MyBatis Generator(MBG)是一款强大的自动化代码生成工具,主要针对MyBatis框架,能够自动生成DTO(Data Transfer Object)、DAO(Data Access Object)以及Mapper接口和XML映射文件,极大地提高了开发效率。...

    java 自动生成代码 entity,dao,service

    这个自动生成工具能够根据数据库表结构,自动生成对应的Entity类,包括注解(如`@Entity`、`@Table`等),以及各个字段的`@Column`注解,自动匹配数据类型和字段名。 接着是DAO层。DAO是数据库操作的接口,它提供了...

    Spring Boot Mybatis 自动生成Entity,controller、serviceImpl ,Dao,方便开发,无需手写

    不过,自动生成的代码虽然提高了效率,但可能需要根据实际需求进行调整和优化,以确保代码的可维护性和性能。总的来说,"Spring Boot Mybatis 自动生成Entity,controller、serviceImpl,Dao"是一个实用的开发辅助...

    基于数据库的自动化生成工具,自动生成JavaBean、自动生成数据库文档等(v5.0.0版_JAR)

    一直以来根据数据库表结构自动生成JavaBean、自动生成MyBaits的Mapper映射配置文件、自动生成数据库设计文档都是一件让人很头痛的事情,既浪费时间又很繁琐,看着几十上百个表的成千上万个字段,真是一件让人痛苦的...

    Idea自动生成代码神器EasyCode,让你如虎添翼

    1. **代码生成**:EasyCode可以根据数据库表信息自动生成对应的Java实体类、Mapper接口和XML配置文件,大大减少了手动编写的工作量。同时,它支持多种数据库,包括MySQL、Oracle、SQL Server等。 2. **模板定制**:...

    C#实体类代码生成器

    使用实体类代码生成器,开发者可以根据数据库表结构自动生成对应的C#类,这些类通常包含属性,对应数据库中的字段,以及一些基本的增删改查方法。这样,当数据库结构发生变化时,只需更新数据库,生成器会自动更新...

    Abator自动生成ibatis代码

    ### Abator自动生成ibatis代码知识点详解 #### 一、Abator概述 - **定义**:Abator是一款针对iBATIS框架的专业代码生成工具,它能够显著减少开发人员在使用iBATIS时需要手动编写的代码量,尤其是针对数据访问对象...

    SQL三层结构生成器

    5. 自动化代码生成:该工具的核心功能是自动生成这些层的代码,包括数据访问接口、业务逻辑类以及可能的DTO(数据传输对象)等。这显著提高了开发效率,降低了出错概率。 6. 设计模式:SQL三层结构生成器遵循了分层...

    mybatis自动生成文件jar包

    执行代码生成器时,会根据配置文件中的设置连接到数据库,扫描表信息,并根据表结构自动生成对应的实体类、Mapper接口、Mapper XML文件以及Service层的接口和实现类。生成的代码会遵循预先定义的命名规则和目录结构...

    基于数据库的自动化生成工具,自动生成JavaBean、自动生成数据库文档等(v5.0.0版_EXE)

    一直以来根据数据库表结构自动生成JavaBean、自动生成MyBaits的Mapper映射配置文件、自动生成数据库设计文档都是一件让人很头痛的事情,既浪费时间又很繁琐,看着几十上百个表的成千上万个字段,真是一件让人痛苦的...

    基于数据库的自动化生成工具,自动生成JavaBean、自动生成数据库文档等(v5.8.0版_JAR)

    一直以来根据数据库表结构自动生成JavaBean、自动生成MyBaits的Mapper映射配置文件、自动生成数据库设计文档都是一件让人很头痛的事情,既浪费时间又很繁琐,看着几十上百个表的成千上万个字段,真是一件让人痛苦的...

    mybatis_plus 3.0以后版本代码生成器

    4. **生成代码**:执行代码生成器,它会根据上述配置自动生成对应的Java源代码,并将其放入指定的目录下。 5. **整合到项目**:将生成的代码导入到项目中,根据项目的实际情况进行适当的调整,然后就可以直接使用...

    mybaties 逆向工程 自动生成数据库相关注解

    MyBatis Generator(MBG)是一个强大的工具,用于自动生成Java源代码,SQL地图和配置文件,基于数据库表。在本例中,我们关注的是如何使用MyBatis Generator逆向工程来自动创建与数据库相关的注解,特别是针对实体类...

    自动生成DTODataAccessBizObject的CodeSmith模板

    本主题聚焦于如何使用CodeSmith自动生成DTO(Data Transfer Object)、DataAccess和BizObject相关的代码。以下是对相关知识点的详细说明: 1. **DTO(Data Transfer Object)模式**: DTO是一种设计模式,用于在...

    MyBatisPlus代码生成器

    它基于MyBatisPlus框架,能够自动根据数据库中的表结构生成对应的Java实体类、Mapper接口及XML配置文件、Service层以及Controller层代码,极大地减少了手动编写这些基础代码的工作量。 首先,我们要理解MyBatisPlus...

    CodeSmith .net 三层自动生成模板

    使用CodeSmith自动生成这些层的代码,可以显著减少手动编写重复代码的工作量,使开发者能更专注于业务逻辑和用户体验的设计。通过调整和定制模板,可以适应不同的开发需求和规范,确保代码的一致性和可维护性。 在...

Global site tag (gtag.js) - Google Analytics