18 Star 59 Fork 11

calvinwilliams / sqlaction

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

sqlaction - 自动生成JDBC代码的数据库持久层工具

1. 概述

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

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

能否造一个更好的轮子?

于是,我结合之前在C技术栈中的产品经验,结合JAVA特点,写了sqlaction。

sqlaction是自动生成JDBC代码的数据库持久层工具,它为应用提供了类似MyBatisHibernate操作数据库能力,但更轻量级和几乎消除了所有的手工冗余工作(编码和配置),提高开发效率,也最大化运行效率。

sqlaction只负责把配置的SQL规则转换为完整的JDBC代码,不干涉数据库连接池管理、数据库事务控制等,也就易于与其它框架搭配使用。

sqlaction核心工作原理是读取数据库中的表结构元信息和少量配置文件信息(SQL语法),自动生成数据库表实体类,自动生成基于JDBC的SQL动作方法代码,自动生成拦截器框架等代码,应用调用其自动生成的代码就能极其快捷的操作数据库,同时还拥有JDBC的高性能,更重要的是开发者能直接看到底层操作代码,增加自主可控,没有低效的反射,没有复杂的热修改字节码,没有庞大笨重的隐藏核心要学习。

sqlaction核心功能:

  1. 多数据库支持,目前支持有MySQL、PostgreSQL、Oracle、Sqlite、SqlServer。
  2. 抽象统一了自增字段和序列两大数据库阵营对主键值的赋值,不同数据库用一样的语法配置。
  3. 抽象统一了物理分页功能,原生自带通用分页能力,不同数据库用一样的语法配置。
  4. 执行效率比MyBatis快约20%。
  5. 高级模式支持任意复杂SQL。

sqlaction核心优势:

  1. 使用sqlactionMyBatis减少一半的开发工作量,大幅提升开发效能。
  2. sqlaction运行性能比MyBatis快20%,明显降低服务迟延。
  3. 抽象统一了不同数据库的SQL方言差异,真正做到开发与数据库无关。
  4. 完整覆盖联机型应用(TP)和分析型应用(AP)两大场景。

2. 一个DEMO

放一个DEMO感受一下:

2.1. 建表DDL

以MySQL为例

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项目

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

建立包目录,在包目录或上级某一级目录中创建数据库连接配置文件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=?" ,
				"DELETE FROM sqlaction_demo WHERE name=?"
			]
		}
	] ,
	"javaPackage" : "xyz.calvinwilliams.sqlaction"
}

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.9.0\sqlaction-0.2.9.0.jar" xyz.calvinwilliams.sqlaction.SqlActionGencode
pause

注意:使用Maven管理的项目,在pom.xml里添加sqlaction坐标后,maven会自动下载sqlaction以及其依赖okjson的jar到C:\User\用户名\.m2\repository\xyz\calvinwilliams\下,上面执行命令直接引用了Maven目录里的包。sqlaction坐标见最后面“关于本项目”章节。

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

//////////////////////////////////////////////////////////////////////////////
/// sqlaction v0.2.9.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.9.0
// WARN : DON'T MODIFY THIS FILE

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 SqlactionDemoSAO {

	int				id ; // 编号 // PRIMARY KEY
	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<SqlactionDemoSAU> sqlactionDemoListForSelectOutput ) throws Exception {
		Statement stmt = conn.createStatement() ;
		ResultSet rs = stmt.executeQuery( "SELECT * FROM sqlaction_demo" ) ;
		while( rs.next() ) {
			SqlactionDemoSAU sqlactionDemo = new SqlactionDemoSAU() ;
			sqlactionDemo.id = rs.getInt( 1 ) ;
			sqlactionDemo.name = rs.getString( 2 ) ;
			sqlactionDemo.address = rs.getString( 3 ) ;
			sqlactionDemoListForSelectOutput.add(sqlactionDemo) ;
		}
		rs.close();
		stmt.close();
		return sqlactionDemoListForSelectOutput.size();
	}

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

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

	// UPDATE sqlaction_demo SET address=? WHERE name=?
	public static int UPDATE_sqlaction_demo_SET_address_E_WHERE_name_E_( 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 );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

	// 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 );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

}

SqlactionDemoSAU.java

// This file generated by sqlaction v0.2.9.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 extends SqlactionDemoSAO {

}

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

Demo.java

