DbUnit - JdbcSQLException:找不到函数“*”

DbUnit - JdbcSQLException: Function "*" not found

我在 MS SQL 服务器中有一个从 Java 代码调用的用户定义函数,当 运行 在 H2 数据库中进行集成测试时,该代码似乎未定义。您可以在 the previous question.

中找到我的代码

测试代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {H2Config.class})
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DbUnitTestExecutionListener.class,
        TransactionalTestExecutionListener.class
})
@TransactionConfiguration(defaultRollback = true)
public class TableDaoTest {

    @Autowired
    private TableDao tableDao;

    @Test
    @DatabaseSetup("/datasets/import.xml")
    public void testMethod01() {
        tableDao.getRecordsByGroup();
        ...

数据库模式由 Hibernate 自动生成。如您所见,测试数据由 DbUnit 使用 xml 数据集填充。这个测试失败了,因为我在 MS SQL 服务器数据库中存在的函数在 H2 数据库中未定义。

应用程序日志:

Caused by: org.hibernate.exception.GenericJDBCException: could not prepare statement
    ...
Caused by: org.h2.jdbc.JdbcSQLException: Function "SAFE_MOD" not found; SQL statement:
    select table10_.id, table10_.value, ... from Table1 table10_ where table10_.group1=dbo.safe_mod(?, ?);
    ...

如何在 DbUnit 测试之前导入/创建函数?

H2 数据库不支持用户定义的 SQL 函数。但是,在此数据库中,Java 函数也可以用作存储过程。

@SuppressWarnings("unused")
public class H2Function {
    public static int safeMod(Integer n, Integer divider) {
        if (divider == null) {
            divider = 5000;
        }

        return n % divider;
    }

}

请注意,仅支持静态 Java 方法; class 和方法都必须是 public.

Java函数必须通过调用CREATE ALIAS ... FOR声明(在数据库中注册)才能使用:

CREATE ALIAS IF NOT EXISTS safe_mod DETERMINISTIC FOR "by.naxa.H2Function.safeMod";

这个语句应该在任何测试之前执行所以我决定把它放在连接初始化中 SQL:

@Bean
public DataSource dataSource() {
    BasicDataSource dataSource = new BasicDataSource();

    dataSource.setDriverClassName("org.h2.Driver");
    dataSource.setUrl("jdbc:h2:mem:my_db_name");
    dataSource.setUsername("sa");
    dataSource.setPassword("");   
    dataSource.setConnectionInitSqls(Collections.singleton(
        "CREATE ALIAS IF NOT EXISTS safe_mod DETERMINISTIC FOR \"by.naxa.H2Function.safeMod\";"));

    return dataSource;
}

归功于 naxa,此解决方案基于他们的解决方案。这是来自 postgres 的 'stub' WORD_SIMILARITY,

@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(
        locations = "classpath:application-test.properties")
public class testServiceTests {

    @Autowired
    private MyService myService;

    @Test
    public void someTest() {

    }
}

这应该在你的申请中-test.properties

spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:~/my_database;MODE=PostgreSQL;INIT=CREATE ALIAS IF NOT EXISTS WORD_SIMILARITY DETERMINISTIC FOR "com.example.H2Function.wordSimilarity";TRACE_LEVEL_FILE=0;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;
spring.datasource.username=postgres
spring.datasource.password=postgres
hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
hibernate.flushMode=FLUSH_AUTO
hibernate.hbm2ddl.auto=create-drop
spring.datasource.data=classpath:V1__base-schema.sql

这里是要替换的H2函数

@SuppressWarnings("unused")
public class H2Function {

    public static double wordSimilarity(String string, String word) {
        if ( word== null ) {

            return 0;
        }

        return 0.5;
    }

}

@naXa 和@Alan 方法适用于标量值函数。对于 Table 值函数使用 ResultSet:

package com.package.app;
import org.h2.tools.SimpleResultSet;

@SuppressWarnings("unused")
public class H2Functions {

    // All function params goes here
    // LocalDate not working here, we have to use java.sql.Date
    public static ResultSet gePrice(Long recipientId, Long currencyId, Date priceDate) {

        SimpleResultSet rs = new SimpleResultSet();
        rs.addColumn("price", Types.DECIMAL, 10, 0);
        rs.addColumn("priceDate", Types.TIMESTAMP, 10, 0);

        rs.addRow(new BigDecimal("123.23"), new Timestamp(LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)) );

        return rs;
    }
}

示例 application.yml 配置:

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:h2:DB_NAME;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    name:
    username:
    password:
    hikari:
      auto-commit: false
      # create alias for functions on db connection initialization
      connection-init-sql: "CREATE ALIAS IF NOT EXISTS SAFE_MOD DETERMINISTIC FOR \"com.package.app.H2Functions.gePrice\";"

然后你可以用你的 POJO 包装响应。

您可以在 H2 文档中找到有关用户定义函数的更多信息: http://www.h2database.com/html/features.html#user_defined_functions