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