如何在Spring Boot代碼中執行sql腳本

在spring應用運行時,有一些建表語句,或則初始化數據,需要從sql腳本導入。

本文推薦以下兩種方法。

假設腳本位於/resources/ddl.sql

1 使用@sql註解

該註解可用於類和方法。

@Sql(scripts = {"/ddl.sql"}, 
    config = @SqlConfig(encoding = "utf-8", 
        transactionMode = SqlConfig.TransactionMode.ISOLATED))
@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Slf4j
public class RepositoryTest {
  
    @Autowired
    AppRepository appRepository;
  
    @Test
    @Order(1)
    public void insertApp(){
        appRepository.add(...)
        // ...
    }
}

注意,如果@Sql用於class,那麼測試類裏面的每個測試方法在運行之前都會跑一次這個腳本

@sql上的註釋有說明:

@Sql is used to annotate a test class or test method to configure SQL scripts and statements to be executed against a given database during integration tests.

...

Script execution is performed by the SqlScriptsTestExecutionListener...

看見during integration tests了吧。

另外,從註釋可知,腳本是被SqlScriptsTestExecutionListener執行的。打開這個類,可以看到裏面有一些方法和debug級別的日誌。將這個包的日誌級別設爲debug:

logging.level.root=info
logging.level.org.springframework.test.context.jdbc=debug

然後運行,即可看到類似下面的日誌:

 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : Started RepositoryTest in 1.959 seconds (JVM running for 2.746)
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@e4927bb dataSource = '', transactionManager = '', transactionMode = ISOLATED, encoding = 'utf-8', separator = ';',...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
 INFO [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertApp
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@5e704109 dataSource = '', transactionManager = '', transactionMode = ISOLATED, encoding = 'utf-8', separator = ';',...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertArtifact
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@701e3274 dataSource = '', transactionManager = '', transactionMode = ISOLATED, encoding = 'utf-8', separator = ';',...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertHost
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@48ab1f96 dataSource = '', transactionManager = '', transactionMode = ISOLATED, encoding = 'utf-8', separator = ';',...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertLicense
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@67d2c696 dataSource = '', transactionManager = '', transactionMode = ISOLATED, encoding = 'utf-8', separator = ';',...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertTag
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@2473bf20 dataSource = '', transactionManager = '', transactionMode = ISOLATED, encoding = 'utf-8', separator = ';',...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertVulnerability
 INFO [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
 INFO [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 7s

從日誌可見,確實在每個測試用例之前執行了腳本:

Executing SQL scripts: [class path resource [ddl.sql]]

如果只需要該腳本執行一次該怎麼做呢?將@Sql(....)註解放在某個@Test方法上,比如init方法,那麼該腳本只會在執行init方法之前執行一次:

@Sql(scripts = {"/ddl.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
@Test
void init() {
    log.info("createTableUseAnno");
}

日誌:

 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : Started RepositoryTest in 1.916 seconds (JVM running for 2.683)
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Processing [MergedSqlConfig@6367d99b dataSource = '', transactionManager = ...
DEBUG [    Test worker] .s.t.c.j.SqlScriptsTestExecutionListener : Executing SQL scripts: [class path resource [ddl.sql]]
 INFO [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
 INFO [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : createTableUseAnno
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertApp
 INFO [    Test worker] cn.whu.wy.osgov.test.RepositoryTest      : insertArtifact
...

2 使用ScriptUtils.executeSqlScript


@Autowired
DataSource dataSource;

void createTable() throws SQLException {
    Resource classPathResource = new ClassPathResource("ddl.sql");
    EncodedResource encodedResource = new EncodedResource(classPathResource, "utf-8");
    ScriptUtils.executeSqlScript(dataSource.getConnection(), encodedResource);
}

這種方式更加靈活,DataSource可以是自己創建的。

中文編碼

上面兩種方法都指定了utf-8編碼。當表裏面有中文時,不指定時會報錯。

案例:有這樣一個表,

CREATE TABLE app
(
    id   int                  NOT NULL AUTO_INCREMENT,
    name varchar(50)          NOT NULL,
    env  enum ('生產','測試') NOT NULL,
    PRIMARY KEY (id),
    INDEX idx_name (name)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

env字段爲枚舉類型,通過本文提到的兩種方法建表後(未指定utf-8編碼),在插入數據時報錯:

> INSERT app(name, env) VALUES ('app-1', '生產');

PreparedStatementCallback; Data truncated for column 'env' at row 1; 
nested exception is java.sql.SQLException: Data truncated for column 'env' at row 1

代碼

我的這個項目使用了本文提到的內容,可以參考。

Reference

  1. Guide on Loading Initial Data with Spring Boot | Baeldung
  2. java執行SQL腳本文件 - 足下之路 - 博客園 (cnblogs.com)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章