使用Rocker模板引擎解决动态拼接SQL语句的问题

相信大部分服务端程序员都和我有一样的感觉,就是在Java代码里根据条件拼接SQL语句是个技术含量很低,又很麻烦,还容易出错的问题。
最简单直观的方式当然是用String/StringBuffer/StringBuilder自己拼,但是因为Java不支持多行字符串,也不能自动解析字符串里的变量,因此写起来、改起来都很麻烦。
也有不少框架给出了自己的方案,比如MyBatis可以在Xml里根据条件来拼,JOOQ可以用链式的方法调用来拼。
但是这些方案我感觉都不是很方便,因为大部分场景都是先在查询编辑器里面写好、调好SQL语句,再移植到Java代码中的。有时要修改SQL语句时,还不得不打印输出sql语句,再贴回到查询编辑器里调试,不是一般的麻烦。
MyBatis的方案比较符合现实需求,但是为了拼个字符串引入一整个ORM框架想想也醉了。
因此一个比较合适的方案是采用一种模板引擎,把SQL语句写在模板中,其中的变化部分用模板引擎来填充及控制是否输出。
模板引擎很多了,比较了一下,对性能特别敏感的我自然而然的选择了Rocker,项目地址 https://github.com/fizzed/rocker
Rocker是一个比较成熟的web框架ninja的子项目,想来质量还是可靠的。
但是Rocker似乎很小众,度娘、谷哥甚至万能的StackOverflow均表示无力,官方文档也只介绍了语法,其它安装配置使用一概欠奉,因此我只好自己踩坑了。

Rocker模板引擎的优点
* 速度快,Rocker自己有个官方的 benchmark项目,其速度基本上是Velocity, FreeMarker的4倍以上
* 模板在构建期转译成Java源码,再通过Java编译器编译成class文件
* 模板中的表达式直接在编译期转为Java源码,不需要在运行时解释执行
* 调用时,直接传入Java值或对象,不需要先把参数写入模板上下文中
* 模板中的表达式因为是直接复制成Java源码,因此语法和Java完全一样,学习成本低

在Maven项目里添加依赖
* 目前最新版本是0.13.1
        <dependency>
        	<groupId>com.fizzed</groupId>
        	<artifactId>rocker-runtime</artifactId>
        	<version>${rocker.version}</version>
        </dependency>

添加Rocker编译器插件
* 此Maven插件会自动把模板转译为Java源码
            <plugin>
                <groupId>com.fizzed</groupId>
                <artifactId>rocker-maven-plugin</artifactId>
                <version>${rocker.version}</version>
                <executions>
                    <execution>
                        <id>generate-rocker-templates</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <javaVersion>${project.build.jdk}</javaVersion>                  <!-- 产生的Java源文件的版本 -->
                            <templateDirectory>src/main/java</templateDirectory>     <!-- 模板源文件目录,Rocker会自动扫描其子目录,并用目录名作为产生的Java文件的包名
                            <outputDirectory>gen</outputDirectory>                            <!-- 输出目录,相对项目根目录 ->
                            <discardLogicWhitespace>true</discardLogicWhitespace> <!-- 删除模板标记带来的空行,此选项也可以在模板中用"@option discardLogicWhitespace=true"打开 ->
                        </configuration>
                    </execution>
                </executions>
            </plugin>

* 注意:
在MyEclipse里面,在execution元素上会报一个错误: Plugin execution not covered by lifecycle configuration
百度到的办法,在plugins外面添加pluginManagement,此方法确实能屏蔽掉错误,但是导致构建时此步骤不执行,因此不能使用
最后还是StackOverflow上的办法,直接Ctrl+1调出Quick Fix,然后选择"Permanently mark goal execute in pom.xml as ignored in Eclipse build"忽略掉即可,目前没感到有其它影响

