为什么在查询对象时 Hibernate 重新更新会导致 Batch update 返回意外的行数?
Why Hibernate re-update when querying objects causes Batch update returned unexpected row count?
我正在编写一个测试 class 来测试我的 DAO (ProjectDao)。数据库中只有一个 table(对于项目)并且在持久性对象中没有关联。
这是我的基础测试class(针对@Before 和@After 方法):
@TransactionConfiguration
@Transactional
public class DaoTestBase extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
protected ProjectDao projectDao;
@Autowired
protected List<Project> dummyProjects;
@Before
public void initTestData() throws Exception {
projectDao.deleteAll();
for (Project project : dummyProjects) {
projectDao.saveOrUpdate(project);
}
}
@After
public void clearTestData() throws Exception {
projectDao.deleteAll();
}
}
这里是实际的测试 class,有 2 种测试方法:
@ContextConfiguration(locations = {
"classpath*:database/dummy-beans-test.xml",
"classpath*:springcontext/*test.xml"})
public class ProjectDaoTest extends DaoTestBase {
private static final Comparator<Project> PROJECT_COMPARATOR =
new Comparator<Project>() {
public int compare(Project o1, Project o2) {
if (o1.getNumber() < o2.getNumber()) {
return -1;
} else if (o1.getNumber() == o2.getNumber()) {
return 0;
} else {
return 1;
}
};
};
@Test
public void shouldQueryTheSameAsDummyObjects() throws Exception {
List<Project> projects = projectDao.getAll();
Assert.assertEquals(dummyProjects.size(), projects.size());
Set<Project> preProjects = new TreeSet<Project>(PROJECT_COMPARATOR);
preProjects.addAll(dummyProjects);
for (Project project : projects) {
if (preProjects.contains(project)) {
preProjects.remove(project);
}
}
Assert.assertEquals(0, preProjects.size());
}
@Test
public void deleteSomeProjectsShouldRemoveThem() throws Exception {
List<Project> projects = projectDao.getAll();
Assert.assertEquals(3, projects.size());
for (Project toDeleteProject : projects) {
projectDao.delete(toDeleteProject);
List<Project> remainProjects = projectDao.getAll();
for (Project project : remainProjects) {
if (project.getNumber() == toDeleteProject.getNumber()) {
Assert.assertTrue(false);
}
}
}
}
}
问题是当我忽略上面的一种测试方法时,一切都测试正常但是当我测试它们时,它会导致:
Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
检查日志,我看到在执行第一个测试时,Hibernate 执行这些 SQL:
Hibernate:
delete
from
project_table
Hibernate:
insert
into
project_table
(pro_customer, pro_end_date, pro_name, pro_number, pro_start_date, pro_status)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
project_table
(pro_customer, pro_end_date, pro_name, pro_number, pro_start_date, pro_status)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
project_table
(pro_customer, pro_end_date, pro_name, pro_number, pro_start_date, pro_status)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
select
this_.id as id0_0_,
this_.pro_customer as pro2_0_0_,
this_.pro_end_date as pro3_0_0_,
this_.pro_name as pro4_0_0_,
this_.pro_number as pro5_0_0_,
this_.pro_start_date as pro6_0_0_,
this_.pro_status as pro7_0_0_
from
project_table this_
Hibernate:
delete
from
project_table
并且在执行第二个测试时,他们会:
Hibernate:
delete
from
project_table
Hibernate:
update
project_table
set
pro_customer=?,
pro_end_date=?,
pro_name=?,
pro_number=?,
pro_start_date=?,
pro_status=?
where
id=?
Hibernate:
update
project_table
set
pro_customer=?,
pro_end_date=?,
pro_name=?,
pro_number=?,
pro_start_date=?,
pro_status=?
where
id=?
Hibernate:
update
project_table
set
pro_customer=?,
pro_end_date=?,
pro_name=?,
pro_number=?,
pro_start_date=?,
pro_status=?
where
id=?
Hibernate:
delete
from
project_table
在每次测试中这些 Hibernate 操作之前和之后,我注意到 Hibernate 打开和关闭会话(并回滚事务)。所以我的问题是:
1/为什么在第二个测试的@Before方法中,Hibernate没有插入数据(他们在第一个方法中插入的数据被回滚了?!)。
2/为什么第二种测试方法我只getAll(使用条件)和Hibernate更新了数据?!
3/ 为什么Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
是这种情况?
在 DAO 的实现中,我从 SessionFactory 检索了当前会话,使用条件进行查询。例如。对于 getAll() 方法:
sessionFactory.getCurrentSession()
.createCriteria(type)
.list()
如果我理解正确,您的项目实体实际上是来自 Spring 上下文的 Spring bean。默认情况下,Spring 上下文在测试之间共享。
第一个测试使用 saveOrUpdate()
将这些实体附加到会话,从而为它们生成一个 ID。然后从数据库中删除它们。
第二个测试然后使用这些完全相同的项目实例作为参数调用 saveOrUpdate()
。由于它们已经有一个 ID,Hibernate 认为它们是分离的项目实例,应该已经存在于数据库中,并使用更新查询来更新它们。但由于它们不再存在于数据库中,因此查询失败。
您不应在测试之间重复使用项目实体实例。在您的 @Before
方法中从头开始创建实例,而不是从 Spring 上下文中获取它们。
我正在编写一个测试 class 来测试我的 DAO (ProjectDao)。数据库中只有一个 table(对于项目)并且在持久性对象中没有关联。
这是我的基础测试class(针对@Before 和@After 方法):
@TransactionConfiguration
@Transactional
public class DaoTestBase extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
protected ProjectDao projectDao;
@Autowired
protected List<Project> dummyProjects;
@Before
public void initTestData() throws Exception {
projectDao.deleteAll();
for (Project project : dummyProjects) {
projectDao.saveOrUpdate(project);
}
}
@After
public void clearTestData() throws Exception {
projectDao.deleteAll();
}
}
这里是实际的测试 class,有 2 种测试方法:
@ContextConfiguration(locations = {
"classpath*:database/dummy-beans-test.xml",
"classpath*:springcontext/*test.xml"})
public class ProjectDaoTest extends DaoTestBase {
private static final Comparator<Project> PROJECT_COMPARATOR =
new Comparator<Project>() {
public int compare(Project o1, Project o2) {
if (o1.getNumber() < o2.getNumber()) {
return -1;
} else if (o1.getNumber() == o2.getNumber()) {
return 0;
} else {
return 1;
}
};
};
@Test
public void shouldQueryTheSameAsDummyObjects() throws Exception {
List<Project> projects = projectDao.getAll();
Assert.assertEquals(dummyProjects.size(), projects.size());
Set<Project> preProjects = new TreeSet<Project>(PROJECT_COMPARATOR);
preProjects.addAll(dummyProjects);
for (Project project : projects) {
if (preProjects.contains(project)) {
preProjects.remove(project);
}
}
Assert.assertEquals(0, preProjects.size());
}
@Test
public void deleteSomeProjectsShouldRemoveThem() throws Exception {
List<Project> projects = projectDao.getAll();
Assert.assertEquals(3, projects.size());
for (Project toDeleteProject : projects) {
projectDao.delete(toDeleteProject);
List<Project> remainProjects = projectDao.getAll();
for (Project project : remainProjects) {
if (project.getNumber() == toDeleteProject.getNumber()) {
Assert.assertTrue(false);
}
}
}
}
}
问题是当我忽略上面的一种测试方法时,一切都测试正常但是当我测试它们时,它会导致:
Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
检查日志,我看到在执行第一个测试时,Hibernate 执行这些 SQL:
Hibernate:
delete
from
project_table
Hibernate:
insert
into
project_table
(pro_customer, pro_end_date, pro_name, pro_number, pro_start_date, pro_status)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
project_table
(pro_customer, pro_end_date, pro_name, pro_number, pro_start_date, pro_status)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
insert
into
project_table
(pro_customer, pro_end_date, pro_name, pro_number, pro_start_date, pro_status)
values
(?, ?, ?, ?, ?, ?)
Hibernate:
select
this_.id as id0_0_,
this_.pro_customer as pro2_0_0_,
this_.pro_end_date as pro3_0_0_,
this_.pro_name as pro4_0_0_,
this_.pro_number as pro5_0_0_,
this_.pro_start_date as pro6_0_0_,
this_.pro_status as pro7_0_0_
from
project_table this_
Hibernate:
delete
from
project_table
并且在执行第二个测试时,他们会:
Hibernate:
delete
from
project_table
Hibernate:
update
project_table
set
pro_customer=?,
pro_end_date=?,
pro_name=?,
pro_number=?,
pro_start_date=?,
pro_status=?
where
id=?
Hibernate:
update
project_table
set
pro_customer=?,
pro_end_date=?,
pro_name=?,
pro_number=?,
pro_start_date=?,
pro_status=?
where
id=?
Hibernate:
update
project_table
set
pro_customer=?,
pro_end_date=?,
pro_name=?,
pro_number=?,
pro_start_date=?,
pro_status=?
where
id=?
Hibernate:
delete
from
project_table
在每次测试中这些 Hibernate 操作之前和之后,我注意到 Hibernate 打开和关闭会话(并回滚事务)。所以我的问题是:
1/为什么在第二个测试的@Before方法中,Hibernate没有插入数据(他们在第一个方法中插入的数据被回滚了?!)。
2/为什么第二种测试方法我只getAll(使用条件)和Hibernate更新了数据?!
3/ 为什么Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
是这种情况?
在 DAO 的实现中,我从 SessionFactory 检索了当前会话,使用条件进行查询。例如。对于 getAll() 方法:
sessionFactory.getCurrentSession()
.createCriteria(type)
.list()
如果我理解正确,您的项目实体实际上是来自 Spring 上下文的 Spring bean。默认情况下,Spring 上下文在测试之间共享。
第一个测试使用 saveOrUpdate()
将这些实体附加到会话,从而为它们生成一个 ID。然后从数据库中删除它们。
第二个测试然后使用这些完全相同的项目实例作为参数调用 saveOrUpdate()
。由于它们已经有一个 ID,Hibernate 认为它们是分离的项目实例,应该已经存在于数据库中,并使用更新查询来更新它们。但由于它们不再存在于数据库中,因此查询失败。
您不应在测试之间重复使用项目实体实例。在您的 @Before
方法中从头开始创建实例,而不是从 Spring 上下文中获取它们。