package xyz.calvinwilliams.sqlaction;

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<SqlactionDemoSAU>	sqlactionDemoList = null ;
		SqlactionDemoSAU		sqlactionDemo = 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 = SqlactionDemoSAU.DELETE_FROM_sqlaction_demo_WHERE_name_E_( conn, "Calvin" ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAU.DELETE_FROM_sqlaction_demo_WHERE_name_E_ failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAU.DELETE_FROM_sqlaction_demo_WHERE_name_E_ ok , rows["+nret+"] effected" );
			}
			
			// Insert record
			sqlactionDemo = new SqlactionDemoSAU() ;
			sqlactionDemo.name = "Calvin" ;
			sqlactionDemo.address = "My address" ;
			nret = SqlactionDemoSAU.INSERT_INTO_sqlaction_demo( conn, sqlactionDemo ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAU.INSERT_INTO_sqlaction_demo failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAU.INSERT_INTO_sqlaction_demo ok" );
			}
			
			// Update record with name
			nret = SqlactionDemoSAU.UPDATE_sqlaction_demo_SET_address_E_WHERE_name_E_( conn, "My address 2", "Calvin" ) ;
			if( nret < 0 ) {
				System.out.println( "SqlactionDemoSAU.UPDATE_sqlaction_demo_SET_address_E_WHERE_name_E_ failed["+nret+"]" );
				conn.rollback();
				return;
			} else {
				System.out.println( "SqlactionDemoSAU.UPDATE_sqlaction_demo_SET_address_E_WHERE_name_E_ ok , rows["+nret+"] effected" );
			}
			
			// Query records
			sqlactionDemoList = new LinkedList<SqlactionDemoSAU>() ;
			nret = SqlactionDemoSAU.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( SqlactionDemoSAU 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. 执行

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

总结:

对表的增删改查只需调用前面自动生成的SAU类中的方法即可,底层执行代码完全基于JDBC,可随时查看,没有什么秘密,没有什么高深的技术。

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

3. 使用参考

3.1. 开发流程

                                        sqlaction
dbserver.conf.json、sqlaction.conf.json ---------> XxxSao.java、XxxSau.java --\
                                                                               ---> App.jar
                                                                   App.java --/

sqlaction读取执行目录或上级目录中的数据库连接配置文件dbserver.conf.json和SQL动作配置文件sqlaction.conf.json,自动生成基于JDBC的SQL动作源代码的SAO类,以及继承SAO类的SAU类,应用直接调用SAU类就能操作数据库DML。

SAO类XXXSao.java在每次执行工具sqlaction时都会被覆盖刷新,所以不要手工修改。

SAU类XXXSau.java在首次执行工具sqlaction时创建,后续执行时不会被覆盖刷新,所以需要定制、扩展的方法和属性都加在这个类中,又因为SAU类继承自SAO类,如果存在相同的方法,应用调用的始终都是SAU类中的版本。

3.2. 配置文件dbserver.conf.json

{
	"driver" : "com.mysql.jdbc.Driver" ,
	"url" : "jdbc:mysql://127.0.0.1:3306/calvindb?serverTimezone=GMT" ,
	"user" : "root" ,
	"pwd" : "root" ,
	"userDefineDataTypes" : [
		{ "source":"decimal,*,12,2" , "redefine":"double,*,14,*" }
	]
}

数据库连接配置文件dbserver.conf.json配置了工具sqlaction执行所需数据库层面上的信息:

driver : DBMS驱动类。

url : DBMS连接配置串。

user : DBMS连接用户名。

pwd : DBMS连接密码。

userDefineDataTypes : 自定义字段类型转换,比如数据库中的类型DECIMAL(12,2)映射到JAVA变量类型是BigDecimal,但在某应用系统中希望是JAVA变量类型double,可以在这个配置集中在正式转换前把DECIMAL(12,2)强制转换成DOUBLE,那么正式转换时DOUBLE会映射成JAVA变量类型double

数据库字段类型与sqlaction的JAVA变量类型映射表:

MySQL字段类型 sqlaction的JAVA变量类型
bit boolean
tinyint byte
smallint short
mediumint int
int int
bigint long
real float
float double
double double
decimal BigDecimal
numeric BigDecimal
char String
varchar String
date java.sql.Date
time java.sql.Time
datetime java.sql.Date
timestamp timestamp
year java.sql.Date
binary byte[]
varbinary byte[]
blob byte[]
tinyblob byte[]
mediumblob byte[]
longblob byte[]
(other) String

注意:数据库连接配置文件dbserver.conf.json一般放在项目根目录里,以便于所有子模块都能使用到。

注意:读取JSON配置文件使用到了我的另一个开源项目:okjson,一个简洁易用的JSON解析器/生成器,只有一个类文件,可以很方便的融合到其它项目中。

dbms : 一般情况下sqlaction根据driver能猜对数据库类型,那么这个配置就不用写明,如果你用的driver实在太怪异导致猜不到,请显式配置。

3.3. 配置文件sqlaction.conf.json

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user_base" ,
			"sqlactions" : [
				"SELECT * FROM user_base" ,
				"SELECT * FROM user_base WHERE name=?" ,
				"SELECT name,address FROM user_base WHERE age<=? AND gender=?" ,
				"SELECT * FROM user_base ORDER BY name DESC" ,
				"SELECT gender,count(*) FROM user_base GROUP BY gender" ,
				"INSERT INTO user_base @@SELECTSEQ(user_base_seq_id) @@SELECTKEY(id)" ,
				"UPDATE user_base SET lvl=?" ,
				"UPDATE user_base SET address='calvin address',lvl=10 WHERE name='Calvin'" ,
				"UPDATE user_base SET lvl=? WHERE age>? AND gender=?" ,
				"DELETE FROM user_base" ,
				"DELETE FROM user_base WHERE name='Calvin'" ,
				"DELETE FROM user_base WHERE age<>? AND gender<>?"
			]
		} ,
		{
			"table" : "user_order" ,
			"sqlactions" : [
				"SELECT /* blablabla~ */ * FROM user_order @@STATEMENT_INTERCEPTOR()" ,
				"SELECT * FROM user_order WHERE user_id=?" ,
				"SELECT * FROM user_order @@PAGEKEY(id)" ,
				"SELECT * FROM user_order WHERE item_name<>'' @@PAGEKEY(id) @@PAGESORT(DESC)" ,
				"SELECT user_base.name,user_base.address,user_order.item_name,user_order.amount,user_order.total_price
					FROM user_base,user_order
					WHERE user_base.name=? AND user_base.id=user_order.user_id
					@@METHOD(queryUserAndOrderByName)" ,
				"SELECT u.name,u.address,o.* FROM user_base u,user_order o WHERE u.name=? AND u.id=o.user_id @@STATEMENT_INTERCEPTOR(statementInterceptorForQueryUserAndOrderByName)" ,
				"SELECT o.* #{UserOrderSAU.*} FROM user_order o #{user_order} @@ADVANCEDMODE" ,
				"SELECT MIN(total_price) #{SqlActionTest.minTotalPrice:double}, MAX(total_price) #{SqlActionTest.maxTotalPrice:double}, COUNT(*) #{UserOrderSAU._count_}
					FROM user_order #{user_order}
					@@ADVANCEDMODE" ,
				"SELECT user_base.name				#{UserBaseSAU.name}
					,user_order.item_name			#{UserOrderSAU.itemName}
					,SUM(user_order.amount)			#{UserOrderSAU.amount}
					,SUM(user_order.total_price)	#{UserOrderSAU.totalPrice}
					FROM user_base					#{user_base}
						,user_order					#{user_order}
					WHERE user_order.user_id IN (
												SELECT id
												FROM user_base
												WHERE id>=?		#{UserOrderSAU.id}
											)
						AND user_order.user_id=user_base.id
					GROUP BY user_base.name
					ORDER BY user_base.name
					@@ADVANCEDMODE @@METHOD(statUsersAmountAndTotalPrice)" ,
				"INSERT INTO user_order @@SELECTSEQ(user_order_seq_id) @@SELECTKEY(id)" ,
				"UPDATE user_order SET total_price=? WHERE user_id=?" ,
				"DELETE FROM user_order" ,
				"DELETE FROM user_order WHERE user_id=? #{UserOrderSAU.userId} @@ADVANCEDMODE @@METHOD(removeUserOrder)"
			]
		}
	] ,
	"javaPackage" : "xyz.calvinwilliams.test"
}

SQL动作配置文件sqlaction.conf.json主要配置了数据库SQL动作列表,用于后续的自动生成JDBC代码。

database : 数据库名。

tables : 表列表。

table : 表名。

sqlactions : SQL语句列表。注意最后一条SQL后不带','以符合JSON规范。

javaPackage : JAVA包名,自动生成JAVA类文件时放在最上面。

目前sqlaction支持的SQL标准:

查询

SQL动作语法:

SELECT [*|[table_name.|table_alias_name.][column_name|*][,...][,COUNT(*)]]
    [ /* hint */ ]
    FROM table_name [table_alias_name],...
    [ WHERE [table_name.|table_alias_name.]column_name [=|<>|>|>=|<|<=] [?|const|[table_name2.|table_alias_name2.]column_name2] [AND ...] ]
    [ GROUP BY [table_name.|table_alias_name.]column[,[table_name2.|table_alias_name.]column2][,...] ]
    [ HAVING ... ]
    [ ORDER BY column[,...] [ASC|DESC] ]
    ...

SQL动作语法示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user_base" ,
			"sqlactions" : [
				"SELECT * FROM user_base" ,
				"SELECT * FROM user_base WHERE name=?" ,
				"SELECT name,address FROM user_base WHERE age<=? AND gender=?" ,
				"SELECT * FROM user_base ORDER BY name DESC" ,
				"SELECT gender,count(*) FROM user_base GROUP BY gender" ,

自动生成JDBC代码:

	// SELECT * FROM user_base
	public static int SELECT_ALL_FROM_user_base( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput ) throws Exception {
		Statement stmt = conn.createStatement() ;
		ResultSet rs = stmt.executeQuery( "SELECT * FROM user_base" ) ;
		while( rs.next() ) {
			UserBaseSAU userBase = new UserBaseSAU() ;
			userBase.id = rs.getInt( 1 ) ;
			userBase.name = rs.getString( 2 ) ;
			userBase.gender = rs.getString( 3 ) ;
			userBase.age = rs.getShort( 4 ) ;
			userBase.address = rs.getString( 5 ) ;
			userBase.lvl = rs.getInt( 6 ) ;
			userBaseListForSelectOutput.add(userBase) ;
		}
		rs.close();
		stmt.close();
		return userBaseListForSelectOutput.size();
	}

	// SELECT * FROM user_base WHERE name=?
	public static int SELECT_ALL_FROM_user_base_WHERE_name_E_( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput, String _1_UserBaseSAU_name ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT * FROM user_base WHERE name=?" ) ;
		prestmt.setString( 1, _1_UserBaseSAU_name );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserBaseSAU userBase = new UserBaseSAU() ;
			userBase.id = rs.getInt( 1 ) ;
			userBase.name = rs.getString( 2 ) ;
			userBase.gender = rs.getString( 3 ) ;
			userBase.age = rs.getShort( 4 ) ;
			userBase.address = rs.getString( 5 ) ;
			userBase.lvl = rs.getInt( 6 ) ;
			userBaseListForSelectOutput.add(userBase) ;
		}
		rs.close();
		prestmt.close();
		return userBaseListForSelectOutput.size();
	}

	// SELECT name,address FROM user_base WHERE age<=? AND gender=?
	public static int SELECT_name_j_address_FROM_user_base_WHERE_age_LE_AND_gender_E_( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput, short _1_UserBaseSAU_age, String _2_UserBaseSAU_gender ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT name,address FROM user_base WHERE age<=? AND gender=?" ) ;
		prestmt.setShort( 1, _1_UserBaseSAU_age );
		prestmt.setString( 2, _2_UserBaseSAU_gender );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserBaseSAU userBase = new UserBaseSAU() ;
			userBase.name = rs.getString( 1 ) ;
			userBase.address = rs.getString( 2 ) ;
			userBaseListForSelectOutput.add(userBase) ;
		}
		rs.close();
		prestmt.close();
		return userBaseListForSelectOutput.size();
	}

	// SELECT * FROM user_base ORDER BY name DESC
	public static int SELECT_ALL_FROM_user_base_ORDER_BY_name_DESC( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput ) throws Exception {
		Statement stmt = conn.createStatement() ;
		ResultSet rs = stmt.executeQuery( "SELECT * FROM user_base ORDER BY name DESC" ) ;
		while( rs.next() ) {
			UserBaseSAU userBase = new UserBaseSAU() ;
			userBase.id = rs.getInt( 1 ) ;
			userBase.name = rs.getString( 2 ) ;
			userBase.gender = rs.getString( 3 ) ;
			userBase.age = rs.getShort( 4 ) ;
			userBase.address = rs.getString( 5 ) ;
			userBase.lvl = rs.getInt( 6 ) ;
			userBaseListForSelectOutput.add(userBase) ;
		}
		rs.close();
		stmt.close();
		return userBaseListForSelectOutput.size();
	}

	// SELECT gender,count(*) FROM user_base GROUP BY gender
	public static int SELECT_gender_j_count_ALL_FROM_user_base_GROUP_BY_gender( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput ) throws Exception {
		Statement stmt = conn.createStatement() ;
		ResultSet rs = stmt.executeQuery( "SELECT gender,count(*) FROM user_base GROUP BY gender" ) ;
		while( rs.next() ) {
			UserBaseSAU userBase = new UserBaseSAU() ;
			userBase.gender = rs.getString( 1 ) ;
			userBase._count_ = rs.getInt( 2 ) ;
			userBaseListForSelectOutput.add(userBase) ;
		}
		rs.close();
		stmt.close();
		return userBaseListForSelectOutput.size();
	}

