美文网首页
DataSet导入三个坑

DataSet导入三个坑

作者: antony已经被占用 | 来源:发表于2020-10-05 09:26 被阅读0次

1外键

本小节从数据库实际使用的视角,给出了在各种场景下的解决方案。对工具提供的功能进行一个综合使用。
外键是一个常见的保证数据库内容完整性的一种方式。当然现在出于性能考虑,在互联网企业中比较少甚至禁止使用外键。在DBRider中,提供了以下的与外键相关的功能
1)@DataSet注解中的disableConstraints属性
这个属性如果为true,则可以暂时去除外键约束,以便于数据导入操作。

@DataSet(value ="users.yml", strategy = SeedStrategy.UPDATE,
       disableConstraints = true,cleanAfter = true,transactional = true)
public void shouldLoadDataSetConfigFromAnnotation(){

 }

2)@ExportedDataSet导出时一并导出关联表
在导入某个数据库表的数据时,如果存在外键的话,经常会发生因为外键不存在导致的数据无法导入的问题。为了预防此类事件的发生,一个好的措施是在导出目标表时将依赖数据表一并导出。DBRider在@ExportDataSet中通过dependentTables提供了该功能。如下例,

@Test
@DataSet("datasets/yml/users.yml")
@ExportDataSet(format = DataSetFormat.YML, includeTables = {"USER"}, dependentTables = true, outputName = "target/exported/yml/dependentTables.yml")
public void shouldExportYMLDataSetUsingIncludesWithDependentTables() {
}

只要简单地将dependentTables设置为 true,就可以实现上述需求。虽然只是导出USER表,但是TWEET和FOLLOWER两个表也被导出了。因为USER表中使用了这两个表中的主键作为外键,表达用户粉与被粉的关系。

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <TWEET ID="abcdef12345" CONTENT="dbunit rules!" DATE="2020-10-02 17:07:52.0" USER_ID="1"/>
  <USER ID="1" NAME="@realpestano"/>
  <USER ID="2" NAME="@dbunit"/>
  <FOLLOWER ID="1" USER_ID="1" FOLLOWER_ID="2"/>
</dataset>

自增序列与ID主键冲突

在往数据库中导入数据时,除了因为外键约束不满足导致无法导入的问题之外,另外一种常见的问题是主键冲突,或者更确切一点说是某个带有自增ID序列带来的冲突。如果在数据库中插入该表的记录,则新插入的值不能和已有的值重复,而且必须大于其中最大的一个值。一般通过程序写入数据库记录大多是新增记录的场景,不指定该列的值,只将其他列的值插入,让ID按照自增规则由数据库自行填写的方式进行。而在通过数据库导入时,属于控制数据库上下文的场景。往往就会产生冲突,
1)导入记录中需指定自增ID的主键值,以保证被导入数据的完整性。
2)待导入的数据源自数据库之前的某一次导出的数据集。随后数据库经历了反复插入删除等操作后,自增主键值已经向后偏移。例如针对某个场景有多个测试用例需要导入数据导同一个表。后续用例的执行上下文于是受到了前面执行用例的影响。
3)导入时通过默认的CLEAN_INSERT策略进行导入,虽然删除了原先存在的数据,但是数据库的自增主键值并没有回退,这样就导致导入记录时报主键冲突。
那是否可以通过采用INSERT/UPDATE的策略呢?之前在介绍各种导入策略时有提及,只INSERT而不是先删除再导入时,会存在数据记录重复无法导入的问题,而在这个场景下,因为主键冲突带来的问题还是没有解决。那是否可以使用UPDATE策略来更新各个记录的主键ID呢?考虑到一般采用主键ID的是记录类数据的场景,无法保证原记录的存在,所以也不太适合使用UPDATE的策略。
从上述问题描述中,读者也理解到了问题产生的原因并不在主键ID和记录自身,而是因为在原数据集导出后,在保持数据不变的情况下,数据库中该表经历了插入和删除后,自增序列已经向后偏移。
于是,只要保证自增序列ID的值小于待插入数据的值,该问题就能规避掉了。利用@DataSet的伴随操作就可以了
解决办法1:利用executeStatementsBefore 重置自增序列
@Test
@DataSet(value = "datasets/yml/users.yml",
executeStatementsBefore = "alter table USER auto_increment = 100;")
public void shouldSeedDataSetDisablingContraintsViaStatement() {
//......
}

