【问题标题】:DbUnit - JdbcSQLException: Function "*" not foundDbUnit - JdbcSQLException:未找到函数“*”
【发布时间】:2017-09-25 15:06:01
【问题描述】:

我在 MS SQL Server 中有一个从 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 server DB 中存在的函数在 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 测试前导入/创建函数?

【问题讨论】:

    标签: java sql-server user-defined-functions dbunit


    【解决方案1】:

    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 方法;类和方法都必须是公共的。

    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;
    }
    

    【讨论】:

      【解决方案2】:

      感谢 naxa,此解决方案基于他们的解决方案。这是为了“存根”来自 postgres 的 WORD_SIMILARITY,

      @RunWith(SpringRunner.class)
      @SpringBootTest
      @TestPropertySource(
              locations = "classpath:application-test.properties")
      public class testServiceTests {
      
          @Autowired
          private MyService myService;
      
          @Test
          public void someTest() {
      
          }
      }
      

      这应该在您的 application-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;
          }
      
      }
      

      【讨论】:

        【解决方案3】:

        @naXa 和@Alan 方法适用于标量值函数。对于表值函数使用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

        【讨论】:

        • 使用你自己的函数名代替SAFE_MOD。例如GET_PRICE.
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-07-28
        • 1970-01-01
        • 2015-03-27
        • 1970-01-01
        • 2016-10-05
        • 1970-01-01
        • 2022-01-15
        相关资源
        最近更新 更多