Java 如何为动态创建数据库表的方法编写单元测试?

Java 如何为动态创建数据库表的方法编写单元测试?,java,spring,jpa,Java,Spring,Jpa,tl;博士。我有一种动态创建新数据库表的方法,我想为它编写一个单元测试。不幸的是,测试运行程序没有以正确的方式在测试后执行回滚,并且在测试完成后表仍然保留在数据库中。我该怎么办 长话短说: 我对Java持久性和Java Spring都不是很熟悉,所以,如果您觉得当前的解决方案很难看(对我来说,它相当难看),请告诉我如何改进它-我将非常感谢您的意见 我有一个带有以下addStaticDatastore方法实现的SysDatastoreService @Service public class Sy

tl;博士。我有一种动态创建新数据库表的方法,我想为它编写一个单元测试。不幸的是,测试运行程序没有以正确的方式在测试后执行回滚,并且在测试完成后表仍然保留在数据库中。我该怎么办

长话短说: 我对Java持久性和Java Spring都不是很熟悉,所以,如果您觉得当前的解决方案很难看(对我来说,它相当难看),请告诉我如何改进它-我将非常感谢您的意见

我有一个带有以下
addStaticDatastore
方法实现的
SysDatastoreService

@Service
public class SysDatastoreServiceImpl implements SysDatastoreService {
    @Autowired
    private SysDatastoreRepository datastoreRepository;

    @Autowired
    private DataSource dataSource;

    @Override
    @Transactional
    public Optional<SysDatastore> addStaticDatastore(String name, String tableName, String ident, Long ord) {
        String createTableSql = PostgresTableSqlBuilder.createTableInPublicSchemaWithBigintPkAndFkId(
                tableName,
                SysObject.TABLE_NAME,
                Optional.of(SysObject.ID_COLUMN_NAME)).buildSql();

        Optional<SysDatastore> sysDatastore = Optional.empty();

        try(
                Connection connection = dataSource.getConnection();
                Statement statement = connection.createStatement()
        ) {
            connection.setAutoCommit(false);
            Savepoint beforeTableCreation = connection.setSavepoint();

            try {
                statement.execute(createTableSql);
                sysDatastore = Optional.ofNullable(
                        datastoreRepository.save(new SysDatastore(name, tableName, DatastoreType.STATIC, ident, ord)));
            } catch(SQLException e) {
                e.printStackTrace();
            }

            if(!sysDatastore.isPresent()) {
                connection.rollback(beforeTableCreation);
            } else {
                connection.commit();
            }
        } catch(SQLException e1) {
            e1.printStackTrace();
        }

        return sysDatastore;
    }
}
这个测试看起来相当简单:我只是比较所有字段,然后检查数据库中是否有新表

但是,当我运行它两次或更多次时,该测试失败。查看数据库,我注意到表
new\u datastore\u table
仍然保留在模式中。我猜,由于手工编写的事务和原始sql执行,它没有正确回滚,但我不确定

问题:我应该如何以适当的方式编写此方法的测试用例?如果当前的方法根本错误,应该如何改变


旁注:我使用PostgreSQL数据库,不能用非关系数据库替换它。

首先,CREATE TABLE是DDL语句,而不是DML语句。这意味着回滚不会删除该表。如果要清理数据库,必须在
@After
@AfterClass
方法中明确删除它

@Service
public class SysDatastoreServiceImpl implements SysDatastoreService {
    @Autowired
    private SysDatastoreRepository datastoreRepository;

    @Autowired
    private DataSource dataSource;

    @Override
    @Transactional
    public Optional<SysDatastore> addStaticDatastore(String name, String tableName, String ident, Long ord) {
        String createTableSql = PostgresTableSqlBuilder.createTableInPublicSchemaWithBigintPkAndFkId(
                tableName,
                SysObject.TABLE_NAME,
                Optional.of(SysObject.ID_COLUMN_NAME)).buildSql();

        Optional<SysDatastore> sysDatastore = Optional.empty();

        try(
                Connection connection = dataSource.getConnection();
                Statement statement = connection.createStatement()
        ) {
            connection.setAutoCommit(false);
            Savepoint beforeTableCreation = connection.setSavepoint();

            try {
                statement.execute(createTableSql);
                sysDatastore = Optional.ofNullable(
                        datastoreRepository.save(new SysDatastore(name, tableName, DatastoreType.STATIC, ident, ord)));
            } catch(SQLException e) {
                e.printStackTrace();
            }

            if(!sysDatastore.isPresent()) {
                connection.rollback(beforeTableCreation);
            } else {
                connection.commit();
            }
        } catch(SQLException e1) {
            e1.printStackTrace();
        }

        return sysDatastore;
    }
}
但是您真的需要在PostgreSQL数据库上进行测试吗?Spring非常支持,默认的嵌入式数据库是HSQL,它非常支持postgresql语法。如果没有复杂的语句,就足够了,可以避免(潜在破坏性的)单元测试对主数据库造成混乱

您可以使用
@BeforeClass
方法创建数据库。下面是一个过于简单的例子:

private static DriverManagerDataSource dataSource;

