0.2.1.0        2019-04-17        calvin
            * 新增拦截器"@@STATEMENT_INTERCEPTOR(...)"

0.2.0.0        2019-04-14        calvin
            * 重整了项目露露结构

0.1.1.0        2019-04-07        calvin
            * SQL动作配置文件支持SQL折行

0.1.0.0        2019-04-06        calvin
            * 重构了语法分析器
            * 调整了自动生成方法名原型
            * 开始写文档
            
0.0.8.0        2019-04-03        calvin
            * 支持自定义方法名"@@METHOD(...)"
            
0.0.7.0        2019-04-02        calvin
            * 支持hint
            * 完善sqlaction-benchmark和mybatis-benchmark

0.0.6.0        2019-04-01        calvin
            * 新增子项目sqlaction-benchmark和mybatis-benchmark用于性能比较

0.0.5.0        2019-03-31        calvin
            * 支持在dbserver.conf.json中用户自定义数据类型
              ,比如MySQL的"decimal(12,2)"对应jdbc的"SQLACTION_DATA_TYPE_DECIMAL"
              通过配置可转定义为jdbc的"SQLACTION_DATA_TYPE_DOUBLE",自动生成JAVA实体属性类型为"double"

0.0.4.0        2019-03-31        calvin
            * SELECT后支持COUNT(*)
            * FROM后支持GROUP、HAVING、ORDER

0.0.3.0        2019-03-30        calvin
            * 调整了增删改查方法名和原型
            * 支持SELECT多表连接查询
            * 支持表别名功能
            * 支持单个目录里同时生成多个表实体类
            * 支持SET和WHERE直接写值常量

0.0.2.0        2019-03-26        calvin
            * 实现单表的增删改查jdbc代码自动生成

0.0.1.0        2019-03-23        calvin
            * 创建
---------------------------------------------------------------------

1. 概述

厌烦了MyBatisJPA(Hibernate)的冗余配置和繁琐使用,以及XML拷来拷去,写那么多Mapper要是直接用JDBC早就写完了,如果使用一个框架/工具比不使用而带来更多的心智负担,那还不如不用。其实,直接使用JDBC还是蛮干净和高性能的,只要解决其三个痛点:

  1. 手工编写数据库表实体类。
  2. 手工编写大量setString和getString代码,尤其还要人工保证字段序号递增。
  3. 因为直接写SQL,涉及不同DBMS的SQL方言时,移植性不好,比如分页查询。

能否造一个更好的轮子?

于是,我花了十多个晚上,结合之前在C技术栈中的设计和经验,结合JAVA特点,写了sqlaction。

sqlaction是自动生成JDBC代码的数据库持久层工具,它为应用提供了类似MyBatisHibernate操作数据库能力,但更轻量级和几乎消除了所有的手工冗余编码,提高开发效率,也易于与其它框架搭配使用。sqlaction读取数据库中的表结构元信息和少量配置文件信息(SQL),自动生成数据库表实体类,自动生成基于JDBC的SQL动作方法代码,自动生成拦截器框架等代码,应用调用其自动生成的代码就能极其快捷的操作数据库,同时还拥有JDBC的高性能,更重要的是开发者能直接看到底层操作代码,增加自主可控,没有低效的反射,没有复杂的热修改字节码,没有庞大笨重的隐藏核心。

2. 一个DEMO

放一个DEMO感受一下:

2.1. 建表DDL

ddl.sql

