文档章节

一种利用JAVA注释支持多行字符串的方法

yong9981
 yong9981
发布于 2017/05/04 15:36
字数 871
阅读 2094
收藏 17

(2018.1.21注:本文已过时,又找到一种好一点的利用注释支持多行字符串的方法,见 再谈让Java支持多行文本 )

从BeetlSql项目将SQL全放在Beetl模板里得到启发,又想到一个比较偏门的用法。以下代码实测通过,详见jSqlBox项目的test\examples\multipleLineSQL\SqlTemplateDemo.java源程序,此示例特殊点在于:运行时必须将此源程序拷贝一份在类根目录(或Maven管理的Resource目录下), 并将后缀".java"改为".sql"

public class SqlTemplateDemo extends TestBase {

	//@formatter:off	
	public static class InsertUser extends SqlTemplate {
		public InsertUser(Object name, Object address, Object age){ 
		/* insert into 
		   users 
		   (username, address, age) */ empty(name,address,age);
		/* values  (?,?,?)*/ 
		}
	}
	
	public static class FindUsers extends SqlTemplate  {
		public FindUsers(Object name, Object age){ 
		/* select count(*) 
		   from
		   users
		  where */
		/* username=? */empty(name); 
		/* and age>? */empty(age);
		/* order by username */  
		}
	}

    public static class GetUserCount extends SqlTemplate  {    
        /* select count(*) 
           from users  */    
    }   
    
	public static class SqlTemplateEndTag{}
	
	@Test
	public void doTest() { 
		Dao.executeInsert(new InsertUser("Tom","BeiJing",10).toString());
		Dao.executeInsert(new InsertUser("Sam","ShangHai",20).toString());
		Assert.assertEquals((Integer) 1,  Dao.queryForInteger(new FindUsers("Sam",15).toString()));
		Assert.assertEquals((Integer) 2,  Dao.queryForInteger(new GetUserCount().toString())); 
	} 	
}

以上代码是利用Java源程序作为模板文件来统一放置SQL,以便于DBA管理。其优点在于:

1)没有引入第三方模板,直接用Java作为模板文件
2)支持多行字符串, 且没有引入IDE插件或Maven编译插件等,通用性好。
3)支持Java方法和模板混用,模板负责存储SQL,Java负责赋值(利用ThreadLocal),互不干拢。
4)public static class的类名即为SQL ID,便于重命名重构和SQL的快速定位(Ctrl+鼠标左键即可),这是文本方式模板无法做到的,后者必须用文本搜索功能才能定位SQL。
缺点是:
1)每次模板更改后,还必须手工拷贝一份同样的文件放在类根目录作为资源文件
2)因为SQL写在注释中, 所以必须利用标记 //@formatter:off 来关闭IDE(我用Eclipse)的格式化功能,防止IDE误格式化。
3)从SQL管理角度出发,模板文件中要注意不要放入过多除SQL以外的无关代码。

以上方法主要应用于SQL模板,但是其它场合如需要用到Java支持多行字符串的,也可以借签。

注:2017.5.17再更新一下,以下是一个通用的支持多行字符串的基类,实现了在开发阶段从源码读取(TextSupport类或其子类在getJavaSrcFolder方法返回值中要给出源码目录),布署阶段从资源文件读取(布署时改一下getJavaSrcFolder方法使其返回空值并拷贝Java模板到类根目类,并改为.txt后缀):

package util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;

/**
 * TextSupport is base class for Java text(multiple line Strings) template.
 */
public class TextSupport {

	public String getJavaSrcFolder() {
		return "F:/your_project_folder/src/test/java/";
	}