避免模板文件被打包输出
* 还需要在resources节点中添加排除规则,避免模板文件构建时被打包到war包中
            <Excludes>
                <Exclude>**/*.rocker.raw</Exclude>
            </Excludes>

把源码输出目录添加到构建路径
* 在项目根目录创建gen目录
* MyEclipse中,右键点击该目录,菜单中 Build Path -> Use as Source Folder

创建Rocker模板
* 在模板源文件目录中添加模板 比如UserSearch.rocker.raw。这里的文件名UserSearch会被转译成产生的Java类名,因此建议用Java类名的命名方式。
* Rocker的模板文件后缀只能是.rocker.html或者.rocker.raw
* 模板语法见后文

转译模板为Java源文件
* 项目根目录运行mvn generate-sources。
* 如果使用MyEclipse,可以右键点击项目,然后选择Run As -> Maven generate-sources
* 如果构建出错,说明模板中有语法错误,请查看Maven的输出
* 在 [INFO] Parsing x rocker template files 后面的 [ERROR] 信息就是语法错误位置,包含模板文件名,行列号等
* 注意,因为rocker-compiler遇到第一个错误就退出,因此错误信息一般只有一行,不太显眼,而后面还会输出一大堆无用的异常信息来吸引注意力,我眼神不好就被坑了挺长时间……
* 无论用哪种方式调用转译,完后在MyEclipse中都要刷新一下gen目录下面的源码,确保IDE中和磁盘中的源文件同步。

在Java中使用转译出的Rocker模板类
* Rocker转译成功并不意味着输出的Java源文件就一定是正确的,如果有语法或语义错误就要回到模板源文件修改。
* 修改生成的java文件是没有用的,因为下次就会被覆盖掉。
* 如果生成的Java文件也没有错误,那就可以被其它Java代码引用了。
* 在Java源码中调用 <模板类名>.template(<模板参数>).render().toString() 就可以获得最终的字符串了,比如:
String familyName = "张";
String sql = UserSearch.template(familyName + "%").render().toString();

Rocker模板语法
* Rocker的模板标记、表达式都是用'@'开头
* 如果文本中有'@'字符,请使用"@@"转义。
* 文本中的'{', '}'只要是匹配的,Rocker就能正确处理,不会把它们当做控制字符,否则,请用"@{", "@}"进行转义
* Rocker注释为@* ... *@,可以跨多行
* 模板文件的结构为头部份和正文部分

模板头部分
@import: 用于导入Java包或类,除了结尾无分号外和Java一致,注意static import也支持。
@args (...): 指定模板的参数,和Java方法的参数声明语法一致,可以换行,Rocker能自动找到匹配的右括号
@option: 指定模板引擎的开关,比如 "@option discardLogicWhitespace=false", "@option targetCharset=UTF-16"

模板正文部分 - 表达式
@<value>: <value>为Java表达式,比如@keyword, @user.getName(), @users.get(0).getAge() 等
@(<expression>): <expression>为复杂的Java表达式;比如Java的三目表达式或数学运算等
@?<value>: 仅在<value>不为null时渲染输出
@<value>?:<other>: <value>不为null输出<value>,否则输出<other>

模板正文部分 - 控制语句
@for (...): 对Collection的遍历,支持Java的两种标准for语法,即for (... ; ... ; ...) 和 for (... : ...)
@for ((ForIterator i, <ItemType> item): items): Rocker的扩展for语法,可以用 i 获取到当前索引
@for (([ForIterator i, ]<KeyType> key, <ValueType> value): map): Rocker的扩展for语法,用于遍历Map, "ForIterator i"可省略
@continue, @break: 用于循环控制
@if (<expression>) { ... } [ else { ... } ] : 和Java语法一样

一个实际例子
* 注意,正文部分我把Rocker的控制语句都用"-- "注释起来,这样不影响Rocker解析,同时我的SQL的语法也是正确的,只要改成真实参数就可以直接放到查询编辑器里面执行
@import com.mycompany.product.model.*
@import com.mycompany.product.utils.*
@import java.util.List
@args (Long corpId, String type, String label, 
			String group, List<Long> posIdList, String level, String startDate,
			String endDate, Integer count)

select s.user_id, sum(s.score * d.factor) score 
from score s force index (created_def) 
 left join score_def d on s.def_id = d.id 
 left join user u on s.user_id = u.id
-- @if (!Util.empty(group)) {
 left join group_member g on s.corp_id = g.corp_id and g.group_name = '@group' and (g.member = concat('u',s.user_id) or g.member = concat('p',u.pos_id))
-- }
where 1=1
 and s.corp_id = @corpId
 and s.state = 3 
-- @if (type != null) {
 and d.type = '@type'
-- }
-- @if (label != null) {
 and d.label = '@label'
-- } 
-- @if (group != null) {
 and g.id is not null 
-- }
-- @if (posIdList != null) {
--   @if (posIdList.size() == 1) {
 and u.pos_id = @posIdList.get(0)
--   } else {
 and u.pos_id in (@Util.join(posIdList, ","))
--   }
-- }
-- @if (level != null) {
 and u.pos_level = '@level'
-- }
-- @if (startDate != null && endDate != null) {
 and s.created between '@startDate' and '@endDate' 
-- } else {
 and s.created between TIMESTAMPADD(day,-day(curdate())+1,CURDATE()) and now() 
-- }
GROUP BY s.user_id
order by score desc
limit @count?:Constants.DEFAULT_COUNT




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 我可以提供一个简单的Java调用RockerMQ的示例,具体如下: 1. 创建一个RockerMQ连接工厂,并设置RockerMQ服务器的地址:ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); 2. 创建一个RockerMQ连接:Connection connection = factory.newConnection(); 3. 创建一个RockerMQ通道:Channel channel = connection.createChannel(); 4. 声明一个消息队列:String queueName = "test_queue"; channel.queueDeclare(queueName, false, false, false, null); 5. 发送消息:String message = "Hello World!"; channel.basicPublish("", queueName, null, message.getBytes()); 6. 关闭连接:channel.close(); connection.close(); ### 回答2: 编写一个Java调用RocketMQ的案例,需要按照以下步骤进行: 1. 引入RocketMQ的依赖库到项目中。在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.7.1</version> </dependency> ``` 2. 创建生产者和消费者类。首先,创建一个Producer类来发送消息到RocketMQ服务器: ```java import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class MyProducer { public static void main(String[] args) throws MQClientException, InterruptedException { // 创建一个生产者实例 DefaultMQProducer producer = new DefaultMQProducer("producer_group"); // 设置NameServer地址 producer.setNamesrvAddr("localhost:9876"); // 启动生产者实例 producer.start(); try { // 创建一个消息实例,包含topic、tag和消息内容 Message message = new Message("topic_name", "tag_name", "Hello RocketMQ".getBytes(RemotingHelper.DEFAULT_CHARSET)); // 发送消息到RocketMQ服务器 producer.send(message); } catch (Exception e) { e.printStackTrace(); } // 关闭生产者实例 producer.shutdown(); } } ``` 接下来,创建一个Consumer类来消费RocketMQ服务器上的消息: ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class MyConsumer { public static void main(String[] args) throws MQClientException { // 创建一个消费者实例 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer_group"); // 设置NameServer地址 consumer.setNamesrvAddr("localhost:9876"); // 订阅一个或多个主题,并指定tag过滤器 consumer.subscribe("topic_name", "*"); // 注册消息监听器 consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> messages, ConsumeConcurrentlyContext context) { for (MessageExt message : messages) { // 处理接收到的消息 System.out.println("Received message: " + new String(message.getBody())); } // 返回消费状态 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // 启动消费者实例 consumer.start(); System.out.println("Consumer started."); } } ``` 3. 在RocketMQ服务器上配置相关的topic和tag。在RocketMQ服务器的conf目录下的broker.conf文件中,添加以下配置: ```conf autoCreateTopicEnable=true autoCreateSubscriptionGroup=true ``` 4. 运行Producer类和Consumer类。确保RocketMQ服务器已经启动并正常运行。运行Producer类发送消息到RocketMQ服务器,然后运行Consumer类来消费消息。 这个案例演示了如何通过Java代码调用RocketMQ进行消息的发送和消费。相应的Producer和Consumer类可以根据需要进行修改和扩展。 ### 回答3: Rocker MQ是一个开源的分布式消息中间件,可以提供高可靠、高性能的消息服务。在Java应用程序中调用Rocker MQ,可以通过以下步骤实现: 1. 引入Rocker MQ的依赖:在项目的pom.xml文件中添加Rocker MQ的依赖。 ```xml <dependency> <groupId>org.apache.rocketmq</groupId> <artifactId>rocketmq-client</artifactId> <version>4.8.1</version> </dependency> ``` 2. 创建生产者:使用Producer类创建一个生产者对象,设置生产者的相关属性。 ```java import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; public class RocketMQProducer { public static void main(String[] args) throws Exception { // 创建一个生产者对象 DefaultMQProducer producer = new DefaultMQProducer("producerGroup"); // 设置NameServer的地址 producer.setNamesrvAddr("localhost:9876"); // 启动生产者 producer.start(); try { // 创建一个消息对象 Message message = new Message("topic", "tag", "Hello RocketMQ".getBytes()); // 发送消息 SendResult result = producer.send(message); System.out.println("发送结果:" + result); } catch (Exception e) { e.printStackTrace(); } finally { // 关闭生产者 producer.shutdown(); } } } ``` 3. 创建消费者:使用Consumer类创建一个消费者对象,设置消费者的相关属性和消息监听器。 ```java import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; import org.apache.rocketmq.common.message.MessageExt; import java.util.List; public class RocketMQConsumer { public static void main(String[] args) throws Exception { // 创建一个消费者对象 DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumerGroup"); // 设置NameServer的地址 consumer.setNamesrvAddr("localhost:9876"); // 订阅消息 consumer.subscribe("topic", "*"); // 注册消息监听器 consumer.registerMessageListener(new MessageListenerConcurrently() { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) { for (MessageExt message : msgs) { // 处理消息 System.out.println("接收到消息:" + new String(message.getBody())); } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } }); // 启动消费者 consumer.start(); } } ``` 以上就是一个简单的Java调用Rocker MQ的案例,通过创建生产者发送消息,然后创建消费者接收并处理消息。需要注意的是,还需在本地搭建Rocker MQ的环境,并配置好相关的属性信息,如NameServer地址等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值