CREATE TABLE `sqlaction_demo` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '名字',
  `address` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`),
  KEY `sqlaction_demo` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

2.2. 新建JAVA项目

只依赖数据库连接库和作者另一个JSON解析器okjson,引入mysql-connector-java-X.Y.Z.jarokjson-0.0.9.0.jar

建立包目录,在包目录或上级某一级目录中创建数据库连接配置文件dbserver.conf.json,工具会从执行目录开始往上查找,只要某一级目录中存在这个配置文件即可。

{
	"driver" : "com.mysql.jdbc.Driver" ,
	"url" : "jdbc:mysql://127.0.0.1:3306/calvindb?serverTimezone=GMT" ,
	"user" : "calvin" ,
	"pwd" : "calvin"
}

在包目录或上级某一级目录中创建SQL动作配置文件sqlaction.conf.json,工具会从执行目录开始往上查找,只要某一级目录中存在这个配置文件即可。

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "sqlaction_demo" ,
			"sqlactions" : [
				"SELECT * FROM sqlaction_demo" ,
				"SELECT * FROM sqlaction_demo WHERE name=?" ,
				"INSERT INTO sqlaction_demo" ,
				"UPDATE sqlaction_demo SET address=? WHERE name=? @@METHOD(updateAddressByName)" ,
				"DELETE FROM sqlaction_demo WHERE name=?"
			]
		}
	] ,
	"javaPackage" : "xyz.calvinwilliams.sqlaction.demo"
}

2.3. 在包目录中执行sqlaction工具

我把执行命令行包成批处理文件后执行,欢迎懂Eclipse插件开发的同学帮我写个插件 :)

pp.bat

java -Dfile.encoding=UTF-8 -classpath "D:\Work\mysql-connector-java-8.0.15\mysql-connector-java-8.0.15.jar;%USERPROFILE%\.m2\repository\xyz\calvinwilliams\okjson\0.0.9.0\okjson-0.0.9.0.jar;%USERPROFILE%\.m2\repository\xyz\calvinwilliams\sqlaction\0.2.1.0\sqlaction-0.2.1.0.jar" xyz.calvinwilliams.sqlaction.SqlActionGencode
pause

注意:使用Maven管理的项目,在添加sqlaction依赖声明后,maven会自动下载sqlaction以及其依赖okjson的jar到C:\User\用户名\.m2\repository\xyz\calvinwilliams\下。sqlaction依赖声明见最后面“关于本项目”章节。

执行pp.bat,工具sqlaction会从执行目录开始往上查找读入dbserver.conf.jsonsqlaction.conf.json并自动生成所有代码。

//////////////////////////////////////////////////////////////////////////////
/// sqlaction v0.0.8.0
/// Copyright by calvin<calvinwilliams@163.com,calvinwilliams@gmail.com>
//////////////////////////////////////////////////////////////////////////////
--- dbserverConf ---
  dbms[mysql]
driver[com.mysql.jdbc.Driver]
   url[jdbc:mysql://127.0.0.1:3306/calvindb?serverTimezone=GMT]
  user[calvin]
   pwd[calvin]
--- sqlactionConf ---
 database[calvindb]
        table[sqlaction_demo]
                sqlaction[SELECT * FROM sqlaction_demo]
                sqlaction[SELECT * FROM sqlaction_demo WHERE name=?]
                sqlaction[INSERT INTO sqlaction_demo]
                sqlaction[UPDATE sqlaction_demo SET address=? WHERE name=? @@METHOD(updateAddressByName)]
                sqlaction[DELETE FROM sqlaction_demo WHERE name=?]
SqlActionTable.getTableInDatabase[sqlaction_demo] ...
...
...
...
*** NOTICE : Write SqlactionDemoSAO.java completed!!!

如果没有出现*** ERROR : ...说明工具执行成功,在执行所在目录中自动生成了一个JAVA源代码文件

SqlactionDemoSAO.java

// This file generated by sqlaction v0.2.1.0

package xyz.calvinwilliams.sqlaction.demo;

import java.math.*;
import java.util.*;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class SqlactionDemoSAO {

	int				id ; // 编号
	String			name ; // 名字
	String			address ; // 地址

	int				_count_ ; // defining for 'SELECT COUNT(*)'

	// SELECT * FROM sqlaction_demo
	public static int SELECT_ALL_FROM_sqlaction_demo( Connection conn, List<SqlactionDemoSAO> sqlactionDemoListForSelectOutput ) throws Exception {
		Statement stmt = conn.createStatement() ;
		ResultSet rs = stmt.executeQuery("SELECT * FROM sqlaction_demo") ;
		while( rs.next() ) {
			SqlactionDemoSAO sqlactionDemo = new SqlactionDemoSAO() ;
			sqlactionDemo.id = rs.getInt( 1 ) ;
			sqlactionDemo.name = rs.getString( 2 ) ;
			sqlactionDemo.address = rs.getString( 3 ) ;
			sqlactionDemoListForSelectOutput.add(sqlactionDemo) ;
		}
		return sqlactionDemoListForSelectOutput.size();
	}

	// SELECT * FROM sqlaction_demo WHERE name=?
	public static int SELECT_ALL_FROM_sqlaction_demo_WHERE_name_E_( Connection conn, List<SqlactionDemoSAO> sqlactionDemoListForSelectOutput, String _1_name ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement("SELECT * FROM sqlaction_demo WHERE name=?") ;
		prestmt.setString( 1, _1_name );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			SqlactionDemoSAO sqlactionDemo = new SqlactionDemoSAO() ;
			sqlactionDemo.id = rs.getInt( 1 ) ;
			sqlactionDemo.name = rs.getString( 2 ) ;
			sqlactionDemo.address = rs.getString( 3 ) ;
			sqlactionDemoListForSelectOutput.add(sqlactionDemo) ;
		}
		return sqlactionDemoListForSelectOutput.size();
	}

	// INSERT INTO sqlaction_demo (name,address) VALUES (?,?)
	public static int INSERT_INTO_sqlaction_demo( Connection conn, SqlactionDemoSAO sqlactionDemo ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement("INSERT INTO sqlaction_demo (name,address) VALUES (?,?)") ;
		prestmt.setString( 1, sqlactionDemo.name );
		prestmt.setString( 2, sqlactionDemo.address );
		return prestmt.executeUpdate() ;
	}

	// UPDATE sqlaction_demo SET address=? WHERE name=? 
	public static int updateAddressByName( Connection conn, String _1_address_ForSetInput, String _1_name_ForWhereInput ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement("UPDATE sqlaction_demo SET address=? WHERE name=? ") ;
		prestmt.setString( 1, _1_address_ForSetInput );
		prestmt.setString( 2, _1_name_ForWhereInput );
		return prestmt.executeUpdate() ;
	}

	// DELETE FROM sqlaction_demo WHERE name=?
	public static int DELETE_FROM_sqlaction_demo_WHERE_name_E_( Connection conn, String _1_name ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement("DELETE FROM sqlaction_demo WHERE name=?") ;
		prestmt.setString( 1, _1_name );
		return prestmt.executeUpdate() ;
	}
}

SqlactionDemoSAU.java

// This file generated by sqlaction v0.2.1.0

package xyz.calvinwilliams.sqlaction;

import java.math.*;
import java.util.*;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class SqlactionDemoSAU {

}

工具sqlaction内部处理流程如下:

  1. 首先查找执行目录中的数据库连接配置文件dbserver.conf.json,如果没有找到就迭代往上级目录继续找,从该配置文件中获得数据库连接配置信息。然后以相同的查找逻辑读取sqlaction.conf.json获得数据库名、表列表、以及每张表需要执行的SQL语句列表等信息。配置文件往上查找机制是为了灵活适应不同规模项目目录的规划。
  2. 连接数据库,读取表结构元信息,每张表对应自动生成数据库表实体类和SQL动作方法JAVA源代码文件XxxSao.java和用户自定义代码文件XxxSau.java,,sqlaction配置文件中的每一条SQL自动解析生成基于JDBC的JAVA方法,从此再也不用手工写JDBC代码了!

2.4. 到目前为止,一行JAVA代码都没写,现在开始写应用代码

Demo.java

package xyz.calvinwilliams.sqlaction.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;

public class Demo {

	public static void main(String[] args) {
		Connection				conn = null ;
		List<SqlactionDemoSAO>	sqlactionDemoList = null ;
		SqlactionDemoSAO		sqlactionDemo = null ;
		SqlactionDemoSAO		sqlactionDemoForSetInput = null ;
		SqlactionDemoSAO		sqlactionDemoForWhereInput = null ;
		int						nret = 0 ;

		// Connect to database
		try {
			Class.forName( "com.mysql.jdbc.Driver" );
			conn = DriverManager.getConnection( "jdbc:mysql://127.0.0.1:3306/calvindb?serverTimezone=GMT", "calvin", "calvin" ) ;
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		try {
			conn.setAutoCommit(false);
			
			// Delete records with name
			nret = SqlactionDemoSAO.DELETE_FROM_sqlaction_demo_WHERE_name_E_( conn, "Calvin" ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAO.DELETE_FROM_sqlaction_demo_WHERE_name_E_ failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAO.DELETE_FROM_sqlaction_demo_WHERE_name_E_ ok , rows["+nret+"] effected" );
			}
			
			// Insert record
			sqlactionDemo = new SqlactionDemoSAO() ;
			sqlactionDemo.name = "Calvin" ;
			sqlactionDemo.address = "My address" ;
			nret = SqlactionDemoSAO.INSERT_INTO_sqlaction_demo( conn, sqlactionDemo ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAO.INSERT_INTO_sqlaction_demo failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAO.INSERT_INTO_sqlaction_demo ok" );
			}
			
			// Update record with name
			nret = SqlactionDemoSAO.updateAddressByName( conn, "My address 2", "Calvin" ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAO.updateAddressByName failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAO.updateAddressByName ok , rows["+nret+"] effected" );
			}
			
			// Query records
			sqlactionDemoList = new LinkedList<SqlactionDemoSAO>() ;
			nret = SqlactionDemoSAO.SELECT_ALL_FROM_sqlaction_demo( conn, sqlactionDemoList ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAO.SELECT_ALL_FROM_sqlaction_demo failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAO.SELECT_ALL_FROM_sqlaction_demo ok" );
				for( SqlactionDemoSAO r : sqlactionDemoList ) {
					System.out.println( "    id["+r.id+"] name["+r.name+"] address["+r.address+"]" );
				}
			}
			
			conn.commit();
		} catch(Exception e) {
			try {
				conn.rollback();
			} catch (Exception e2) {
				return;
			}
		}
		
		return;
	}

}

2.5. 执行

SqlactionDemoSAO.DELETE_FROM_sqlaction_demo_WHERE_name_E_ ok , rows[1] effected
SqlactionDemoSAO.INSERT_INTO_sqlaction_demo ok
SqlactionDemoSAO.UPDATE_sqlaction_demo_SET_address_E_WHERE_name_E_ ok , rows[1] effected
SqlactionDemoSAO.SELECT_ALL_FROM_sqlaction_demo ok
    id[15] name[Calvin] address[My address 2]

总结:

对表的增删改查只需调用前面自动生成的数据库表实体类中的方法即可,而且底层执行代码可随时查看,没有什么秘密,没有什么高深的技术。

工具sqlaction只在开发阶段使用,与运行阶段无关,说到底只是在应用与JDBC之间自动生成了薄薄的一层代码而已,把大量手工冗余代码都自动生成了,让开发者节省大量时间而去关注业务,减少大量机械操作减轻心智负担,提高生产力,早点做完工作回家抱女盆友/老婆 :)

6. 关于本项目

欢迎使用sqlaction,如果你在使用中碰到了问题请告诉我,谢谢 ^_^

源码托管地址 : Giteegithub

Apache Maven

<dependency>
  <groupId>xyz.calvinwilliams</groupId>
  <artifactId>sqlaction</artifactId>
  <version>0.2.1.0</version>
</dependency>

Gradle Kotlin DSL

compile("xyz.calvinwilliams:sqlaction:0.2.1.0")

7. 关于作者

厉华,右手C,左手JAVA,写过小到性能卓越方便快捷的日志库、HTTP解析器、日志采集器等,大到交易平台/中间件等,分布式系统实践者,容器技术专研者,目前在某城商行负责基础架构。

通过邮箱联系我 : 网易Gmail