私のプロジェクトでは、プログラムによるトランザクション境界でHibernateを使用しています。私のServiceメソッドでは、毎回これに似たものを書きます。
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
.. perform operation here
session.getTransaction().commit();
次に、宣言型トランザクション管理を使用してコードをリファクタリングします。私が今得たもの...
<context:component-scan base-package="package.*"/>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<property name="configurationClass">
<value>org.hibernate.cfg.AnnotationConfiguration</value>
</property>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
</bean>
サービスクラス:
@Service
public class TransactionalService {
@Autowired
private SessionFactory factory;
@Transactional
public User performSimpleOperation() {
return (User)factory.getCurrentSession().load(User.class, 1L);
}
}
そして簡単なテスト-
@Test
public void useDeclarativeDem() {
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("spring-config.xml");
TransactionalService b = (TransactionalService)ctx.getBean("transactionalService");
User op = b.performSimpleOperation();
op.getEmail();
Transactionalメソッドの外部でユーザーの電子メールを取得しようとすると、レイジー初期化例外が発生しました。電子メールは私の場合は単純な文字列です。 POJOでゲッターを呼び出すまで、HibernateはSQLクエリを実行しません。
1)私がここで間違っていることは何ですか?
2)このアプローチは有効ですか?
3)アノテーションベースの構成でSpring/Hibernateで動作するオープンソースプロジェクトを提案できますか?
更新
何らかの理由で、getCurrentSessionをopenSessionに置き換えると、このコードは正常に機能します。誰かがそれを説明できますか?
ありがとうございました
さて、ついに私は何が問題なのかを理解しました。上記のコードでは、getの代わりにloadを使用しました。 Session.loadは実際にはデータベースにヒットしませんでした。これが、@ Transactionalメソッドの外部で遅延初期化例外が発生する理由です。
GetCurrentSessionの代わりにopenSessionを使用すると、セッションはスコープのSpringコンテナの外で開かれます。その結果、セッションが閉じられず、@ Transactionalメソッドの外部でオブジェクトのプロパティを読み取ることができました
ゲッターを呼び出すまでHibernateがSQLクエリを実行しない理由は、FetchTypeがLAZYに設定されていると信じているためです。この問題を修正するには、POJOでFetchTypeをEAGERに変更する必要があります。
@Entity
@Table(name = "user")
public class User {
/*Other data members*/
@Basic(fetch = FetchType.EAGER)
private String email;
}
個人的には、EAGER FetchTypeを持つために基本タイプを指定する必要がなかったので、構成でこれが必要な理由が完全にはわかりません。テストでのみ発生する場合は、JUnitテストの構成方法が原因である可能性があります。クラス宣言には次のようなものが必要です。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/test-app-config.xml"})
@Transactional
public class UserServiceTest {
}
良いリソースに関しては、私はいつも SpringByExample が役立つと思います。
[〜#〜]編集[〜#〜]
したがって、構成の何が問題になっているのか完全にはわかりません。それは私がセットアップした方法とは異なるので、これが役立つことを期待して私の典型的な構成です。 hibernate.transaction.factory_class
は、不足している重要なプロパティである可能性があります。 AnnotationSessionFactoryBean
も使用します:
<!-- DataSource -->
<bean id="dataSource" class="org.Apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost/dbname"
p:username="root"
p:password="password"/>
<!-- Hibernate session factory -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
p:dataSource-ref="dataSource">
<property name="packagesToScan" value="com.beans"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</prop>
</props>
</property>
</bean>
<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
Hibernateドキュメントから https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch11.html#objectstate-loading :
一致するデータベース行がない場合、load()は回復不能な例外をスローすることに注意してください。クラスがプロキシでマップされている場合、load()は初期化されていないプロキシを返すだけで、プロキシのメソッドを呼び出すまで実際にはデータベースにアクセスしません。これは、データベースから実際にロードせずにオブジェクトへの関連付けを作成する場合に役立ちます。また、クラスマッピングにバッチサイズが定義されている場合は、複数のインスタンスをバッチとしてロードできます。
上記のドキュメントから、BeanはSpringプロキシにマッピングされているため、loadが返されます。したがって、常にデータベースにアクセスするload()の代わりにget()を使用する必要があります。