我正在尝试使用 JPA (Hibernate 4) + Spring 实现简单的多对多关联。已经看到了大量类似 helloworld 的示例,其中连接表在从 2 个关联表中保存实体时会自动更新。
然而,这并没有发生在我的情况下——即使我设置了双向关联和级联,连接表也不会在em.persist()上得到更新。在寻找为什么会这样的同时,我在 SO 找到了这个答案,建议使用em.persist(); em.flush(); 来解决这个问题。我试过 - 奇迹,坚持工作正常!但为什么???
问题:
- 为什么我应该在这里使用flush()?
- 这是在官方 JPA/Hibernate 文档中的某处提到的吗?
- 在处理多对多关联时,我应该在每个 persist()/update()/remove() 之后调用 flush() 吗?这种方法可能有哪些缺点——性能、副作用?
这是相关代码。
实体类
@Entity
@Table(name="ROLE")
public class Role extends EntityBase implements Comparable
{
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "ROLE_PERMISSION",
joinColumns = @JoinColumn(name="role_id", referencedColumnName="id"),
inverseJoinColumns = @JoinColumn(name="permission_id", referencedColumnName="id"))
private Set permissions = new HashSet();
//... other code (getters/setters/extra columns) is omitted ...
}
@Entity
@Table(name="PERMISSION")
public class Permission extends EntityBase
{
@ManyToMany(mappedBy = "permissions", fetch = FetchType.LAZY)
private Set roles = new HashSet();
//... other code (getters/setters/extra columns) is omitted ...
}
通用 DAO 实现(由我的具体 DAO 使用):
@Repository
@Transactional(value="transactionManager")
public abstract class GenericDaoImpl implements GenericDao
{
@PersistenceContext(unitName = "entityManagerFactory")
protected EntityManager em;
public T create( final T t )
{
em.persist(t);
// em.flush(); - if I put this here, all works well (and fails if I'm not)
return t;
}
// ... other code is omitted ...
}
我正在尝试测试的服务层方法:
@Component("securityService")
public class SecurityServiceImpl implements SecurityService
{
// ... other code is omitted ...
@Transactional(value="transactionManager",
rollbackFor = Exception.class, readOnly = false)
public void createRole( Role role )
{
Validate.notNull( role, "Role should not be null" );
roleDao.create( role );
}
}
最后是我的 TestNG 集成测试(使用内存 H2 DB):
@ContextConfiguration(
locations={"/META-INF/beans-test.xml"})
@TransactionConfiguration(
transactionManager = "transactionManager", defaultRollback = true)
public class SecurityServiceImplIT
extends AbstractTransactionalTestNGSpringContextTests
{
@Autowired
@Qualifier("securityService")
private SecurityService securityService;
@Test
@Transactional(value = "transactionManager")
public void createRole_createRoleWithPermissions()
{
// Add test data to DB.
super.executeSqlScript( TESTDATA_PATH, false);
// Remove all associations between permissions and roles, need
// clear intermediate table for this test case.
super.simpleJdbcTemplate.update(
"delete from DB_TEST.ROLE_PERMISSION;" );
super.simpleJdbcTemplate.update(
"delete from DB_TEST.ROLE;" );
final Role role = new Role();
role.setName( "Test role" );
role.addPermission( securityService.getAllPermissions().get(0) );
final int expectedPermissionCount = 1;
securityService.createRole(role);
// This is always passed
Assert.assertEquals( super.countRowsInTable( "DB_TEST.ROLE" ), 1,
"New role should be added, so table should contain 1 row" );
// This is failed if I'm not using flush() in my DAO.
Assert.assertEquals( super.countRowsInTable( "DB_TEST.ROLE_PERMISSION" ),
expectedPermissionCount, "Role-permission associations should be added" );
}
// ... other code is omitted ...
}
Hibernate 调试日志(DAO 中没有 flush() 调用):
Hibernate: 插入 DB_TEST.ROLE (id, version, description, name) 值 (null, ?, ?, ?) ... aa TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl:在语句执行处理后启动 [ON_CLOSE] aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions:没有依赖于 [[xxx.logic.db.model.Role#4]] 的未解析实体插入 aa TRACE org.hibernate.engine.internal.Cascade:为 xxx.logic.db.model.Role 处理级联 ACTION_PERSIST_SKIPLAZY aa TRACE org.hibernate.engine.internal.Cascade:用于集合的级联 ACTION_PERSIST_SKIPLAZY:xxx.logic.db.model.Role.permissions aa TRACE org.hibernate.engine.spi.EJB3CascadingAction:级联以保持:xxx.logic.db.model.Permission aa TRACE org.hibernate.event.internal.AbstractSaveEventListener:持久实例:xxx.logic.db.model.Permission aa TRACE org.hibernate.event.internal.DefaultPersistEventListener:忽略持久实例 aa TRACE org.hibernate.engine.internal.Cascade:为集合完成级联 ACTION_PERSIST_SKIPLAZY:xxx.logic.db.model.Role.permissions aa TRACE org.hibernate.engine.internal.Cascade:完成处理级联 ACTION_PERSIST_SKIPLAZY 为:xxx.logic.db.model.Role aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions:没有实体插入操作具有不可为空的瞬态实体依赖项。aa TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl:在语句执行处理后开始 [ON_CLOSE] aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions:没有依赖于 [[xxx.logic.db.model.Role#4]] 的未解析实体插入 aa TRACE org.hibernate.engine.internal.Cascade:为 xxx.logic.db.model.Role 处理级联 ACTION_PERSIST_SKIPLAZY aa TRACE org.hibernate.engine.internal.Cascade:用于集合的级联 ACTION_PERSIST_SKIPLAZY:xxx.logic.db.model.Role.permissions aa TRACE org.hibernate.engine.spi.EJB3CascadingAction:级联以保持:xxx.logic.db.model.Permission aa TRACE org.hibernate.event.internal.AbstractSaveEventListener:持久实例:xxx.logic.db.model.Permission aa TRACE org.hibernate.event.internal.DefaultPersistEventListener:忽略持久实例 aa TRACE org.hibernate.engine.internal.Cascade:为集合完成级联 ACTION_PERSIST_SKIPLAZY:xxx.logic.db.model.Role.permissions aa TRACE org.hibernate.engine.internal.Cascade:完成处理级联 ACTION_PERSIST_SKIPLAZY 为:xxx.logic.db.model.Role aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions:没有实体插入操作具有不可为空的瞬态实体依赖关系。
该日志说 Hibernate 正在通过 Permissions 集合,但由于某种原因忽略了那里的项目。我完全不明白为什么在这里调用 flush() 会产生任何影响......通常,flush() 只是一种明确告诉 Hibernate 何时将 SQL 查询发布到 DB 的能力。
任何人都可以解释这一点,或者至少指出我正确的文档吗?