Конфигурации менеджера транзакций Hibernate в Spring
В моем проекте я использую Hibernate с программной демаркацией транзакций.
Каждый раз в своих методах службы я пишу что-то похожее на это.
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, я получил исключение инициализации Lazy, email - это мой случай, это простая строка. Hibernate даже не выполняет sql-запрос, пока я не назову каких-либо получателей на моем POJO.
1) что я здесь делаю неправильно?
2) Является ли этот подход действительным?
3) Можете ли вы предложить любой проект opensources, который работает на spring/hibernate с настройкой на основе аннотаций?
Обновить
По какой-то причине, если я заменил getCurrentSession на openSession, этот код отлично работает. Может кто-нибудь объяснить это пожалуйста?
Спасибо
Ответы
Ответ 1
Хорошо, наконец, я понял, в чем проблема. В коде выше я использовал загрузку вместо get. Session.load фактически не попал в базу данных. Это причина, по которой я получаю исключение ленивой инициализации вне метода @Transactional
Если я использую openSession вместо getCurrentSession, сеанс открывается за пределами контейнера spring. В результате сеанс не был закрыт, и он позволяет мне читать свойства объекта вне метода @Transactional
Ответ 2
Причина, по которой Hibernate не выполняет SQL-запросы, пока вы не вызываете геттеры, потому что я считаю, что для FetchType установлено значение LAZY. Чтобы исправить эту проблему, вам нужно будет изменить FetchType на EAGER в POJO:
@Entity
@Table(name = "user")
public class User {
/*Other data members*/
@Basic(fetch = FetchType.EAGER)
private String email;
}
Мне лично никогда не приходилось указывать Basic типы, чтобы иметь EACHER 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"/>
Ответ 3
Из документации Hibernate https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch11.html#objectstate-loading:
Имейте в виду, что load() выдает неустранимое исключение, если нет соответствующей строки базы данных. Если класс сопоставляется с прокси-сервером, load() просто возвращает неинициализированный прокси-сервер и фактически не попадает в базу данных до тех пор, пока вы не вызовете метод прокси-сервера. Это полезно, если вы хотите создать связь с объектом, не загружая его из базы данных. Он также позволяет загружать несколько экземпляров в виде пакета, если размер пакета определен для сопоставления классов.
Из вышеприведенной документации в вашем случае bean отображается с прокси-сервером spring, поэтому загрузка просто возвращается. Следовательно, вам нужно использовать get() вместо load(), который всегда попадает в базу данных.