@BeforeClass
public static void setupClass() throws Exception {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScript(new ClassPathResource("path/to/package/defaults.sql"));
    dataSource = new DriverManagerDataSource(); 
    dataSource.setUrl("jdbc:hsqldb:mem:pgtest;sql.syntax_pgs=true");
    dataSource.setUsername("SA");

    Connection con = dataSource.getConnection();
    assertNotNull(con);
    populator.populate(con);
    con.close();
}

首先,CREATE TABLE是DDL语句,而不是DML语句。这意味着回滚不会删除该表。如果要清理数据库,必须在
@After
@AfterClass
方法中明确删除它

@Service
public class SysDatastoreServiceImpl implements SysDatastoreService {
    @Autowired
    private SysDatastoreRepository datastoreRepository;

    @Autowired
    private DataSource dataSource;

    @Override
    @Transactional
    public Optional<SysDatastore> addStaticDatastore(String name, String tableName, String ident, Long ord) {
        String createTableSql = PostgresTableSqlBuilder.createTableInPublicSchemaWithBigintPkAndFkId(
                tableName,
                SysObject.TABLE_NAME,
                Optional.of(SysObject.ID_COLUMN_NAME)).buildSql();

        Optional<SysDatastore> sysDatastore = Optional.empty();

        try(
                Connection connection = dataSource.getConnection();
                Statement statement = connection.createStatement()
        ) {
            connection.setAutoCommit(false);
            Savepoint beforeTableCreation = connection.setSavepoint();

            try {
                statement.execute(createTableSql);
                sysDatastore = Optional.ofNullable(
                        datastoreRepository.save(new SysDatastore(name, tableName, DatastoreType.STATIC, ident, ord)));
            } catch(SQLException e) {
                e.printStackTrace();
            }

            if(!sysDatastore.isPresent()) {
                connection.rollback(beforeTableCreation);
            } else {
                connection.commit();
            }
        } catch(SQLException e1) {
            e1.printStackTrace();
        }

        return sysDatastore;
    }
}
但是您真的需要在PostgreSQL数据库上进行测试吗?Spring非常支持,默认的嵌入式数据库是HSQL,它非常支持postgresql语法。如果没有复杂的语句,就足够了,可以避免(潜在破坏性的)单元测试对主数据库造成混乱

您可以使用
@BeforeClass
方法创建数据库。下面是一个过于简单的例子:

private static DriverManagerDataSource dataSource;

@BeforeClass
public static void setupClass() throws Exception {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScript(new ClassPathResource("path/to/package/defaults.sql"));
    dataSource = new DriverManagerDataSource(); 
    dataSource.setUrl("jdbc:hsqldb:mem:pgtest;sql.syntax_pgs=true");
    dataSource.setUsername("SA");

    Connection con = dataSource.getConnection();
    assertNotNull(con);
    populator.populate(con);
    con.close();
}

我不确定在测试中是否会在类上遵循@Transactional,请尝试将其放在实际方法上(
testAddStaticDatastore
)。在这里,我的测试工作正常,因为事务会回滚。@FlorianSchaetz,不幸的是,这对我不起作用。我还有其他带有顶级
@Transactional
注释的测试类,它的行为与预期的一样。但是,当然,他们不会用手工编写的事务管理调用方法。难道你不能用一个拆卸方法来删除数据库吗?@Kayaman,是的,当然,但这似乎是一个修复效果的方法,但不是修复原因的方法。我担心将来可能会出现错误。安装->测试->拆卸是一种相当标准的构造,在安装时构建测试数据库,然后将其拆除。请记住,您可能会试图使测试比它应该的更聪明。我不确定测试中类是否会遵循@Transactional,请尝试将它放在实际方法上(
testAddStaticDatastore
)。在这里,我的测试工作正常,因为事务会回滚。@FlorianSchaetz,不幸的是,这对我不起作用。我还有其他带有顶级
@Transactional
注释的测试类,它的行为与预期的一样。但是,当然,他们不会用手工编写的事务管理调用方法。难道你不能用一个拆卸方法来删除数据库吗?@Kayaman,是的,当然,但这似乎是一个修复效果的方法,但不是修复原因的方法。我担心将来可能会出现错误。安装->测试->拆卸是一种相当标准的构造,在安装时构建测试数据库,然后将其拆除。请记住,您可能正试图使您的测试比它应该的更聪明。不幸的是,当前项目没有用Java定义好的数据库方案(啊!这让我一直很生气),因此,
HSQL
现在无法使用(我有代码重构计划)。谢谢你,我相信,这是正确的解决方案。再问一个问题:“回滚不会删除表”-因此,使用
connection.rollback(在创建表之前)addStaticDatastore
方法中的code>是否也无效?据我所知,Postgres支持
CREATE TABLE
语句的事务处理。@很快,如果您有一个创建数据库架构的.sql文件,就足够了。我的示例使用
ResourceDatabasePopulator
传递一个(这里有一个,但可能有多个)sql脚本来初始化新数据库。对于第二个问题,回滚并不是无效的,因为它对DDL语句没有任何影响:您只能回滚插入、更新、删除(和锁定)。不幸的是,当前项目在Java中没有定义良好的数据库方案(啊!这让我一直很生气),因此,
HSQL
目前无法使用(我有代码重构计划)。谢谢你,我相信,这是正确的解决方案。再问一个问题:“回滚不会删除表”-因此,使用
connection.rollback(在创建表之前)addStaticDatastore
方法中的code>为als