插入

SQL动作语法:

INSERT INTO table_name

SQL动作语法示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user_base" ,
			"sqlactions" : [
				"INSERT INTO user_base @@SELECTSEQ(user_base_seq_id) @@SELECTKEY(id)" ,

自动生成JDBC代码:

	// INSERT INTO user_base @@SELECTSEQ(user_base_seq_id) @@SELECTKEY(id)
	public static int INSERT_INTO_user_base( Connection conn, UserBaseSAU userBase ) throws Exception {
		PreparedStatement prestmt ;
		Statement stmt ;
		ResultSet rs ;
		prestmt = conn.prepareStatement( "INSERT INTO user_base (name,gender,age,address,lvl) VALUES (?,?,?,?,?)" ) ;
		prestmt.setString( 1, userBase.name );
		prestmt.setString( 2, userBase.gender );
		prestmt.setShort( 3, userBase.age );
		prestmt.setString( 4, userBase.address );
		prestmt.setInt( 5, userBase.lvl );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		if( count != 1 )
			return count;
		
		stmt = conn.createStatement() ;
		rs = stmt.executeQuery( "SELECT LAST_INSERT_ID()" ) ;
		rs.next();
		userBase.id = rs.getInt( 1 ) ;
		rs.close();
		stmt.close();
		
		return count;
	}

更新

SQL动作语法:

UPDATE table_name
    SET column_name = [?|const|column_name2] [,...]
    [ WHERE column_name [=|<>|>|>=|<|<=] [const|column_name2] [AND ...] ]

SQL动作语法示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user_base" ,
			"sqlactions" : [
				"UPDATE user_base SET lvl=?" ,
				"UPDATE user_base SET address='calvin address',lvl=10 WHERE name='Calvin'" ,
				"UPDATE user_base SET lvl=? WHERE age>? AND gender=?" ,

自动生成JDBC代码:

	// UPDATE user_base SET lvl=?
	public static int UPDATE_user_base_SET_lvl_E_( Connection conn, int _1_lvl_ForSetInput ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "UPDATE user_base SET lvl=?" ) ;
		prestmt.setInt( 1, _1_lvl_ForSetInput );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

	// UPDATE user_base SET address='calvin address',lvl=10 WHERE name='Calvin'
	public static int UPDATE_user_base_SET_address_E_calvin_address_j_lvl_E_10_WHERE_name_E_Calvin_( Connection conn ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "UPDATE user_base SET address='calvin address',lvl=10 WHERE name='Calvin'" ) ;
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

	// UPDATE user_base SET lvl=? WHERE age>? AND gender=?
	public static int UPDATE_user_base_SET_lvl_E_WHERE_age_GT_AND_gender_E_( Connection conn, int _1_lvl_ForSetInput, short _1_age_ForWhereInput, String _2_gender_ForWhereInput ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "UPDATE user_base SET lvl=? WHERE age>? AND gender=?" ) ;
		prestmt.setInt( 1, _1_lvl_ForSetInput );
		prestmt.setShort( 2, _1_age_ForWhereInput );
		prestmt.setString( 3, _2_gender_ForWhereInput );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

删除

SQL动作语法:

DELETE FROM table_name
    [ WHERE column_name [=|<>|>|>=|<|<=] [const|column_name2] [AND ...] ]

SQL动作语法示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user_base" ,
			"sqlactions" : [
				"DELETE FROM user_base" ,
				"DELETE FROM user_base WHERE name='Calvin'" ,
				"DELETE FROM user_base WHERE age<>? AND gender<>?"

自动生成JDBC代码:

	// DELETE FROM user_base
	public static int DELETE_FROM_user_base( Connection conn ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "DELETE FROM user_base" ) ;
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

	// DELETE FROM user_base WHERE name='Calvin'
	public static int DELETE_FROM_user_base_WHERE_name_E_Calvin_( Connection conn ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "DELETE FROM user_base WHERE name='Calvin'" ) ;
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

	// DELETE FROM user_base WHERE age<>? AND gender<>?
	public static int DELETE_FROM_user_base_WHERE_age_NE_AND_gender_NE_( Connection conn, short _1_age, String _2_gender ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "DELETE FROM user_base WHERE age<>? AND gender<>?" ) ;
		prestmt.setShort( 1, _1_age );
		prestmt.setString( 2, _2_gender );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

注意:数据库连接配置文件sqlaction.conf.json一般放在JAVA包目录里,以便于自动生成的类打包。

3.4. 自动生成JDBC代码的规则

工具sqlaction读取数据库中的表结构元信息和SQL动作配置文件sqlaction.conf.json,在执行目录里自动生成JAVA类源代码文件XxxSao.javaXxxSau.java。类源代码文件XxxSao.java包含数据库表实体信息(字段映射属性)和SQL动作对应方法,每次运行sqlaction都会刷新该类源代码文件,所以不要修改此文件。类源代码文件XxxSau.java包含用户自定义代码,首次运行sqlaction会生成该类源代码文件,所以用户定制代码可写到此文件中。

数据库表字段映射属性由数据库中的表结构元信息映射生成,转换规则见前面的数据库字段类型与sqlaction的JAVA变量类型映射表。如果DDL中有comment,则在表实体类的对应属性后面加注释。

SQL动作对应缺省方法名为SQL转换而来,具体算法为所有非字母数字字符都转换为'_',合并多个'_'为一个。

方法前的注释是原SQL,以便于对照和定位。

方法的第一个参数是数据库连接对象,可以和连接池框架结合使用。

如果SQL动作涉及输出,自动生成的代码在SQL执行后,根据解析出来的输出项(SELECT)自动生成getString等代码,方法参数中也要求给予以便于输出,按表实体类列表对象排列,SQL JOIN多表对应多个表实体类列表对象。

如果SQL动作涉及输入,自动生成的代码将使用JDBC的prepareStatement,并根据解析出来的输入项(SET、WHERE)自动生成setString等代码,方法参数中也要求给予以便于输入,按字段名排列。如果没有输入则使用createStatement。

如果是查询SQL,JAVA方法返回表实体类列表大小。如果是插入、更新、删除SQL,JAVA方法返回受影响记录条数。

表实体类属性列表中额外有"int count ;",用于查询COUNT(*)时存储输出结果用。

插入方法中会自动识别忽略自增类型。

就这么简单!

3.5. 配置元

SQL动作的最后面可追加一些以"@@"开头的配置元以实现一些额外的功能。

3.5.1. 自定义SQL动作方法名

允许自定义SQL动作方法名,在SQL动作配置中追加元信息"@@METHOD(自定义方法名)",如:

				"SELECT user_base.name,user_base.address,user_order.item_name,user_order.amount,user_order.total_price
					FROM user_base,user_order
					WHERE user_base.name=? AND user_base.id=user_order.user_id
					@@METHOD(queryUserAndOrderByName)" ,

自动生成代码如下:

	public static int queryUserAndOrderByName( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput, List<UserOrderSAU> userOrderListForSelectOutput, String _1_UserBaseSAU_name ) throws Exception {
		...
		return userBaseListForSelectOutput.size();
	}

3.5.2. 抽象统一了自增字段和序列两大数据库阵营对主键值的赋值

在INSERT时对主键值的赋值,一些拥有自增类型的数据库如MySQL只要在DDL里指明AUTO_INCREMENT,插入时会自动取当前值自增一作为新记录该字段的插入值,插入成功后调用SELECT LAST_INSERT_ID()回取出来告知应用,另一些拥有序列(SEQUENCE)的数据库如Oracle在插入前获取序列的下一个值作为新记录该字段的插入值。sqlaction为了统一前面两种对主键值的赋值在使用层面的抽象,定义了两个配置元@@SELECTKEY(字段名)@@SELECTSEQ(序列名)

示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user" ,
			"sqlactions" : [
				...
				"INSERT INTO user_base @@SELECTKEY(id) @@SELECTSEQ(user_base_seq_id)" ,

当在MySQL等有自增功能的DBMS中,sqlaction处理sqlaction.conf.json自动生成的JDBC代码中,在INSERT操作完后,追加生成调用"SELECT LAST_INSERT_ID()"的代码,取得自增主键值,赋值到主键字段中:

	public static int INSERT_INTO_user_base( Connection conn, UserBaseSAU userBase ) throws Exception {
		PreparedStatement prestmt ;
		Statement stmt ;
		ResultSet rs ;
		prestmt = conn.prepareStatement( "INSERT INTO user_base (name,gender,age,address,lvl) VALUES (?,?,?,?,?)" ) ;
		prestmt.setString( 1, userBase.name );
		prestmt.setString( 2, userBase.gender );
		prestmt.setShort( 3, userBase.age );
		prestmt.setString( 4, userBase.address );
		prestmt.setInt( 5, userBase.lvl );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		if( count != 1 )
			return count;
		
		stmt = conn.createStatement() ;
		rs = stmt.executeQuery( "SELECT LAST_INSERT_ID()" ) ;
		rs.next();
		userBase.id = rs.getInt( 1 ) ;
		rs.close();
		stmt.close();
		
		return count;
	}

当在PostgreSQL等没有自增功能但有序列的DBMS中,sqlaction处理sqlaction.conf.json自动生成的JDBC代码中,在INSERT操作前,调用"select user_id_seq.nextval from dual;"取得序列值,赋值到主键字段中,然后再INSERT:

	public static int INSERT_INTO_user_base( Connection conn, UserBaseSAU userBase ) throws Exception {
		PreparedStatement prestmt ;
		Statement stmt ;
		ResultSet rs ;
		stmt = conn.createStatement() ;
		rs = stmt.executeQuery( "SELECT NEXTVAL('user_base_seq_id')" ) ;
		rs.next();
		userBase.id = rs.getInt( 1 ) ;
		rs.close();
		stmt.close();
		
		prestmt = conn.prepareStatement( "INSERT INTO user_base (id,name,gender,age,address,lvl) VALUES (?,?,?,?,?,?)" ) ;
		prestmt.setInt( 1, userBase.id );
		prestmt.setString( 2, userBase.name );
		prestmt.setString( 3, userBase.gender );
		prestmt.setShort( 4, userBase.age );
		prestmt.setString( 5, userBase.address );
		prestmt.setInt( 6, userBase.lvl );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

注意:没有拿Oracle作例子是因为sqlaction对Oracle支持还有问题,主要是Oracle表名的大写约束。

3.5.3. 抽象统一了物理分页功能,原生自带通用分页能力

不同数据库的分页SQL写法各异,sqlaction为了统一所有分页SQL,定义了两个配置元@@PAGEKEY(分页字段)@@PAGESORT(ASC|DESC)

在SQL动作追加分页键配置元"@@PAGEKEY(...)"以自动生成分页代码,还可以可选的加上排序配置元"@@PAGESORT(ASC|DESC)",如:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user" ,
			"sqlactions" : [
				...
				"SELECT * FROM user_order @@PAGEKEY(id)" ,
				"SELECT * FROM user_order WHERE item_name<>'' @@PAGEKEY(id) @@PAGESORT(DESC)" ,

当数据库为MySQL时自动生成的JAVA代码如下:

	// SELECT * FROM user_order @@PAGEKEY(id)
	public static int SELECT_ALL_FROM_user_order_PAGEKEY_id( Connection conn, List<UserOrderSAU> userOrderListForSelectOutput, int _1_pageSize, int _2_pageNum ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT * FROM user_order WHERE id>=(SELECT id FROM user_order ORDER BY id LIMIT ?,1) ORDER BY id LIMIT ?" ) ;
		prestmt.setInt( 1, _1_pageSize*(_2_pageNum-1) );
		prestmt.setInt( 2, _1_pageSize );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserOrderSAU userOrder = new UserOrderSAU() ;
			userOrder.id = rs.getInt( 1 ) ;
			userOrder.userId = rs.getInt( 2 ) ;
			userOrder.itemName = rs.getString( 3 ) ;
			userOrder.amount = rs.getInt( 4 ) ;
			userOrder.totalPrice = rs.getDouble( 5 ) ;
			userOrderListForSelectOutput.add(userOrder) ;
		}
		rs.close();
		prestmt.close();
		return userOrderListForSelectOutput.size();
	}

	// SELECT * FROM user_order WHERE item_name<>'' @@PAGEKEY(id) @@PAGESORT(DESC)
	public static int SELECT_ALL_FROM_user_order_WHERE_item_name_NE__PAGEKEY_id( Connection conn, List<UserOrderSAU> userOrderListForSelectOutput, int _1_pageSize, int _2_pageNum ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT * FROM user_order WHERE id<=(SELECT id FROM user_order ORDER BY id DESC LIMIT ?,1) AND item_name<>'' ORDER BY id DESC LIMIT ?" ) ;
		prestmt.setInt( 1, _1_pageSize*(_2_pageNum-1) );
		prestmt.setInt( 2, _1_pageSize );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserOrderSAU userOrder = new UserOrderSAU() ;
			userOrder.id = rs.getInt( 1 ) ;
			userOrder.userId = rs.getInt( 2 ) ;
			userOrder.itemName = rs.getString( 3 ) ;
			userOrder.amount = rs.getInt( 4 ) ;
			userOrder.totalPrice = rs.getDouble( 5 ) ;
			userOrderListForSelectOutput.add(userOrder) ;
		}
		rs.close();
		prestmt.close();
		return userOrderListForSelectOutput.size();
	}

当数据库为PostgrelSQL时自动生成的JAVA代码如下:

	// SELECT * FROM user_order @@PAGEKEY(id)
	public static int SELECT_ALL_FROM_user_order_PAGEKEY_id( Connection conn, List<UserOrderSAU> userOrderListForSelectOutput, int _1_pageSize, int _2_pageNum ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT * FROM user_order OFFSET ? LIMIT ?" ) ;
		prestmt.setInt( 1, _1_pageSize*(_2_pageNum-1) );
		prestmt.setInt( 2, _1_pageSize );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserOrderSAU userOrder = new UserOrderSAU() ;
			userOrder.id = rs.getInt( 1 ) ;
			userOrder.userId = rs.getInt( 2 ) ;
			userOrder.itemName = rs.getString( 3 ) ;
			userOrder.amount = rs.getInt( 4 ) ;
			userOrder.totalPrice = rs.getDouble( 5 ) ;
			userOrderListForSelectOutput.add(userOrder) ;
		}
		rs.close();
		prestmt.close();
		return userOrderListForSelectOutput.size();
	}

	// SELECT * FROM user_order WHERE item_name<>'' @@PAGEKEY(id) @@PAGESORT(DESC)
	public static int SELECT_ALL_FROM_user_order_WHERE_item_name_NE__PAGEKEY_id( Connection conn, List<UserOrderSAU> userOrderListForSelectOutput, int _1_pageSize, int _2_pageNum ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT * FROM user_order WHERE item_name<>'' OFFSET ? LIMIT ?" ) ;
		prestmt.setInt( 1, _1_pageSize*(_2_pageNum-1) );
		prestmt.setInt( 2, _1_pageSize );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserOrderSAU userOrder = new UserOrderSAU() ;
			userOrder.id = rs.getInt( 1 ) ;
			userOrder.userId = rs.getInt( 2 ) ;
			userOrder.itemName = rs.getString( 3 ) ;
			userOrder.amount = rs.getInt( 4 ) ;
			userOrder.totalPrice = rs.getDouble( 5 ) ;
			userOrderListForSelectOutput.add(userOrder) ;
		}
		rs.close();
		prestmt.close();
		return userOrderListForSelectOutput.size();
	}

其它支持的数据库还有Oracle、Sqlite、SqlServer。

应用代码如下:

			for( int pageNum=1 ; ; pageNum++ ) {
				userOrderListForSelectOutput = new LinkedList<UserOrderSAU>() ;
				nret = UserOrderSAU.SELECT_ALL_FROM_user_order_PAGEKEY_id( conn, userOrderListForSelectOutput, 3, pageNum ) ;
				if( nret < 0 ) {
					System.out.println( "\t" + "SELECT_ALL_FROM_user_order_PAGEKEY_id failed["+nret+"]" );
					return -23;
				} else {
					System.out.println( "\t" + "SELECT_ALL_FROM_user_order_PAGEKEY_id ok , ["+userOrderListForSelectOutput.size()+"]records" );
					if( userOrderListForSelectOutput.size() == 0 )
						break;
					for( UserOrderSAU o : userOrderListForSelectOutput ) {
						System.out.println( "\t\t" + "id["+o.id+"] userId["+o.userId+"] itemName["+o.itemName+"] amount["+o.amount+"] totalPrice["+o.totalPrice+"]" );
					}
				}
			}
			
			for( int pageNum=1 ; ; pageNum++ ) {
				userOrderListForSelectOutput = new LinkedList<UserOrderSAU>() ;
				nret = UserOrderSAU.SELECT_ALL_FROM_user_order_WHERE_item_name_NE__PAGEKEY_id( conn, userOrderListForSelectOutput, 3, pageNum ) ;
				if( nret < 0 ) {
					System.out.println( "\t" + "SELECT_ALL_FROM_user_order_WHERE_item_name_NE__PAGEKEY_id failed["+nret+"]" );
					return -24;
				} else {
					System.out.println( "\t" + "SELECT_ALL_FROM_user_order_WHERE_item_name_NE__PAGEKEY_id ok , ["+userOrderListForSelectOutput.size()+"]records" );
					if( userOrderListForSelectOutput.size() == 0 )
						break;
					for( UserOrderSAU o : userOrderListForSelectOutput ) {
						System.out.println( "\t\t" + "id["+o.id+"] userId["+o.userId+"] itemName["+o.itemName+"] amount["+o.amount+"] totalPrice["+o.totalPrice+"]" );
					}
				}
			}

注意:目前只支持单表单分页键分页,SQL动作语法如下:

SELECT [*|column_name[,...]]
    [ /* hint */ ]
    FROM table_name
    [ WHERE column_name [=|<>|>|>=|<|<=] [?|const|column_name2] [AND ...] ]
    @@PAGEKEY(column_name) [ @@PAGESORT(ASC|DESC) ]

3.5.4. 拦截器

3.5.4.1. SQL拦截器

如果需要SQL真正执行前微调一下SQL(如分库分表修改hint),可加入拦截器配置元"@@STATEMENT_INTERCEPTOR(拦截器方法名,填空则自动生成一个)",配置示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user" ,
			"sqlactions" : [
				...
				"SELECT /* blablabla~ */ * FROM user_order @@STATEMENT_INTERCEPTOR()" ,

或自定义拦截器方法名

				"SELECT u.name,u.address,o.* FROM user_base u,user_order o WHERE u.name=? AND u.id=o.user_id @@STATEMENT_INTERCEPTOR(statementInterceptorForQueryUserAndOrderByName)" ,

首次生成XxxSau.java中出现

public class UserOrderSAU extends UserOrderSAO {
	
	public static String STATEMENT_INTERCEPTOR_for_SELECT_HT_blablabla_TH_ALL_FROM_user_order( String statementSql ) {
		
		return statementSql;
	}
	
	public static String statementInterceptorForQueryUserAndOrderByName( String statementSql, String _1_UserBaseSAU_name ) {
		
		return statementSql;
	}

拦截器方法输入参数与SQL动作方法的一致,可根据每次调用SQL动作方法时,拦截器方法拿到相同的输入参数对SQL进行修改,如hint。

注意:由于XxxSau.java只有在首次执行sqlaction才会自动生成,后面增加的拦截器方法框架源代码虽然不会自动添加到XxxSau.java中,但会作为注释出现在XxxSao.java中,方便开发者复制粘贴过去。

3.6. 高级模式

之前介绍的普通模式支持SQL语法有限,主要用于联机服务(TP场景),如果要支持复杂SQL(AP场景)可使用高级模式。

sqlaction.conf.json的SQL动作最后面加上配置元@@ADVANCEDMODE即可开启这条SQL动作的高级模式。高级模式对SQL书写几乎没有任何限制,但引入一定的配置复杂性,示例:

{
	"database" : "calvindb" ,
	"tables" : [
		{
			"table" : "user_base" ,
			"sqlactions" : [
				"SELECT * FROM user_base" ,
				"SELECT * FROM user_base WHERE name=?" ,
				"SELECT name,address FROM user_base WHERE age<=? AND gender=?" ,
				"SELECT * FROM user_base ORDER BY name DESC" ,
				"SELECT gender,count(*) FROM user_base GROUP BY gender" ,
				"INSERT INTO user_base @@SELECTSEQ(user_base_seq_id) @@SELECTKEY(id)" ,
				"UPDATE user_base SET lvl=?" ,
				"UPDATE user_base SET address='calvin address',lvl=10 WHERE name='Calvin'" ,
				"UPDATE user_base SET lvl=? WHERE age>? AND gender=?" ,
				"DELETE FROM user_base" ,
				"DELETE FROM user_base WHERE name='Calvin'" ,
				"DELETE FROM user_base WHERE age<>? AND gender<>?"
			]
		} ,
		{
			"table" : "user_order" ,
			"sqlactions" : [
				"SELECT MIN(total_price) #{SqlActionTest.minTotalPrice:double}, MAX(total_price) #{SqlActionTest.maxTotalPrice:double}, COUNT(*) #{UserOrderSAU._count_}
					FROM user_order #{user_order}
					@@ADVANCEDMODE" ,
				"SELECT user_base.name				#{UserBaseSAU.name}
					,user_order.item_name			#{UserOrderSAU.itemName}
					,SUM(user_order.amount)			#{UserOrderSAU.amount}
					,SUM(user_order.total_price)	#{UserOrderSAU.totalPrice}
					FROM user_base					#{user_base}
						,user_order					#{user_order}
					WHERE user_order.user_id IN (
												SELECT id
												FROM user_base
												WHERE id>=?		#{UserOrderSAU.id}
											)
						AND user_order.user_id=user_base.id
					GROUP BY user_base.name
					ORDER BY user_base.name
					@@ADVANCEDMODE @@METHOD(statUsersAmountAndTotalPrice)" ,
				"DELETE FROM user_order WHERE user_id=? #{UserOrderSAU.userId} @@ADVANCEDMODE @@METHOD(removeUserOrder)"

SQL的FROM后必须配置有#{表名},在SELECT后的每一个字段后都要有#{SAU类名.类属性名}绑定以便查询结果输出,在WHERE或SET后的每一个字段后都要有#{SAU类名.类属性名}绑定以便查询请求输入,给足了这些信息就能让工具自动生成完整的JDBC源代码了。

之前示例SQL动作生成的JDBC代码如下:

	// SELECT MIN(total_price) #{SqlActionTest.minTotalPrice:double}, MAX(total_price) #{SqlActionTest.maxTotalPrice:double}, COUNT(*) #{UserOrderSAU._count_}
	// 					FROM user_order #{user_order}
	// 					@@ADVANCEDMODE
	public static int SELECT_MIN_total_price_j_MAX_total_price_j_COUNT_ALL_FROM_user_order( Connection conn, List<UserOrderSAU> userOrderListForSelectOutput, List<SqlActionTest> sqlActionTestListForSelectOutput ) throws Exception {
		Statement stmt = conn.createStatement() ;
		ResultSet rs = stmt.executeQuery( "SELECT MIN(total_price) , MAX(total_price) , COUNT(*) FROM user_order" ) ;
		while( rs.next() ) {
			UserOrderSAU userOrder = new UserOrderSAU() ;
			SqlActionTest sqlActionTest = new SqlActionTest() ;
			sqlActionTest.minTotalPrice = rs.getDouble( 1 ) ;
			sqlActionTest.maxTotalPrice = rs.getDouble( 2 ) ;
			userOrder._count_ = rs.getInt( 3 ) ;
			userOrderListForSelectOutput.add(userOrder) ;
			sqlActionTestListForSelectOutput.add(sqlActionTest) ;
		}
		rs.close();
		stmt.close();
		return userOrderListForSelectOutput.size();
	}

	// SELECT user_base.name				#{UserBaseSAU.name}
	// 					,user_order.item_name			#{UserOrderSAU.itemName}
	// 					,SUM(user_order.amount)			#{UserOrderSAU.amount}
	// 					,SUM(user_order.total_price)	#{UserOrderSAU.totalPrice}
	// 					FROM user_base					#{user_base}
	// 						,user_order					#{user_order}
	// 					WHERE user_order.user_id IN (
	// 												SELECT id
	// 												FROM user_base
	// 												WHERE id>=?		#{UserOrderSAU.id}
	// 											)
	// 						AND user_order.user_id=user_base.id
	// 					GROUP BY user_base.name
	// 					ORDER BY user_base.name
	// 					@@ADVANCEDMODE @@METHOD(statUsersAmountAndTotalPrice)
	public static int statUsersAmountAndTotalPrice( Connection conn, List<UserBaseSAU> userBaseListForSelectOutput, List<UserOrderSAU> userOrderListForSelectOutput, int _1_UserOrderSAU_id ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "SELECT user_base.name ,user_order.item_name ,SUM(user_order.amount) ,SUM(user_order.total_price) FROM user_base ,user_order WHERE user_order.user_id IN ( SELECT id FROM user_base WHERE id>=? ) AND user_order.user_id=user_base.id GROUP BY user_base.name ORDER BY user_base.name" ) ;
		prestmt.setInt( 1, _1_UserOrderSAU_id );
		ResultSet rs = prestmt.executeQuery() ;
		while( rs.next() ) {
			UserBaseSAU userBase = new UserBaseSAU() ;
			UserOrderSAU userOrder = new UserOrderSAU() ;
			userBase.name = rs.getString( 1 ) ;
			userOrder.itemName = rs.getString( 2 ) ;
			userOrder.amount = rs.getInt( 3 ) ;
			userOrder.totalPrice = rs.getDouble( 4 ) ;
			userBaseListForSelectOutput.add(userBase) ;
			userOrderListForSelectOutput.add(userOrder) ;
		}
		rs.close();
		prestmt.close();
		return userBaseListForSelectOutput.size();
	}
	
	// DELETE FROM user_order WHERE user_id=? #{UserOrderSAU.userId} @@ADVANCEDMODE @@METHOD(removeUserOrder)
	public static int removeUserOrder( Connection conn, int _1_userId ) throws Exception {
		PreparedStatement prestmt = conn.prepareStatement( "DELETE FROM user_order WHERE user_id=?" ) ;
		prestmt.setInt( 1, _1_userId );
		int count = prestmt.executeUpdate() ;
		prestmt.close();
		return count;
	}

4. 为什么这样设计?

数据库应用接口层框架/工具对于表结构的配置源的有两派思路,一派是定义在配置文件中,好处是可以做不同DBMS的统一规范,如同一种数据类型的统一表达,坏处是与数据库之间同步较麻烦,另一派是定义在数据库中,需要用时读数据库中的元信息,好处是可以利用数据库设计工具,图形化界面管理表结构,还能自动生成E-R图,坏处是不同DBMS存在标准差异。sqlaction采用后一派思路,在数据类型与JAVA属性类型之间建立多DBMS映射表来解决标准差异。

很多数据库持久化框架对于SQL动作都定义了一套自己的动作语法标准,sqlaction坚持采用原SQL来配置,减少开发人员学习负担。在自增字段或序列对主键值的赋值和分页查询等差异问题上,sqlaction定义一套兼容标准来统一SQL动作表达。

sqlaction读取数据库连接配置文件和SQL动作配置文件支持本目录或上级某一层目录,这样就可以通过存放位置灵活适应各种各样的项目代码结构,比如整个项目只对应一个数据库实例,或者项目中每个模块对应一个数据库实例。

为了兼顾修改刷新和定制代码留存两全问题上,sqlaction设计了两个JAVA类源代码文件XxxSAO.javaXxxSAU.java,并规定每次执行工具强制XxxSAO.java,但只有首次执行才创建XxxSAU.java,这样定制代码只要写在XxxSAU.java中就不怕被刷新覆盖,但又通过刷新XxxSAO.java来体现每次修改。由于XxxSAU类继承自XxxSAO类,所以一旦重载了方法,XxxSAU定制内容优先。

sqlaction的SQL集合都放在一个文件中,便于审计表操作,如很容易查出是否有操作游离于索引之外,但生产部署又不需要其SQL动作配置文件,防止被人篡改。

sqlaction坚持采用最小化配置原则,规避一切冗余配置,尽力减少开发人员工作量,推荐用缺省值工作,如果需要自定义再提供额外的配置,如SQL动作方法名默认情况下无需配置,按照缺省规则就能自动根据SQL生成一个含义清晰的名字,开发人员无须为每个SQL动作必须配置方法名,甚至无需繁复的XML替代JAVA语言定义方法的输入输出参数(MyBatis),而且配置错误时只有在运行期才告知。

sqlaction朴素,无需MyBatisHibernate那么复杂、炫耀技术之嫌;sqlaction简单,只是代替手工而自动生成JDBC代码段落,没有运行时框架,不做其它事情(如连接池、分布式事务控制等),保持代码架构简单、透明、可控和高效,便于和其它数据库框架/工具协同工作;sqlaction对开发友好,大部分错误都能在预处理期或编译期发现和提示,而不像某些“高端”框架只有到了运行期才警示开发问题。

简洁是优秀工具的特质,而不是为了解决一种复杂性而带来另一种复杂性。

5. 与MyBatis的开发量比较

MyBatis sqlaction
每个项目手工开发量
编写数据库连接信息配置文件 编写数据库连接信息配置文件
每个表手工开发量
编写表操作配置文件 编写表操作配置文件
编写表实体类文件 sqlaction原生自动生成
编写表操作接口类文件 sqlaction不需要
MyBatis不需要 sqlaction处理命令行:
java -Dfile.encoding=UTF-8 -classpath "D:\Work\sqlaction\sqlaction.jar;D:\Work\mysql-connector-java-8.0.15\mysql-connector-java-8.0.15.jar" xyz.calvinwilliams.sqlaction.gencode.SqlActionGencode

以上表格可以看出,每个项目都要配置一遍的数据库连接信息配置文件,MyBatis比sqlaction大了一倍,日常表配置/源文件,MyBatis要手工编写三个文件(存在很多冗余工作),sqlaction只需手工编写一个文件即可(一点冗余都没有,最大化提升开发效能)。

6. 与MyBatis的性能比较

由于sqlaction自动生成的JDBC代码,与手工代码基本无异,没有低效的反射,没有多坑的热修改字节码,所以稳定性和运行性能都非常出色,下面是sqlactionMyBatis的性能测试。

CPU : Intel Core i5-7500 3.4GHz 3.4GHz 内存 : 16GB 操作系统 : WINDOWS 10 JAVA开发工具 : Eclipse 2018-12 数据库 : MySQL 8.0.15 Community Server 数据库连接地址 : 127.0.0.1:3306

DDL

CREATE TABLE `sqlaction_benchmark` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
  `name` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '英文名',
  `name_cn` varchar(128) COLLATE utf8mb4_bin NOT NULL COMMENT '中文名',
  `salary` decimal(12,2) NOT NULL COMMENT '薪水',
  `birthday` date NOT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42332 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

6.1. 准备sqlaction

手工编写数据库连接配置文件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_benchmark" ,
			"sqlactions" : [
				"INSERT INTO sqlaction_benchmark" ,
				"UPDATE sqlaction_benchmark SET salary=? WHERE name=?" ,
				"SELECT * FROM sqlaction_benchmark WHERE name=?" ,
				"SELECT * FROM sqlaction_benchmark" ,
				"DELETE FROM sqlaction_benchmark WHERE name=?" ,
				"DELETE FROM sqlaction_benchmark"
			]
		}
	] ,
	"javaPackage" : "xyz.calvinwilliams.sqlaction.benchmark"
}

运行工具sqlaction,自动生成SqlactionBenchmarkSAO.java

手工编写性能测试应用类SqlActionBenchmarkCrud.java

/*
 * sqlaction - SQL action object auto-gencode tool based JDBC for Java
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * See the file LICENSE in base directory.
 */

package xyz.calvinwilliams.sqlaction.benchmark;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

public class SqlActionBenchmarkCrud {

	public static void main(String[] args) {
		Connection					conn = null ;
		SqlactionBenchmarkSAO		sqlactionBenchmark ;
		List<SqlactionBenchmarkSAO>	sqlactionBenchmarkList ;
		long						beginMillisSecondstamp ;
		long						endMillisSecondstamp ;
		double						elpaseSecond ;
		long						i , j , k ;
		long						count , count2 , count3 ;
		int							rows = 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);
			
			sqlactionBenchmark = new SqlactionBenchmarkSAO() ;
			sqlactionBenchmark.name = "Calvin" ;
			sqlactionBenchmark.nameCn = "卡尔文" ;
			sqlactionBenchmark.salary = new BigDecimal(0) ;
			long time = System.currentTimeMillis() ;
			sqlactionBenchmark.birthday = new java.sql.Date(time) ;
			count = 500 ;
			count2 = 5 ;
			count3 = 1000 ;
			
			rows = SqlactionBenchmarkSAO.DELETE_FROM_sqlaction_benchmark( conn ) ;
			conn.commit();
			
			// benchmark for INSERT
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( i = 0 ; i < count ; i++ ) {
				sqlactionBenchmark.name = "Calvin"+i ;
				sqlactionBenchmark.nameCn = "卡尔文"+i ;
				rows = SqlactionBenchmarkSAO.INSERT_INTO_sqlaction_benchmark( conn, sqlactionBenchmark ) ;
				if( rows != 1 ) {
					System.out.println( "SqlactionBenchmarkSAO.INSERT_INTO_sqlaction_benchmark failed["+rows+"]" );
					return;
				}
				if( i % 10 == 0 ) {
					conn.commit();
				}
			}
			conn.commit();
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All sqlaction INSERT done , count["+count+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for UPDATE
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( i = 0 ; i < count ; i++ ) {
				rows = SqlactionBenchmarkSAO.UPDATE_sqlaction_benchmark_SET_salary_E_WHERE_name_E_( conn, new BigDecimal(i), "Calvin"+i ) ;
				if( rows != 1 ) {
					System.out.println( "SqlactionBenchmarkSAO.UPDATE_sqlaction_benchmark_SET_salary_E_WHERE_name_E_ failed["+rows+"]" );
					return;
				}
				if( i % 10 == 0 ) {
					conn.commit();
				}
			}
			conn.commit();
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All sqlaction UPDATE WHERE done , count["+count+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for SELECT ... WHERE ...
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( j = 0 ; j < count2 ; j++ ) {
				for( i = 0 ; i < count ; i++ ) {
					sqlactionBenchmarkList = new LinkedList<SqlactionBenchmarkSAO>() ;
					rows = SqlactionBenchmarkSAO.SELECT_ALL_FROM_sqlaction_benchmark_WHERE_name_E_( conn, sqlactionBenchmarkList, "Calvin"+i ) ;
					if( rows != 1 ) {
						System.out.println( "SqlactionBenchmarkSAO.SELECT_ALL_FROM_sqlaction_benchmark_WHERE_name_E_ failed["+rows+"]" );
						return;
					}
				}
			}
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All sqlaction SELECT WHERE done , count2["+count2+"] count["+count+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for SELECT
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( k = 0 ; k < count3 ; k++ ) {
				sqlactionBenchmarkList = new LinkedList<SqlactionBenchmarkSAO>() ;
				rows = SqlactionBenchmarkSAO.SELECT_ALL_FROM_sqlaction_benchmark( conn, sqlactionBenchmarkList ) ;
				if( rows != count ) {
					System.out.println( "SqlactionBenchmarkSAO.SELECT_ALL_FROM_sqlaction_benchmark failed["+rows+"]" );
					return;
				}
			}
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All sqlaction SELECT to LIST done , count3["+count3+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for DELETE
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( i = 0 ; i < count ; i++ ) {
				rows = SqlactionBenchmarkSAO.DELETE_FROM_sqlaction_benchmark_WHERE_name_E_( conn, "Calvin"+i ) ;
				if( rows != 1 ) {
					System.out.println( "SqlactionBenchmarkSAO.DELETE_FROM_sqlaction_benchmark_WHERE_name_E_ failed["+rows+"]" );
					return;
				}
				if( i % 10 == 0 ) {
					conn.commit();
				}
			}
			conn.commit();
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All sqlaction DELETE WHERE done , count["+count+"] elapse["+elpaseSecond+"]s" );
		} catch(Exception e) {
			e.printStackTrace();
			try {
				conn.rollback();
			} catch (Exception e2) {
				e.printStackTrace();
				return;
			}
		} finally {
			try {
				conn.close();
			} catch (Exception e2) {
				e2.printStackTrace();
				return;
			}
		}
		
		return;
	}
}

6.2. 准备MyBatis

手工编写数据库连接配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<settings>
		<setting name="cacheEnabled" value="false" />
	</settings>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://127.0.0.1:3306/calvindb?serverTimezone=GMT" />
				<property name="username" value="calvin" />
				<property name="password" value="calvin" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="mybatis-mapper.xml" />
	</mappers>
</configuration>

手工编写mapper配置文件mybatis-mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAOMapper">
	<insert id="insertOne" parameterType="xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAO">
		INSERT INTO sqlaction_benchmark (name,name_cn,salary,birthday) VALUES( #{name}, #{name_cn}, #{salary}, #{birthday} )
	</insert>
	<update id="updateOneByName" parameterType="xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAO">
		UPDATE sqlaction_benchmark SET salary=#{salary} WHERE name=#{name}
	</update>
	<select id="selectOneByName" parameterType="java.lang.String" resultType="xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAO" flushCache="true" useCache="false">
		SELECT * FROM sqlaction_benchmark WHERE name=#{name}
	</select>
	<select id="selectAll" resultType="xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAO" flushCache="true" useCache="false">
		SELECT * FROM sqlaction_benchmark
	</select>
	<delete id="deleteOneByName" parameterType="java.lang.String">
		DELETE FROM sqlaction_benchmark WHERE name=#{name}
	</delete>
	<delete id="deleteAll">
		DELETE FROM sqlaction_benchmark
	</delete>
</mapper>

手工编写数据库表实体类SqlactionBenchmarkSAO.java

package xyz.calvinwilliams.mybatis.benchmark;

import java.math.*;

public class SqlactionBenchmarkSAO {

	int				id ; // 编号
	String			name ; // 英文名
	String			name_cn ; // 中文名
	BigDecimal		salary ; // 薪水
	java.sql.Date	birthday ; // 生日

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

}

手工编写数据库表Mapper接口类SqlactionBenchmarkSAOMapper.java

package xyz.calvinwilliams.mybatis.benchmark;

import java.util.*;

public interface SqlactionBenchmarkSAOMapper {
    public void insertOne(SqlactionBenchmarkSAO sqlactionBenchmark);
    public void updateOneByName(SqlactionBenchmarkSAO sqlactionBenchmark);
    public SqlactionBenchmarkSAO selectOneByName(String name);
    public List<SqlactionBenchmarkSAO> selectAll();
    public void deleteOneByName(String name);
    public void deleteAll();
}

手工编写性能测试应用类MyBatisBenchmarkCrud.java

/*
 * sqlaction - SQL action object auto-gencode tool based JDBC for Java
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * See the file LICENSE in base directory.
 */

package xyz.calvinwilliams.mybatis.benchmark;

import java.io.FileInputStream;
import java.math.BigDecimal;
import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAO;
import xyz.calvinwilliams.mybatis.benchmark.SqlactionBenchmarkSAOMapper;

public class MyBatisBenchmarkCrud {

	public static void main(String[] args) {
		SqlSession					session = null ;
		SqlactionBenchmarkSAOMapper	mapper ;
		List<SqlactionBenchmarkSAO>	sqlactionBenchmarkList ;
		long						beginMillisSecondstamp ;
		long						endMillisSecondstamp ;
		double						elpaseSecond ;
		long						i , j , k ;
		long						count , count2 , count3 ;
		
		try {
			FileInputStream in = new FileInputStream("src/main/java/mybatis-config.xml");
			session = new SqlSessionFactoryBuilder().build(in).openSession();
			
			SqlactionBenchmarkSAO	sqlactionBenchmark = new SqlactionBenchmarkSAO() ;
			sqlactionBenchmark.id = 1 ;
			sqlactionBenchmark.name = "Calvin" ;
			sqlactionBenchmark.name_cn = "卡尔文" ;
			sqlactionBenchmark.salary = new BigDecimal(0) ;
			long time = System.currentTimeMillis() ;
			sqlactionBenchmark.birthday = new java.sql.Date(time) ;
			count = 500 ;
			count2 = 5 ;
			count3 = 1000 ;
			
			mapper = session.getMapper(SqlactionBenchmarkSAOMapper.class) ;
			
			mapper.deleteAll();
			session.commit();
			
			// benchmark for INSERT
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( i = 0 ; i < count ; i++ ) {
				sqlactionBenchmark.name = "Calvin"+i ;
				sqlactionBenchmark.name_cn = "卡尔文"+i ;
				mapper.insertOne(sqlactionBenchmark);
				if( i % 10 == 0 ) {
					session.commit();
				}
			}
			session.commit();
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All mybatis INSERT done , count["+count+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for UPDATE
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( i = 0 ; i < count ; i++ ) {
				sqlactionBenchmark.name = "Calvin"+i ;
				sqlactionBenchmark.salary = new BigDecimal(i) ;
				mapper.updateOneByName(sqlactionBenchmark);
				if( i % 10 == 0 ) {
					session.commit();
				}
			}
			session.commit();
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All mybatis UPDATE done , count["+count+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for SELECT ... WHERE ...
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( j = 0 ; j < count2 ; j++ ) {
				for( i = 0 ; i < count ; i++ ) {
					sqlactionBenchmark = mapper.selectOneByName(sqlactionBenchmark.name) ;
					if( sqlactionBenchmark == null ) {
						System.out.println( "mapper.selectOneByName failed" );
						return;
					}
				}
			}
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All mybatis SELECT WHERE done , count2["+count2+"] count["+count+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for SELECT
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( k = 0 ; k < count3 ; k++ ) {
				sqlactionBenchmarkList = mapper.selectAll() ;
				if( sqlactionBenchmarkList == null ) {
					System.out.println( "mapper.selectAll failed" );
					return;
				}
			}
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All mybatis SELECT to List done , count3["+count3+"] elapse["+elpaseSecond+"]s" );
			
			// benchmark for DELETE
			beginMillisSecondstamp = System.currentTimeMillis() ;
			for( i = 0 ; i < count ; i++ ) {
				sqlactionBenchmark.name = "Calvin"+i ;
				mapper.deleteOneByName(sqlactionBenchmark.name);
				if( i % 10 == 0 ) {
					session.commit();
				}
			}
			session.commit();
			endMillisSecondstamp = System.currentTimeMillis() ;
			elpaseSecond = (endMillisSecondstamp-beginMillisSecondstamp)/1000.0 ;
			System.out.println( "All mybatis DELETE done , count["+count+"] elapse["+elpaseSecond+"]s" );
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

6.3. 测试案例

INSERT表500条记录(每10条提交一次) UPDATE表500条记录(每10条提交一次) SELECT表单条记录500*5次 SELECT表所有记录1000次 DELETE表500条记录(每10条提交一次)

6.4. 测试结果

All sqlaction INSERT done , count[500] elapse[4.742]s
All sqlaction UPDATE WHERE done , count[500] elapse[5.912]s
All sqlaction SELECT WHERE done , count2[5] count[500] elapse[0.985]s
All sqlaction SELECT to LIST done , count3[1000] elapse[1.172]s
All sqlaction DELETE WHERE done , count[500] elapse[5.001]s

All mybatis INSERT done , count[500] elapse[5.869]s
All mybatis UPDATE WHERE done , count[500] elapse[6.921]s
All mybatis SELECT WHERE done , count2[5] count[500] elapse[1.239]s
All mybatis SELECT to List done , count3[1000] elapse[1.792]s
All mybatis DELETE WHERE done , count[500] elapse[5.382]s

All sqlaction INSERT done , count[500] elapse[5.392]s
All sqlaction UPDATE WHERE done , count[500] elapse[5.821]s
All sqlaction SELECT WHERE done , count2[5] count[500] elapse[0.952]s
All sqlaction SELECT to LIST done , count3[1000] elapse[1.15]s
All sqlaction DELETE WHERE done , count[500] elapse[5.509]s

All mybatis INSERT done , count[500] elapse[6.066]s
All mybatis UPDATE WHERE done , count[500] elapse[6.946]s
All mybatis SELECT WHERE done , count2[5] count[500] elapse[1.183]s
All mybatis SELECT to List done , count3[1000] elapse[1.804]s
All mybatis DELETE WHERE done , count[500] elapse[5.958]s

All sqlaction INSERT done , count[500] elapse[5.236]s
All sqlaction UPDATE WHERE done , count[500] elapse[5.84]s
All sqlaction SELECT WHERE done , count2[5] count[500] elapse[0.985]s
All sqlaction SELECT to LIST done , count3[1000] elapse[1.222]s
All sqlaction DELETE WHERE done , count[500] elapse[4.91]s

All mybatis INSERT done , count[500] elapse[5.448]s
All mybatis UPDATE WHERE done , count[500] elapse[7.287]s
All mybatis SELECT WHERE done , count2[5] count[500] elapse[1.149]s
All mybatis SELECT to List done , count3[1000] elapse[1.873]s
All mybatis DELETE WHERE done , count[500] elapse[6.035]s

benchmark_INSERT.png

benchmark_UPDATE_WHERE.png

benchmark_SELECT_WHERE.png

benchmark_SELECT_to_LIST.png

benchmark_DELETE_WHERE.png

从以上性能测试图表中可以看出,sqlaction运行性能比MyBatis快大约20%,这意味着技术选型sqlaction的应用系统的交易延迟比MyBatis有明显优势。

综合开发效能和运行效率,总结:

  • 从测试前准备来看,无论配置文件、源代码文件数量还是大小,sqlaction的工作量只有MyBatis一半,能更快速的展开业务开发,减轻开发人员学习压力和心智负担,且采用的技术更简单更透明更易掌控。
  • 从设计理念来看,sqlaction只是在应用和JDBC之间自动生成了以往需要手工编写的代码,真正运行时与sqlaction无关,性能等价于高效直接的JDBC,理论上不可能有比它更快的数据库持久层类库/工具了。sqlaction只自动生成了SQL处理对应的JAVA方法操作,可以和其它优秀数据库类库/工具(如连接池、分布式事务控制)更容易的集成使用。

7. 后续开发

  1. Eclipse插件触发执行工具sqlaction
  2. 增加缓存
  3. 进一步优化支持Oracle。

8. 关于本项目

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

源码托管地址 : 开源中国github

Apache Maven

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

9. 关于作者

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

通过邮箱联系我 : 网易Gmail

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

自动生成JDBC代码的数据库持久层工具 展开 收起
Java 等 2 种语言
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/calvinwilliams/sqlaction.git
git@gitee.com:calvinwilliams/sqlaction.git
calvinwilliams
sqlaction
sqlaction
release

搜索帮助