	@Override
	public String toString() {
		try {
			if (StringUtils.isEmpty(getJavaSrcFolder()))
				return extractTextFromComments(getTextFromResourceFile());
			else
				return extractTextFromComments(getTextFromJavaFile());
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private String extractTextFromComments(String templateText) {
		String thisPublicStaticClassName = this.getClass().getSimpleName();
		String classText = StringUtils.substringBetween(templateText,
				"public static class " + thisPublicStaticClassName, "*/}");
		if (StringUtils.isEmpty(classText))
			throw new RuntimeException("Can not find text between \"public static class " + thisPublicStaticClassName
					+ " and end tag \"*/}\"");
		StringBuilder sb = new StringBuilder();
		for (String str : StringUtils.substringsBetween(classText + "*/", "/*", "*/"))
			sb.append(str);
		return sb.toString();
	}

	private String getTextFromJavaFile() throws IOException {
		String className = this.getClass().getName(); // aaa.bbb.CCC$DDD
		String fileName = getJavaSrcFolder()
				+ StringUtils.replace(StringUtils.substringBeforeLast(className, "$"), ".", "/") + ".java";
		return FileUtils.readFileToString(new File(fileName));
	}

	private String getTextFromResourceFile() throws IOException {
		String className = StringUtils.substringAfterLast("." + this.getClass().getName(), ".");// CCC$DDD
		String resFile = StringUtils.substringBefore(className, "$") + ".txt";// CCC.txt
		URL url = this.getClass().getResource("/" + resFile);
		if (url == null)
			throw new RuntimeException("Can not find resource file \"" + resFile + "\"");
		return FileUtils.readFileToString(new File(url.getFile()));
	}

	public static void main(String[] args) {
		System.out.println("Text=" + new DemoString());
	}

	//@formatter:off
	public static class DemoString extends TextSupport{   
	 /*   
	  Hello,
	  This is multiple line strings demo
     */} 	
}

 

 

© 著作权归作者所有

共有 人打赏支持
yong9981

yong9981

粉丝 16
博文 9
码字总数 20328
作品 10
南京
私信 提问
加载中

评论(14)

yong9981
yong9981

引用来自“YanbinQ”的评论

注释就可能被格式化,而且不能假定 IDE 就是在 Eclipse,我之前也对此有所探索 https://unmi.cc/java-implement-here-document/,最后还是放弃了。要多行字符串,宁愿用别的语言,如 Scala, 我觉得即使在 Java 项目中放 Scala 代码也无伤大雅。
用/*-开头的注释在Eclipse中就不会被格式化了,其它的IDE没试过。
YanbinQ
YanbinQ
注释就可能被格式化,而且不能假定 IDE 就是在 Eclipse,我之前也对此有所探索 https://unmi.cc/java-implement-here-document/,最后还是放弃了。要多行字符串,宁愿用别的语言,如 Scala, 我觉得即使在 Java 项目中放 Scala 代码也无伤大雅。
yong9981
yong9981
谢谢几位关于groovy的建议。这个贴子的起源是要解决模板(txt、XML或Beetl之类模板)方式存放SQL造成的问题,详见https://my.oschina.net/xiandafu/blog/894155贴子中我关于模板的意见,模板的问题主要有文本的快速定位和模板ID及占位符的重构问题,这些都是属于IDE对模板支持不够造成的,我对groovy不太熟,没时间实测一下,感觉它和模板非常象,请问目前groovy的Eclipse插件能否做到:
1.点击Java程序中代表字符串的变量名例如groovyEntity.SqlID,IDE会跳转到groovy的SqlID源码位置?
2.在Java程序里选中groovyEntity.SqlID并重构变量名,groovy源程序中的变量名也会跟着重构?反之亦可。
如果以上两点在Eclipse中可以做到,则groovy确实可以代替本文介绍的Java注释方法。另外占位符的重构功能(当Java变量名在groovy中以占位符方式出现)就不做要求了,当然如果支持的话更好。
模板语言的弱项在IDE支持,模板实际上不是比拼模板,而是在比拼IDE支持,这点很多模板发明人重视不够。
另外楼上几位可能没注意到本文的方法还在定义SQl的同时进行了参数的注入,这点可以提高SQL的可维护性。原理见http://www.iteye.com/topic/1145415, 如果用groovy的法请问有什么优雅的办法实现?最好能上代码,谢谢。
YanbinQ
YanbinQ
我也曾经在 Java 的 Here Document 支持上浪费了不少时间和精力,
Java 的多行字符串 Here Document 的实现(https://unmi.cc/java-implement-here-document/)
现在不再这么折腾了,倒不如在 Java 项目中引入一 groovy 文件来支持多行字符串,反正编译后效率不会成问题。
如果是 Spring 项目,可以尝试把 SQL 写在外部的 *.sql 或 *.xml 文件中,具体做法

Spring 项目中把 SQL 语句写在 .sql 文件中(https://unmi.cc/spring-external-sql-statements/)
Spring 项目中把属性或 SQL 语句写在 .xml 文件中(https://unmi.cc/spring-properties-sql-in-xml-file/)

这样会更切实际些
飞天奔月
飞天奔月
java 注释就是注释, 并且只是注释, 对不同的概念要有清晰的定义, 对博主的精神点赞,但是实现方式可以讨论
yong9981
yong9981
对Groovy不熟,简单看了一下Groovy的语法,居然包含占位符语法,不知道有没有IDE能对占位符支持重构。但先不管Groovy, 把Java的潜力先挖到底再说,正文又更新了一下,新上的代码可以做到开发阶段从源码读目录(继承于TextSupport的子类在getJavaSrcFolder方法中要给出源码根目录),布署阶段从资源文件读,这样更方便一些。
腾飞工作室
腾飞工作室
建议还是用groovy把
"""
三个引号里面是写多行的,在兼容Java的基础上扩展Java一些功能
"""
银杏林守望者
唉唉唉,这么麻烦干嘛。。。。。。
直接用groovy就行了,和python一样 ''' 支持多行字符串,ide支持也完备。
原来的java代码不用动直接支持。编译出来还是跑在虚拟机上。
yong9981
yong9981
"myeclipse 装了 这个插件没有效果呀? 求解 " 这就反映了一个问题:需要插件或特殊编译开关才能支持的方法,不太适合于团队开发,在一个人机器上能跑的在其它人机器上就跑不起来,需要额外的沟通。
另外说一下,拷贝到类根目录是必须的,但是在开发阶段也可以改进一下SqlTemplate,把java源码目录放进去,则可以直接读取Java源码,不必每次改动Java后再拷贝一遍到类根目录。仅仅在发布时再拷贝一遍,改成从资源文件读。我在另一个小工具项目里已经这么做了,感觉很方便。
另外一个常见的需求是仅当变量为非空值时才插入SQL片段并赋值, 初步构想是可以通过写以下Java方法来实现:ifHasValue(name, "username=?");
具体原理是:当ifHasValue()方法运行时先在Threadlocal中存入一个对应"username=?"键的name值,在toString()方法运行时检查对应"username=?"的键是否有值,如果有值的话才将username=?这个字串插入SQL。键必须是唯一的,如果有多个相同的键可以通过添加空格来区分。
_N
_N

引用来自“TSC9526”的评论

eclipse插件可以方便的书写sql或其它多行的字符,并支持变量.

String name="zzg";
String lines = ""/**~!{
SELECT *
FROM user
WHERE name="$name"
}*/;
System.out.println(lines);

输出:
SELECT *
FROM user
WHERE name=zzg

参考地址:
https://github.com/11039850/monalisa-orm/wiki/Multiple-line-syntax
myeclipse 装了 这个插件没有效果呀? 求解
java编程新手入门学习的基础语法

Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于PC、数据中心、游戏控制台、科学超级计算机、移动电话和互...

Java小辰
2018/05/28
0
0
扩展 JDT 实现自动代码注释与格式化

简介: 本文介绍了一个为 Eclipse 工作空间中的 Java 代码自动添加统一注释并格式化排版的工具及其具体实现。该工具扩展 Eclipse Java Development Tool(JDT)API,操作 Java 代码。通过本文...

红薯
2010/07/30
1K
1
Java 面试题问与答:编译时与运行时

Q:下面的代码片段中,行A和行B所标识的代码有什么区别呢? public class ConstantFolding { static final int number1 = 5; static final int number2 = 6; static int number3 = 5; stati......

heroShane
2014/02/26
0
0
IntelliJ IDEA 2018.3 正式版发布!

温馨提示:有用户反馈升级至该版本后,部分插件不能使用,请谨慎。 IntelliJ IDEA 今年的最后一次大版本更新 2018.3 现已正式发布,值得关注的更新包括支持 Java 12、Git submodule、GitHub ...

局长
2018/11/22
10.2K
55
Android Gradle(三)Groovy快速入门指南

本文首发于微信公众号「刘望舒」 原文链接:Groovy快速入门看这篇就够了 前言 在前面我们学习了为什么现在要用Gradle?和Gradle入门前奏两篇文章,对Gradle也有了大概的了解,这篇文章我们接...

刘望舒
2018/10/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

C++随笔(四)Nuget打包

首先把自己编译好的包全部准备到一个文件夹 像这样 接下来新建一个文本文档,后缀名叫.nuspec 填写内容 <?xml version="1.0"?><package xmlns="http://schemas.microsoft.com/packaging/201......

Pulsar-V
59分钟前
2
0
再谈使用开源软件搭建数据分析平台

三年前,我写了这篇博客使用开源软件快速搭建数据分析平台, 当时收到了许多的反馈,有50个点赞和300+的收藏。到现在我还能收到一些关于dataplay2的问题。在过去的三年,开源社区和新技术的发...

naughty
今天
3
0
Python3的日期和时间

python 中处理日期时间数据通常使用datetime和time库 因为这两个库中的一些功能有些重复,所以,首先我们来比较一下这两个库的区别,这可以帮助我们在适当的情况下时候合适的库。 在Python文...

编程老陆
今天
2
0
分布式面试整理

并发和并行 并行是两个任务同时进行,而并发呢,则是一会做一个任务一会又切换做另一个任务。 临界区 临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用,但是每一次,只能有...

群星纪元
今天
3
0
手机通过wifi遥控arduino

手机下载Blinker 从Blinker官网下载手机App,安装到手机。 手机连接WiFi。 点击我的设备右上角的"+"添加设备,选择Arduino -> wifi接入,复制密钥以备后续使用。 点击新建的设备,可以在新界...

davidwbnu
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部