在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
代碼
我的這個項目使用了本文提到的內容,可以參考。