# Spring 中的 @Sql 注解

大概我们在别处也用不上 Spring 的 @Sql 注解,主要也就是在单元测试中它才有用武之地。

# 1. @Sql 注解

@Sql 注解可以执行 SQL 脚本,也可以执行 SQL 语句。它既可以加上类上面,也可以加在方法上面。

默认情况下,方法上的 @Sql 注解会覆盖类上的 @Sql 注解。但可以通过 @SqlMergeMode 注解来修改此默认行为。

@Sql 有下面的属性:

属性 说明
config 与注解 @SqlConfig 作用一样,用来配置 注释前缀分隔符 等。
executionPhase 决定 SQL 脚本或语句什么时候会执行,默认是 BEFORE_TEST_METHOD 。
statements 配置要一起执行的 SQL 语句。
scripts 配置 SQL 脚本路径。
value scripts 的别名,它不能和 scripts 同时配置,但 statements 可以。

例如:

@Sql({ "/drop_schema.sql", "/create_schema.sql" })
@Sql(scripts = "/insert_data1.sql", statements = "insert into student(id, name) values (100, 'Shiva')")
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class SqlTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void fetchRows1() {
        List<Map<String, Object>> students = jdbcTemplate.queryForList("SELECT * FROM student");
        assertEquals(3, students.size());
    }

    @Sql("/insert_more_data1.sql")
    @Test
    public void fetchRows2() {
        List<Map<String, Object>> students = jdbcTemplate.queryForList("SELECT * FROM student");
        assertEquals(5, students.size());
    }
} 
  • drop_schema.sql :

    drop table if exists student; 
    
  • create_schema.sql :

    CREATE TABLE student (
        id INT NOT NULL,
        name VARCHAR(50) NOT NULL,
        PRIMARY KEY(id)
    ); 
    
  • insert_data1.sql :

    insert into student(id, name) values (101, 'Mohan');
    insert into student(id, name) values (102, 'Krishna'); 
    
  • insert_more_data1.sql :

    insert into student(id, name) values (103, 'Indra');
    insert into student(id, name) values (104, 'Chandra'); 
    

# 2. @SqlConfig(了解)

@SqlConfig 用于配置如何去解释 @Sql 注解中指定的 Sql 脚本。

@SqlConfig 用于配置 @Sql 的一些个性化的行为,通常,我们不会那么有个性。

@SqlConfig 可以用于类上,也可以用于方法上。

@Sql 注解也有一个 config 属性,作用与 @SqlConfig 相同,不同的是作用域只在对应的 @Sql 注解范围。它的优先级也大于类注解的 @SqlConfig 。

属性 说明
blockCommentStartDelimiter 多行注释开始字符,默认是 /*
blockCommentEndDelimiter 多行注释结束字符,默认是 */
commentPrefix 单行注释前缀,默认是
commentPrefixes 指定多个单行注释前缀,默认是 ["–"]
dataSource 指定脚本执行的数据库的名字,只有在多个数据源时需要指定
encoding 指定 sql 脚本文件的字符编码。
errorMode 配置错误模式,默认是 SqlConfig.ErrorMode 的 DEFAULT
separator 配置脚本语句分隔符,默认是 \n
transactionManager 指定 transactionManager bean,只有有多个 transactionManager 时需要指定
transactionMode 指定脚本执行的事务模式,默认是 SqlConfig.TransactionMode 的 DEFAULT

例子:

@SqlConfig(commentPrefix = "#")
@Sql({ "/drop_schema.sql", "/create_schema.sql" })
@Sql(scripts = { "/insert_data2.sql" })
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class SqlConfigTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void fetchRows1() {
        List<Map<String, Object>> students = jdbcTemplate.queryForList("SELECT * FROM student");
        assertEquals(2, students.size());
    }

    @Sql(scripts = "/insert_more_data2.sql", config= @SqlConfig(commentPrefix = "~"))
    @Test
    public void fetchRows2() {
        List<Map<String, Object>> students = jdbcTemplate.queryForList("SELECT * FROM student");
        assertEquals(4, students.size());
    }
}

insert_data2.sql :

-- Insert initial data
insert into student(id, name) values (101, 'Mohan');
insert into student(id, name) values (102, 'Krishna'); 

# 3. @SqlMergeMode(了解)

@SqlMergeMode 可以加在类上,也可以加在方法上。用于指示方法上的 @Sql 和类上 @Sql 注解配置是否合并。方法上的 @SqlMergeMode 注解优先级更高。默认值是 SqlMergeMode.MergeMode 的 OVERRIDE 。

和 @SqlConfig 注解一样,@SqlMergeMode 注解也是用于配置 @Sql 的一些个性化的行为,通常,我们不会那么有个性。

@SqlMergeMode(MergeMode.MERGE)
@Sql({ 
    "/drop_schema.sql", 
    "/create_schema.sql", 
    "/insert_data1.sql" 
})
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
public class SqlMergeModeTest {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Sql(statements = "insert into student(id, name) values (100, 'Shiva')")    
    @Test
    public void fetchRows1() {
        List<Map<String, Object>> students = jdbcTemplate.queryForList("SELECT * FROM student");
        assertEquals(3, students.size());
    }

    @SqlMergeMode(MergeMode.OVERRIDE)    
    @Sql("/insert_more_data1.sql")
    @Test
    public void fetchRows2() {
        List<Map<String, Object>> students = jdbcTemplate.queryForList("SELECT * FROM student");
        assertEquals(5, students.size());
    }
}