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


相关推荐: EditPlus中的正则表达式 实战(1)  Laravel Debugbar怎么安装_Laravel调试工具栏配置指南  php在windows下怎么调试_phpwindows环境调试操作说明【操作】  使用Dockerfile构建java web环境  ,南京靠谱的征婚网站?  详解jQuery停止动画——stop()方法的使用  Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)  公司门户网站制作流程,华为官网怎么做?  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  个人网站制作流程图片大全,个人网站如何注销?  Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧  如何实现javascript表单验证_正则表达式有哪些实用技巧  Laravel模型事件有哪些_Laravel Model Event生命周期详解  Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程  Laravel Telescope怎么调试_使用Laravel Telescope进行应用监控与调试  大型企业网站制作流程,做网站需要注册公司吗?  Thinkphp 中 distinct 的用法解析  html如何与html链接_实现多个HTML页面互相链接【互相】  Laravel怎么调用外部API_Laravel Http Client客户端使用  Windows驱动无法加载错误解决方法_驱动签名验证失败处理步骤  三星网站视频制作教程下载,三星w23网页如何全屏?  Laravel Asset编译怎么配置_Laravel Vite前端构建工具使用  Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】  ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集  Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析  如何基于云服务器快速搭建个人网站?  深圳网站制作公司好吗,在深圳找工作哪个网站最好啊?  php增删改查怎么学_零基础入门php数据库操作必知基础【教程】  微信公众帐号开发教程之图文消息全攻略  Laravel如何生成和使用数据填充?(Seeder和Factory示例)  Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】  如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】  Laravel队列由Redis驱动怎么配置_Laravel Redis队列使用教程  Laravel如何理解并使用服务容器(Service Container)_Laravel依赖注入与容器绑定说明  如何用y主机助手快速搭建网站?  Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】  Windows11怎样设置电源计划_Windows11电源计划调整攻略【指南】  网站制作大概要多少钱一个,做一个平台网站大概多少钱?  Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势  网站制作大概多少钱一个,做一个平台网站大概多少钱?  HTML5段落标签p和br怎么选_文本排版常用标签对比【解答】  宙斯浏览器文件分类查看教程 快速筛选视频文档与图片方法  Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】  iOS正则表达式验证手机号、邮箱、身份证号等  Android中Textview和图片同行显示(文字超出用省略号,图片自动靠右边)  ChatGPT 4.0官网入口地址 ChatGPT在线体验官网  Android中AutoCompleteTextView自动提示  Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧  Laravel如何正确地在控制器和模型之间分配逻辑_Laravel代码职责分离与架构建议  魔方云NAT建站如何实现端口转发?