Loading...
Spring Framework Reference Documentation 7.0.2의 Executing SQL Scripts의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
관계형 database를 대상으로 integration test를 작성할 때, database schema를 수정하거나
table에 test data를 insert하기 위해 SQL script를 실행하는 것이 유용한 경우가 많습니다.
spring-jdbc module은 Spring ApplicationContext가 로드될 때 SQL script를 실행하여
embedded 또는 기존 database를 initializing 하는 기능을 제공합니다. 자세한 내용은
Embedded database support 및
Testing data access logic with an embedded database
을 참고하십시오.
ApplicationContext가 로드될 때 database를 test용으로 한 번 초기화하는 것은 매우
유용하지만, 때로는 integration test 중간에 database를 수정할 수 있어야 할 때도
있습니다. 다음 섹션에서는 integration test 동안 SQL script를 프로그래밍 방식으로 및
선언적으로 실행하는 방법을 설명합니다.
Spring은 integration test method 내에서 SQL script를 프로그래밍 방식으로 실행하기 위해 다음과 같은 옵션을 제공합니다.
org.springframework.jdbc.datasource.init.ScriptUtilsorg.springframework.jdbc.datasource.init.ResourceDatabasePopulatororg.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTestsorg.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTestsScriptUtils는 SQL script를 다루기 위한 일련의 static 유틸리티 메서드를 제공하며,
주로 framework 내부 사용을 목적으로 합니다. 그러나 SQL script가 어떻게 파싱되고
실행되는지에 대해 완전한 제어가 필요하다면, ScriptUtils는 이후에 설명하는 다른
대안들보다 더 적합할 수 있습니다. 개별 메서드에 대한 자세한 내용은
ScriptUtils의 javadoc을
참고하십시오.
ResourceDatabasePopulator는 외부 리소스에 정의된 SQL script를 사용하여 database를
프로그래밍 방식으로 채우고, 초기화하거나 정리하기 위한 객체 기반 API를
제공합니다. ResourceDatabasePopulator는 script를 파싱하고 실행할 때 사용되는
문자 인코딩, 구문 구분자, 주석 구분자 및 오류 처리 플래그를
설정하기 위한 옵션을 제공합니다. 각 설정 옵션에는 합리적인 기본값이
제공됩니다. 기본값에 대한 자세한 내용은
javadoc을
참고하십시오. ResourceDatabasePopulator에 설정된 script를 실행하려면
populate(Connection) 메서드를 호출하여 java.sql.Connection에 대해 populator를 실행하거나
execute(DataSource) 메서드를 호출하여 javax.sql.DataSource에 대해 populator를 실행할
수 있습니다. 다음 예제에서는 test schema와 test data용 SQL script를 지정하고,
구문 구분자를 @@로 설정한 다음 DataSource에 대해 script를 실행합니다:
1@Test 2void databaseTest() { 3 ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); 4 populator.addScripts( 5 new ClassPathResource("test-schema.sql"), 6 new ClassPathResource("test-data.sql")); 7 populator.setSeparator("@@"); 8 populator.execute(this.dataSource); 9 // run code that uses the test schema and data 10}
1@Test 2fun databaseTest() { 3 val populator = ResourceDatabasePopulator() 4 populator.addScripts( 5 ClassPathResource("test-schema.sql"), 6 ClassPathResource("test-data.sql")) 7 populator.setSeparator("@@") 8 populator.execute(dataSource) 9 // run code that uses the test schema and data 10}
ResourceDatabasePopulator는 내부적으로 SQL script를 파싱하고 실행하기 위해
ScriptUtils에 위임합니다. 유사하게,
AbstractTransactionalJUnit4SpringContextTests
와 AbstractTransactionalTestNGSpringContextTests
의 executeSqlScript(..) 메서드는 내부적으로 SQL script를 실행하기 위해
ResourceDatabasePopulator를 사용합니다. 다양한 executeSqlScript(..) 메서드에 대한
자세한 내용은 Javadoc을 참고하십시오.
앞에서 언급한 SQL script를 프로그래밍 방식으로 실행하는 메커니즘 외에도, Spring
TestContext Framework에서 SQL script를 선언적으로 설정할 수 있습니다. 구체적으로,
test class 또는 test method에 @Sql 어노테이션을 선언하여 integration test class 또는
test method 전후에 특정 database에 대해 실행해야 하는 개별 SQL 구문 또는 SQL
script의 리소스 경로를 설정할 수 있습니다. @Sql에 대한 지원은
SqlScriptsTestExecutionListener에 의해 제공되며, 기본적으로 활성화되어 있습니다.
Method-level
@Sql선언은 기본적으로 class-level 선언을 override하지만, 이 동작은@SqlMergeMode를 통해 test class 또는 test method별로 설정할 수 있습니다. 자세한 내용은 Merging and Overriding Configuration with@SqlMergeMode를 참고하십시오. 그러나 이는BEFORE_TEST_CLASS또는AFTER_TEST_CLASS실행 단계에 대해 설정된 class-level 선언에는 적용되지 않습니다. 이러한 선언은 override될 수 없으며, 해당 script와 구문은 method-level script와 구문에 추가로 class당 한 번씩 실행됩니다.
각 경로는 Spring Resource로 해석됩니다. 일반 경로(예: "schema.sql")는 test class가
정의된 패키지를 기준으로 하는 classpath 리소스로 처리됩니다. 슬래시로 시작하는 경로는
절대 classpath 리소스(예: "/org/example/schema.sql")로 처리됩니다. URL을 참조하는
경로(예: classpath:, file:, http:로 prefix된 경로)는 지정된 리소스 프로토콜을
사용하여 로드됩니다.
Spring Framework 6.2부터 경로에는 프로퍼티 플레이스홀더(${…})를 포함할 수 있으며,
이는 test의 ApplicationContext의 Environment에 저장된 프로퍼티로 대체됩니다.
다음 예제는 JUnit Jupiter 기반 integration test class 내에서 class level과 method level에서
@Sql을 사용하는 방법을 보여 줍니다:
1@SpringJUnitConfig 2@Sql("/test-schema.sql") 3class DatabaseTests { 4 5 @Test 6 void emptySchemaTest() { 7 // run code that uses the test schema without any test data 8 } 9 10 @Test 11 @Sql({"/test-schema.sql", "/test-user-data.sql"}) 12 void userTest() { 13 // run code that uses the test schema and test data 14 } 15}
1@SpringJUnitConfig 2@Sql("/test-schema.sql") 3class DatabaseTests { 4 5 @Test 6 fun emptySchemaTest() { 7 // run code that uses the test schema without any test data 8 } 9 10 @Test 11 @Sql("/test-schema.sql", "/test-user-data.sql") 12 fun userTest() { 13 // run code that uses the test schema and test data 14 } 15}
SQL script나 구문이 지정되지 않은 경우, @Sql이 선언된 위치에 따라 default
script를 탐지하려고 시도합니다. default를 탐지할 수 없으면 IllegalStateException이
발생합니다.
com.example.MyTest인 경우,
해당하는 default script는 classpath:com/example/MyTest.sql입니다.testMethod()이고
class com.example.MyTest에 정의되어 있는 경우, 해당하는 default script는
classpath:com/example/MyTest.testMethod.sql입니다.어떤 SQL script가 실행되는지 확인하려면
org.springframework.test.context.jdbc 로깅 카테고리를 DEBUG로 설정하십시오.
어떤 SQL 구문이 실행되는지 확인하려면
org.springframework.jdbc.datasource.init 로깅 카테고리를 DEBUG로 설정하십시오.
@Sql Sets특정 test class 또는 test method에 대해 서로 다른 구문 설정, 서로 다른
오류 처리 규칙 또는 set별로 서로 다른 실행 단계를 가진 여러 set의 SQL
script를 설정해야 하는 경우, @Sql의 여러 인스턴스를 선언할 수 있습니다. @Sql을
반복 가능한 어노테이션으로 사용하거나, 여러 @Sql 인스턴스를 선언하기 위한 명시적인
컨테이너로 @SqlGroup 어노테이션을 사용할 수 있습니다.
다음 예제는 반복 가능한 어노테이션으로 @Sql을 사용하는 방법을 보여 줍니다:
1@Test 2@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")) 3@Sql("/test-user-data.sql") 4void userTest() { 5 // run code that uses the test schema and test data 6}
1@Test 2@Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")) 3@Sql("/test-user-data.sql") 4fun userTest() { 5 // run code that uses the test schema and test data 6}
앞선 예제에서 제시된 시나리오에서는 test-schema.sql script가 single-line comment에
대해 다른 구문을 사용합니다.
다음 예제는 앞선 예제와 동일하지만, @Sql 선언을 @SqlGroup 내에 그룹화한 것입니다.
@SqlGroup 사용은 선택 사항이지만, 다른 JVM 언어와의 호환성을 위해 @SqlGroup을
사용해야 할 수도 있습니다.
1@Test 2@SqlGroup({ 3 @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")), 4 @Sql("/test-user-data.sql") 5}) 6void userTest() { 7 // run code that uses the test schema and test data 8}
1@Test 2@SqlGroup( 3 Sql("/test-schema.sql", config = SqlConfig(commentPrefix = "`")), 4 Sql("/test-user-data.sql") 5) 6fun userTest() { 7 // Run code that uses the test schema and test data 8}
기본적으로 SQL script는 해당 test method 전에 실행됩니다. 그러나 특정 script set을
test method 이후에 실행해야 하는 경우(예: database 상태를 정리하기 위해),
다음 예제와 같이 @Sql의 executionPhase 속성을 AFTER_TEST_METHOD로 설정할 수
있습니다:
1@Test 2@Sql( 3 scripts = "create-test-data.sql", 4 config = @SqlConfig(transactionMode = ISOLATED) 5) 6@Sql( 7 scripts = "delete-test-data.sql", 8 config = @SqlConfig(transactionMode = ISOLATED), 9 executionPhase = AFTER_TEST_METHOD 10) 11void userTest() { 12 // run code that needs the test data to be committed 13 // to the database outside of the test's transaction 14}
1@Test 2@Sql("create-test-data.sql", 3 config = SqlConfig(transactionMode = ISOLATED)) 4@Sql("delete-test-data.sql", 5 config = SqlConfig(transactionMode = ISOLATED), 6 executionPhase = AFTER_TEST_METHOD) 7fun userTest() { 8 // run code that needs the test data to be committed 9 // to the database outside of the test's transaction 10}
ISOLATED와AFTER_TEST_METHOD는 각각Sql.TransactionMode와Sql.ExecutionPhase에서 static import됩니다.
Spring Framework 6.1부터는 class-level @Sql 선언에서 executionPhase 속성을
BEFORE_TEST_CLASS 또는 AFTER_TEST_CLASS로 설정하여 test class 전이나 후에 특정
script set을 실행할 수 있습니다. 다음 예제는 이를 보여 줍니다:
1@SpringJUnitConfig 2@Sql(scripts = "/test-schema.sql", executionPhase = BEFORE_TEST_CLASS) 3class DatabaseTests { 4 5 @Test 6 void emptySchemaTest() { 7 // run code that uses the test schema without any test data 8 } 9 10 @Test 11 @Sql("/test-user-data.sql") 12 void userTest() { 13 // run code that uses the test schema and test data 14 } 15}
1@SpringJUnitConfig 2@Sql("/test-schema.sql", executionPhase = BEFORE_TEST_CLASS) 3class DatabaseTests { 4 5 @Test 6 fun emptySchemaTest() { 7 // run code that uses the test schema without any test data 8 } 9 10 @Test 11 @Sql("/test-user-data.sql") 12 fun userTest() { 13 // run code that uses the test schema and test data 14 } 15}
BEFORE_TEST_CLASS는Sql.ExecutionPhase에서 static import됩니다.
@SqlConfig@SqlConfig 어노테이션을 사용하여 script 파싱 및 오류 처리를 설정할 수 있습니다.
integration test class에 class-level 어노테이션으로 선언된 경우, @SqlConfig는 test class
계층 구조 내의 모든 SQL script에 대한 전역 설정 역할을 합니다. @Sql
어노테이션의 config 속성을 통해 직접 선언된 경우, @SqlConfig는 둘러싼 @Sql
어노테이션 내에 선언된 SQL script에 대한 로컬 설정 역할을 합니다. @SqlConfig의
모든 속성에는 암묵적인 기본값이 있으며, 해당 속성의 javadoc에
문서화되어 있습니다. Java Language Specification에서 정의된 어노테이션 속성 규칙으로
인해 어노테이션 속성에 null 값을 할당하는 것은 불가능합니다. 따라서 상속된
전역 설정을 override하는 것을 지원하기 위해, @SqlConfig 속성에는
""(String의 경우), {}(배열의 경우) 또는 DEFAULT(열거형의 경우)라는 명시적인
기본값이 있습니다. 이 접근 방식은 로컬 @SqlConfig 선언이 "", {} 또는
DEFAULT 이외의 값을 제공함으로써 전역 @SqlConfig 선언의 개별 속성을 선택적으로
override할 수 있도록 합니다. 로컬 @SqlConfig 속성이 "", {} 또는 DEFAULT
이외의 명시적인 값을 제공하지 않는 경우, 전역 @SqlConfig 속성이 상속됩니다.
따라서 명시적인 로컬 설정은 전역 설정을 override합니다.
@Sql 및 @SqlConfig에서 제공하는 설정 옵션은 ScriptUtils 및
ResourceDatabasePopulator에서 지원하는 옵션과 동일하지만,
<jdbc:initialize-database/> XML 네임스페이스 요소에서 제공하는 옵션보다 상위 집합입니다.
자세한 내용은 @Sql 및
@SqlConfig의
개별 속성에 대한 javadoc을 참고하십시오.
@Sql기본적으로 SqlScriptsTestExecutionListener는 @Sql을 사용하여 설정된 script에 대해
원하는 트랜잭션 시맨틱을 추론합니다. 구체적으로, SQL script는 @SqlConfig의
transactionMode 속성에 설정된 값과 test의 ApplicationContext 내에
PlatformTransactionManager가 존재하는지 여부에 따라 트랜잭션 없이, 기존 Spring-managed
트랜잭션 내(예: @Transactional로 어노테이션된 test에 대해
TransactionalTestExecutionListener가 관리하는 트랜잭션), 또는 분리된 트랜잭션 내에서
실행됩니다. 그러나 최소한으로 test의 ApplicationContext 내에 javax.sql.DataSource가
존재해야 합니다.
SqlScriptsTestExecutionListener가 DataSource와 PlatformTransactionManager를 탐지하고
트랜잭션 시맨틱을 추론하는 데 사용하는 알고리즘이 요구 사항에 맞지 않는 경우,
@SqlConfig의 dataSource 및 transactionManager 속성을 설정하여 명시적인 이름을
지정할 수 있습니다. 또한 @SqlConfig의 transactionMode 속성을 설정하여
트랜잭션 전파 동작(예: script를 분리된 트랜잭션에서 실행할지 여부)을 제어할
수 있습니다. @Sql과 함께 사용하는 트랜잭션 관리에 대해 지원되는 모든 옵션을
자세히 설명하는 것은 이 레퍼런스 매뉴얼의 범위를 벗어나지만,
@SqlConfig 및
SqlScriptsTestExecutionListener
의 javadoc은 자세한 정보를 제공하며, 다음 예제는 JUnit Jupiter와 @Sql이 있는
트랜잭션 test를 사용하는 일반적인 testing 시나리오를 보여 줍니다:
1@SpringJUnitConfig(TestDatabaseConfig.class) 2@Transactional 3class TransactionalSqlScriptsTests { 4 5 final JdbcTemplate jdbcTemplate; 6 7 @Autowired 8 TransactionalSqlScriptsTests(DataSource dataSource) { 9 this.jdbcTemplate = new JdbcTemplate(dataSource); 10 } 11 12 @Test 13 @Sql("/test-data.sql") 14 void usersTest() { 15 // verify state in test database: 16 assertNumUsers(2); 17 // run code that uses the test data... 18 } 19 20 int countRowsInTable(String tableName) { 21 return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName); 22 } 23 24 void assertNumUsers(int expected) { 25 assertEquals(expected, countRowsInTable("user"), 26 "Number of rows in the [user] table."); 27 } 28}
1@SpringJUnitConfig(TestDatabaseConfig::class) 2@Transactional 3class TransactionalSqlScriptsTests @Autowired constructor(dataSource: DataSource) { 4 5 val jdbcTemplate: JdbcTemplate = JdbcTemplate(dataSource) 6 7 @Test 8 @Sql("/test-data.sql") 9 fun usersTest() { 10 // verify state in test database: 11 assertNumUsers(2) 12 // run code that uses the test data... 13 } 14 15 fun countRowsInTable(tableName: String): Int { 16 return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName) 17 } 18 19 fun assertNumUsers(expected: Int) { 20 assertEquals(expected, countRowsInTable("user"), 21 "Number of rows in the [user] table.") 22 } 23}
usersTest() 메서드가 실행된 후 database를 정리할 필요가 없다는 점에 주의하십시오.
test method 내에서 또는 /test-data.sql script 내에서 database에 가해진 모든 변경 사항은
TransactionalTestExecutionListener에 의해 자동으로 롤백되기 때문입니다(자세한 내용은
transaction management
을 참고하십시오).
@SqlMergeModemethod-level @Sql 선언을 class-level 선언과 병합하는 것이 가능합니다. 예를 들어,
database schema 또는 일부 공통 test data에 대한 설정을 test class당 한 번만
제공하고, test method별로 추가적인 사용 사례별 test data를 제공할 수 있습니다. @Sql
병합을 활성화하려면 test class 또는 test method에 @SqlMergeMode(MERGE)를
어노테이션으로 추가하십시오. 특정 test method(또는 특정 test subclass)에 대해 병합을
비활성화하려면 @SqlMergeMode(OVERRIDE)를 통해 기본 모드로 다시 전환할 수 있습니다.
예제 및 자세한 내용은
@SqlMergeMode annotation documentation section
을 참고하십시오.
Transaction Management
Parallel Test Execution