JPA (Hibernate 4) + Spring を使用して単純な多対多の関連付けを実装しようとしています。関連付けられた 2 つのテーブルからエンティティを保存するときに結合テーブルが自動的に更新される、helloworld のような例をたくさん見てきました。
ただし、これは私の場合は発生していません-双方向の関連付けとカスケードを設定しても、結合テーブルはem.persist()で更新されません。これがなぜなのかを調べているうちに、私はここ SOでこの回答にたどり着きました。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: 次のカスケード ACTION_PERSIST_SKIPLAZY を処理しています: xxx.logic.db.model.Role aa TRACE org.hibernate.engine.internal.Cascade: コレクションの 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: エンティティ挿入アクションには、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: 次のカスケード ACTION_PERSIST_SKIPLAZY を処理しています: xxx.logic.db.model.Role aa TRACE org.hibernate.engine.internal.Cascade: コレクションの 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: null 非許容の一時的なエンティティ依存関係を持つエンティティ挿入アクションはありません。
このログは、Hibernate が Permissions コレクションを通過していることを示していますが、何らかの理由でそこからのアイテムを無視しています。flush() を呼び出すとここで違いが生じる理由がまったくわかりません...一般に、flush() は、SQL クエリを DB にポストするタイミングを Hibernate に明示的に伝える機能にすぎません。
誰かがこれを説明できますか、少なくとも正しいドキュメントを教えてくれますか?