Spring项目里将SQL语句写在.sql文件中的方法
发布时间 - 2026-01-10 22:37:52 点击率:次前言

我们在使用 JDBC 时, 如果把所有的 SQL 语句全写在 Java 文件中, 由于 Java 不支持 Here Document, 多行字符串要么用加号, 要么用 Java 8 的 String.join() 方法来连接, 同时不能对 SQL 语句进行语法加亮, 所以这样的 SQL 字符串阅读性很差. 别说为何不用 Hibernate 之类的而不直接写原始的 SQL 语句, 在操作复杂的系统时还是会用到 JdbcTemplate 吧.
所以我们希望能把 SQL 语句写在单独的 *.sql 文件里, 这样很多编辑器就能语法高亮显示, 或在输入时还能得到智能提示.
有种办法是把 *.sql 用作为属性文件, 那么在其中定义多行的 SQL 语句时就得这样
select.user=select id, firstname, lastname, address \ from users \ where id=?
加载后就能用 getProperty("select.user") 来引用相应的语句了. 属性文件的换行与 Bash 一样, 也是用 \, 但如此, 则 *.sql 并非一个纯粹的 SQL 文件, 不能正确的进行语法加亮, 一旦写上 SQL 的注释 -- 就更是在添乱了.
所以我们的第二个方案是: 首先 *.sql 就该是一个真正的 SQL 文件, 而不是伪装的属性文件, 为了能在程序中引用每一条 SQL 语句, 我们该如何表示各自的 Key 呢? 这里的灵感仍然是来自于 Linux Shell, 在 Linux Shell 中指定执行环境的用了特殊的注释方式 #!, 如
#!/bin/bash #!/usr/bin/env python
依葫芦画瓢, SQL 的标准单注释是 --, 因而我们也创建一个特别的注释 --!, , 其后的字符串就是接下来 SQL 语句的 Key.
举例如下
--!select.user select id, firstname, lastname, address from users where id=? --!update.user update ........
--! 之后是 key select.user, 往下在未到文件结束, 或是遇到下一个 --! 之前就是这个 key 对应的完整 SQL 语句的内容.
本文以 Spring 项目为例来演示如何应这个 SQL 文件, 其实在其他类型的 Java 项目中同样可以借鉴.
因为这是一个真正的 SQL 文件, 所以在 Spring 中我们无法直接作为属性文件来加载. 假设我们把该文件存储为 src/resources/sql/queries.sql, 因此我们不能直接用
@PropertySource(value = "classpath:sql/queries.sql")
public class AppConfig { ...... }
加载该文件.
幸好 PropertySource 注解还有一个属性 factory, 类型为 PropertySourceFactory, 这就是我们作文章的地方, 马上着手自定义一个 SqlPropertySourceFactory, 在其中总有办法把一个 *.sql 的内容转换为 Properties. 因此将来我们要加载 sql/queries.sql 文件所用的注解形式就会是
@PropertySource(value = "classpath:sql/queries.sql", factory = SqlPropertySourceFactory.class)
public class AppConfig { ......}
接下来就是本文的关键, 看看 SqlPropertySourceFactory 的实现
SqlPropertySourceFactory.java
package cc.unmi;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class SqlPropertySourceFactory implements PropertySourceFactory {
private static final String KEY_LEADING = "--!";
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Deque<Pair> queries = new LinkedList<>();
new BufferedReader(resource.getReader()).lines().forEach(line -> {
if (line.startsWith(KEY_LEADING)) {
queries.addLast(new Pair(line.replaceFirst(KEY_LEADING, "")));
} else if (line.startsWith("--")) {
//skip comment line
} else if (!line.trim().isEmpty()) {
Optional.ofNullable(queries.getLast()).ifPresent(pair -> pair.lines.add(line));
}
});
Map<String, Object> sqlMap = queries.stream()
.filter(pair -> !pair.lines.isEmpty())
.collect(Collectors.toMap(pair -> pair.key,
pair -> String.join(System.lineSeparator(), pair.lines),
(r, pair) -> r, LinkedHashMap::new));
System.out.println("Configured SQL statements:");
sqlMap.forEach((s, o) -> System.out.println(s + "=" + o));
return new MapPropertySource(resource.toString(), sqlMap);
}
private static class Pair {
private String key;
private List<String> lines = new LinkedList<>();
Pair(String key) {
this.key = key;
}
}
}
我们定义的 src/resources/sql/queries.sql 文件内容如下:
--external queries in this file --!select_users_by_id select id, firstname, lastname, address from users where id=? --!add_user insert users(id, firstname, lastname, address) values(DEFAULT, ?, ?, ?) -- --!no_statement --- --!update update users set firstname=? where id=?
最后是如何应用它, 我们以 SpringBoot 的方式来启动一个 Spring 项目
DemoApplication.java
package cc.unmi;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@SpringBootApplication
@PropertySource(value = "classpath:sql/queries.sql", factory = SqlPropertySourceFactory.class)
public class DemoApplication implements EnvironmentAware {
private Environment env;
@Value("${add_user}")
private String sqlAddUser;
@Bean
public String testBean() {
System.out.println("SQL_1:" + env.getProperty("select_users_by_id"));
System.out.println("SQL_2:" + sqlAddUser);
return "testBean";
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void setEnvironment(Environment environment) {
env = environment;
}
}
既然已转换为普通的属性了, 所以可以通过表达式 ${key} 或 env.getProperty("key") 来引用它们.
执行上面的代码, 输出如下:
Configured SQL statements: select_users_by_id=select id, firstname, lastname, address from users where id=? add_user=insert users(id, firstname, lastname, address) values(DEFAULT, ?, ?, ?) update=update users set firstname=? where id=? SQL_1:select id, firstname, lastname, address from users where id=? SQL_2:insert users(id, firstname, lastname, address) values(DEFAULT, ?, ?, ?)
就这么简单. 当然那个 *.sql 文件最好是写得严谨一些, 我们可以将来对 SqlPropertySourceFactory 进行逐步完善以应对更多的可能. 不管怎么说它是一个真正的 SQL 文件, 在代码中也能像任何别的属性那么方便的引用其中定义的 SQL 语句了.
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
# spring
# mvc
# sql语句
# 实现sql语句
# spring封装
# 解决springmvc+mybatis+mysql中文乱码问题
# mysql+Spring数据库隔离级别与性能分析
# Spring整合MyBatis(Maven+MySQL)图文教程详解
# Java+Spring+MySql环境中安装和配置MyBatis的教程
# struts2.3.24+spring4.1.6+hibernate4.3.11+mysql5.5.
# spring mvc4.1.6 spring4.1.6 hibernate4.3.11 mysql5
# 深入浅出重构Mybatis与Spring集成的SqlSessionFactoryBean(上)
# SpringMVC+Mysql实例详解(附demo)
# 详解spring开发_JDBC操作MySQL数据库
# 使用Spring AOP实现MySQL数据库读写分离案例分析(附demo)
# 加亮
# 是一个
# 加载
# 写在
# 转换为
# 依葫芦画瓢
# 将来
# 就会
# 是在
# 就能
# 这就是
# 还能
# 能在
# 我们可以
# 这是一个
# 而不
# 可以通过
# 用了
# 第二个
# 还有一个
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel全局作用域是什么_Laravel Eloquent Global Scopes应用指南
教你用AI将一段旋律扩展成一首完整的曲子
Python文件流缓冲机制_IO性能解析【教程】
如何彻底删除建站之星生成的Banner?
想要更高端的建设网站,这些原则一定要坚持!
JavaScript如何实现倒计时_时间函数如何精确控制
Win11任务栏卡死怎么办 Windows11任务栏无反应解决方法【教程】
进行网站优化必须要坚持的四大原则
高配服务器限时抢购:企业级配置与回收服务一站式优惠方案
Laravel如何理解并使用服务容器(Service Container)_Laravel依赖注入与容器绑定说明
Laravel如何处理JSON字段_Eloquent原生JSON字段类型操作教程
中山网站推广排名,中山信息港登录入口?
Laravel Sail是什么_基于Docker的Laravel本地开发环境Sail入门
Laravel广播系统如何实现实时通信_Laravel Reverb与WebSockets实战教程
Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
如何利用DOS批处理实现定时关机操作详解
大学网站设计制作软件有哪些,如何将网站制作成自己app?
北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?
Laravel怎么创建控制器Controller_Laravel路由绑定与控制器逻辑编写【指南】
如何实现建站之星域名转发设置?
Python高阶函数应用_函数作为参数说明【指导】
Laravel DB事务怎么使用_Laravel数据库事务回滚操作
Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道
北京的网站制作公司有哪些,哪个视频网站最好?
如何在云主机上快速搭建网站?
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
Android滚轮选择时间控件使用详解
清除minerd进程的简单方法
通义万相免费版怎么用_通义万相免费版使用方法详细指南【教程】
Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
Windows10如何更改计算机工作组_Win10系统属性修改Workgroup
Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】
如何在 React 中条件性地遍历数组并渲染元素
如何快速搭建FTP站点实现文件共享?
Laravel如何实现多对多模型关联?(Eloquent教程)
android nfc常用标签读取总结
Laravel如何集成Inertia.js与Vue/React?(安装配置)
如何在宝塔面板中修改默认建站目录?
深圳网站制作培训,深圳哪些招聘网站比较好?
Laravel如何使用Sanctum进行API认证?(SPA实战)
使用PHP下载CSS文件中的所有图片【几行代码即可实现】
Win11怎么开启自动HDR画质_Windows11显示设置HDR选项
Laravel如何使用API Resources格式化JSON响应_Laravel数据资源封装与格式化输出
浅谈javascript alert和confirm的美化
如何快速上传建站程序避免常见错误?
网站制作壁纸教程视频,电脑壁纸网站?
Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】
为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】

