JPA多对多:不清楚为什么连接表在持久化后是空的

时间:2013-07-16 06:53:51

标签: hibernate jpa many-to-many

我正在尝试使用JPA(Hibernate 4)+ Spring实现简单的多对多关联。已经看到了大量类似helloworld的示例,其中当从2个关联表中保存实体时,连接表会自动更新。

然而,在我的情况下并没有发生这种情况 - 即使我设置了双向关联和级联,联接表也没有在 em.persist()上更新。在查看原因时,我来到this answer here at SO,建议使用 em.persist(); em.flush(); 来解决这个问题。我试过 - 奇迹,坚持做得很好!但为什么???

问题:

  1. 我为什么要在这里使用flush()?
  2. 这是在官方JPA / Hibernate文档中提到的吗?
  3. 在处理多对多关联时,我应该在每个persist()/ update()/ remove()之后调用flush()吗?这种方法有哪些可能的缺点 - 性能,副作用?
  4. 以下是相关代码。

    实体类

    
    
        @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: insert into DB_TEST.ROLE (id, version, description, name) values (null, ?, ?, ?)
    
    ...
    
    aa TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl: Starting after statement execution processing [ON_CLOSE]
    aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions: No unresolved entity inserts that depended on [[xxx.logic.db.model.Role#4]]
    aa TRACE org.hibernate.engine.internal.Cascade: Processing cascade ACTION_PERSIST_SKIPLAZY for: xxx.logic.db.model.Role
    aa TRACE org.hibernate.engine.internal.Cascade: Cascade ACTION_PERSIST_SKIPLAZY for collection: xxx.logic.db.model.Role.permissions
    aa TRACE org.hibernate.engine.spi.EJB3CascadingAction: Cascading to persist: xxx.logic.db.model.Permission
    aa TRACE org.hibernate.event.internal.AbstractSaveEventListener: Persistent instance of: xxx.logic.db.model.Permission
    aa TRACE org.hibernate.event.internal.DefaultPersistEventListener: Ignoring persistent instance
    aa TRACE org.hibernate.engine.internal.Cascade: Done cascade ACTION_PERSIST_SKIPLAZY for collection: xxx.logic.db.model.Role.permissions
    aa TRACE org.hibernate.engine.internal.Cascade: Done processing cascade ACTION_PERSIST_SKIPLAZY for: xxx.logic.db.model.Role
    aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions: No entity insert actions have non-nullable, transient entity dependencies.aa TRACE org.hibernate.engine.jdbc.internal.LogicalConnectionImpl: Starting after statement execution processing [ON_CLOSE]
    aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions: No unresolved entity inserts that depended on [[xxx.logic.db.model.Role#4]]
    aa TRACE org.hibernate.engine.internal.Cascade: Processing cascade ACTION_PERSIST_SKIPLAZY for: xxx.logic.db.model.Role
    aa TRACE org.hibernate.engine.internal.Cascade: Cascade ACTION_PERSIST_SKIPLAZY for collection: xxx.logic.db.model.Role.permissions
    aa TRACE org.hibernate.engine.spi.EJB3CascadingAction: Cascading to persist: xxx.logic.db.model.Permission
    aa TRACE org.hibernate.event.internal.AbstractSaveEventListener: Persistent instance of: xxx.logic.db.model.Permission
    aa TRACE org.hibernate.event.internal.DefaultPersistEventListener: Ignoring persistent instance
    aa TRACE org.hibernate.engine.internal.Cascade: Done cascade ACTION_PERSIST_SKIPLAZY for collection: xxx.logic.db.model.Role.permissions
    aa TRACE org.hibernate.engine.internal.Cascade: Done processing cascade ACTION_PERSIST_SKIPLAZY for: xxx.logic.db.model.Role
    aa TRACE org.hibernate.action.internal.UnresolvedEntityInsertActions: No entity insert actions have non-nullable, transient entity dependencies.
    
    

    这个日志说Hibernate正在通过Permissions集合,但由于某种原因忽略了那里的项目。我完全不明白为什么调用flush()会在这里产生任何不同......通常,flush()只是一种明确告诉Hibernate何时将SQL查询发布到DB的能力。

    任何人都可以解释这个或者至少指出我正确的文档吗?

1 个答案:

答案 0 :(得分:1)

Hibernate延迟SQL语句的执行,直到绝对必要。这可以避免在事务最终回滚时执行不必要的语句,从而节省时间。

最后,当提交事务时,Hibernate会刷新挂起的修改和提交。但是,由于您已将测试配置为回滚而未提交事务,因此此自动刷新永远不会发生,并且您需要显式调用flush()以使其执行插入。