通过重置该表的Sequence到一个小于待导入数据集中最小ID的值,再配合随后的CLEAN_INSERT操作,就可以规避该问题了。当然这个操作需要在准备好数据集后,根据具体内容进行修改上述executeStatementsBefore 。因为很有可能待导入数据源自某一份导出数据,根据测试用例需求稍加修改而来,因此该部分修改也具备一定的通用性,工作量可控。
另外,上述示例代码是MYSQL语法,如果目标数据库是ORACLE,重置Sequence的写法略有差别。
解决办法2: 利用TRUNCATE_INSERT的SeedStrategy
通过源码分析,可以了解到DBRider还额外提供了TRUNCATE_INSERT的操作,虽然该功能未在其官方文档中说明,但是也可以直接使用。

public enum SeedStrategy {
    CLEAN_INSERT(DatabaseOperation.CLEAN_INSERT),
    TRUNCATE_INSERT(new CompositeOperation(DatabaseOperation.TRUNCATE_TABLE, DatabaseOperation.INSERT)),
    INSERT(DatabaseOperation.INSERT),
    REFRESH(DatabaseOperation.REFRESH),
    UPDATE(DatabaseOperation.UPDATE);
//......
}

利用TRUNCATE操作提供的重置序列的功能,强制重新初始化,进而保证了数据导入时不再会发生自增主键冲突的问题。
当然也可以参考被测系统向数据库插入数据时不指定ID,而是由数据库自行决定的方式,不过这个方案相比前面的来说略显复杂,涉及到导出数据时剔除该列数据,工作量较大,不是很推荐。感兴趣的读者可以自行尝试。

Null处理

数据库中最容易让程序员踩坑的问题如果进行一个排名,估计Nullable会排在最前面。如果一个数据列是Nullable,在导入导出时会遇到不少问题。
首先DBRider 在使用JSON格式在导出null时,会在该条记录的最后位置额外多一个逗号,导致导出内容不符合JSON格式,需要手工修改。当然,该问题在报告之后很快就被修复了。详见fix bug with extra comma in json object #160,但似乎还是未解决,如果遇到,需要手工处理,或尝试最新的DBRider版本。
其次是在数据导入时的问题,DBUnit一个著名的bug是在导入XML、CSV格式的文件时,如果待导入文件的第一条记录的Nullable列的数据正好是Null,那么DBUnit会忽略该列,整列数据都会被丢失,即使后续数据记录中该列不为Null,也会被忽略而不导入进数据库。
解决办法1:调整数据行顺序,让第一条记录包含不为Null
这样做是最简单的处理方式,正所谓将问题解决在发生前。不过数据文件较多时手工调整也比较麻烦,或者记录顺序调整会影响测试用例执行结果时,这样调整就会带来麻烦了。
解决办法2:XML导入时指定DTD
DBUnit给出的一个解决办法是,在导出XML文件的同时,再导出一份XML_DTD,来指明数据库的列。导入数据时,利用DTD来指定数据列,如下例:

<!--ELEMENT COMPANY EMPTY-->
<!--ATTLIST COMPANY
    ID CDATA #REQUIRED
    NAME CDATA #REQUIRED
    PARENT CDATA #IMPLIED
  -->
]>
<dataset>
  <company id="1" name="HQ"></company>
  <company id="2" name="spain hq" parent="1"></company>
  <company id="3" name="barcelona" parent="2"></company>
</dataset>

这样就不会发生因为Null导致的数据列丢失问题了。
解决办法3:利用DBRider提供的JSON/YAML文件格式进行导入
新的数据类型规避了上述DBUnit的缺陷,因此不会再发生整列数据丢失的问题了。这也是笔者喜欢DBRier的原因之一。
还有什么样的数据导入导出的坑呢?欢迎与笔者讨论。

相关文章

网友评论

      本文标题:DataSet导入三个坑

      本文链接:https://www.haomeiwen.com/subject/vfzouktx.html