Spring項(xiàng)目里將SQL語(yǔ)句寫(xiě)在.sql文件中的方法
前言
我們?cè)谑褂?JDBC 時(shí), 如果把所有的 SQL 語(yǔ)句全寫(xiě)在 Java 文件中, 由于 Java 不支持 Here Document, 多行字符串要么用加號(hào), 要么用 Java 8 的 String.join()
方法來(lái)連接, 同時(shí)不能對(duì) SQL 語(yǔ)句進(jìn)行語(yǔ)法加亮, 所以這樣的 SQL 字符串閱讀性很差. 別說(shuō)為何不用 Hibernate 之類的而不直接寫(xiě)原始的 SQL 語(yǔ)句, 在操作復(fù)雜的系統(tǒng)時(shí)還是會(huì)用到 JdbcTemplate 吧.
所以我們希望能把 SQL 語(yǔ)句寫(xiě)在單獨(dú)的 *.sql 文件里, 這樣很多編輯器就能語(yǔ)法高亮顯示, 或在輸入時(shí)還能得到智能提示.
有種辦法是把 *.sql 用作為屬性文件, 那么在其中定義多行的 SQL 語(yǔ)句時(shí)就得這樣
select.user=select id, firstname, lastname, address \ from users \ where id=?
加載后就能用 getProperty("select.user")
來(lái)引用相應(yīng)的語(yǔ)句了. 屬性文件的換行與 Bash 一樣, 也是用 \, 但如此, 則 *.sql 并非一個(gè)純粹的 SQL 文件, 不能正確的進(jìn)行語(yǔ)法加亮, 一旦寫(xiě)上 SQL 的注釋 -- 就更是在添亂了.
所以我們的第二個(gè)方案是: 首先 *.sql 就該是一個(gè)真正的 SQL 文件, 而不是偽裝的屬性文件, 為了能在程序中引用每一條 SQL 語(yǔ)句, 我們?cè)撊绾伪硎靖髯缘?Key 呢? 這里的靈感仍然是來(lái)自于 Linux Shell, 在 Linux Shell 中指定執(zhí)行環(huán)境的用了特殊的注釋方式 #!, 如
#!/bin/bash #!/usr/bin/env python
依葫蘆畫(huà)瓢, SQL 的標(biāo)準(zhǔn)單注釋是 --, 因而我們也創(chuàng)建一個(gè)特別的注釋 --!, , 其后的字符串就是接下來(lái) SQL 語(yǔ)句的 Key.
舉例如下
--!select.user select id, firstname, lastname, address from users where id=? --!update.user update ........
--! 之后是 key select.user
, 往下在未到文件結(jié)束, 或是遇到下一個(gè) --! 之前就是這個(gè) key 對(duì)應(yīng)的完整 SQL 語(yǔ)句的內(nèi)容.
本文以 Spring 項(xiàng)目為例來(lái)演示如何應(yīng)這個(gè) SQL 文件, 其實(shí)在其他類型的 Java 項(xiàng)目中同樣可以借鑒.
因?yàn)檫@是一個(gè)真正的 SQL 文件, 所以在 Spring 中我們無(wú)法直接作為屬性文件來(lái)加載. 假設(shè)我們把該文件存儲(chǔ)為 src/resources/sql/queries.sql, 因此我們不能直接用
@PropertySource(value = "classpath:sql/queries.sql") public class AppConfig { ...... }
加載該文件.
幸好 PropertySource 注解還有一個(gè)屬性 factory, 類型為 PropertySourceFactory, 這就是我們作文章的地方, 馬上著手自定義一個(gè) SqlPropertySourceFactory, 在其中總有辦法把一個(gè) *.sql 的內(nèi)容轉(zhuǎn)換為 Properties. 因此將來(lái)我們要加載 sql/queries.sql 文件所用的注解形式就會(huì)是
@PropertySource(value = "classpath:sql/queries.sql", factory = SqlPropertySourceFactory.class) public class AppConfig { ......}
接下來(lái)就是本文的關(guān)鍵, 看看 SqlPropertySourceFactory 的實(shí)現(xiàn)
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 文件內(nèi)容如下:
--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=?
最后是如何應(yīng)用它, 我們以 SpringBoot 的方式來(lái)啟動(dòng)一個(gè) Spring 項(xiàng)目
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; } }
既然已轉(zhuǎn)換為普通的屬性了, 所以可以通過(guò)表達(dá)式 ${key}
或 env.getProperty("key")
來(lái)引用它們.
執(zhí)行上面的代碼, 輸出如下:
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, ?, ?, ?)
就這么簡(jiǎn)單. 當(dāng)然那個(gè) *.sql 文件最好是寫(xiě)得嚴(yán)謹(jǐn)一些, 我們可以將來(lái)對(duì) SqlPropertySourceFactory 進(jìn)行逐步完善以應(yīng)對(duì)更多的可能. 不管怎么說(shuō)它是一個(gè)真正的 SQL 文件, 在代碼中也能像任何別的屬性那么方便的引用其中定義的 SQL 語(yǔ)句了.
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流。
- 解決springmvc+mybatis+mysql中文亂碼問(wèn)題
- mysql+Spring數(shù)據(jù)庫(kù)隔離級(jí)別與性能分析
- Spring整合MyBatis(Maven+MySQL)圖文教程詳解
- Java+Spring+MySql環(huán)境中安裝和配置MyBatis的教程
- struts2.3.24+spring4.1.6+hibernate4.3.11+mysql5.5.25開(kāi)發(fā)環(huán)境搭建圖文教程
- spring mvc4.1.6 spring4.1.6 hibernate4.3.11 mysql5.5.25開(kāi)發(fā)環(huán)境搭建圖文教程
- 深入淺出重構(gòu)Mybatis與Spring集成的SqlSessionFactoryBean(上)
- SpringMVC+Mysql實(shí)例詳解(附demo)
- 詳解spring開(kāi)發(fā)_JDBC操作MySQL數(shù)據(jù)庫(kù)
- 使用Spring AOP實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)讀寫(xiě)分離案例分析(附demo)
相關(guān)文章
SpringBoot+Hutool實(shí)現(xiàn)圖片驗(yàn)證碼的示例代碼
圖片驗(yàn)證碼在注冊(cè)、登錄、交易、交互等各類場(chǎng)景中都發(fā)揮著巨大作用,本文主要介紹了SpringBoot+Hutool實(shí)現(xiàn)圖片驗(yàn)證碼的示例代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Java小程序賽馬游戲?qū)崿F(xiàn)過(guò)程詳解
這篇文章主要介紹了Java小程序賽馬游戲?qū)崿F(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03Java調(diào)用JavaScript實(shí)現(xiàn)字符串計(jì)算器代碼示例
這篇文章主要介紹了Java調(diào)用JavaScript實(shí)現(xiàn)字符串計(jì)算器代碼示例,具有一定參考價(jià)值,需要的朋友可以了解下。2017-12-12Spring Boot統(tǒng)一異常處理最佳實(shí)踐(拓展篇)
這篇文章主要給大家介紹了關(guān)于Spring Boot統(tǒng)一異常處理最佳實(shí)踐(拓展篇)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02Java網(wǎng)絡(luò)IO模型詳解(BIO、NIO、AIO)
Java支持BIO、NIO和AIO三種網(wǎng)絡(luò)IO模型,BIO是同步阻塞模型,適用于連接數(shù)較少的場(chǎng)景,NIO是同步非阻塞模型,適用于處理多個(gè)連接,支持自JDK1.4起,AIO是異步非阻塞模型,適用于異步操作多的場(chǎng)景,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10springboot swagger不顯示接口的問(wèn)題及解決
這篇文章主要介紹了springboot swagger不顯示接口的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06