Поскольку ответ прокручивает вниз в конец этого...
Основная проблема совпадает с, спросили несколько время. У меня есть простая программа с двумя Событиями POJOs и Пользователем - где у пользователя может быть несколько событий.
@Entity
@Table
public class Event {
private Long id;
private String name;
private User user;
@Column
@Id
@GeneratedValue
public Long getId() {return id;}
public void setId(Long id) { this.id = id; }
@Column
public String getName() {return name;}
public void setName(String name) {this.name = name;}
@ManyToOne
@JoinColumn(name="user_id")
public User getUser() {return user;}
public void setUser(User user) {this.user = user;}
}
Пользователь:
@Entity
@Table
public class User {
private Long id;
private String name;
private List events;
@Column
@Id
@GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
@Column
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@OneToMany(mappedBy="user", fetch=FetchType.LAZY)
public List getEvents() { return events; }
public void setEvents(List events) { this.events = events; }
}
Примечание: Это - демонстрационный проект. Я действительно хочу использовать Ленивую выборку здесь.
Теперь мы должны настроить пружину и быть в спящем режиме и иметь простой basic-db.xml для загрузки:
data.model.User
data.model.Event
org.hibernate.dialect.MySQLDialect
true
create
Примечание: Я играл вокруг с CustomScopeConfigurer и SimpleThreadScope, но это ничего не изменило.
У меня есть простой дао-impl (только вставляющий userDao - EventDao является в значительной степени тем же - кроме с функцией "listWith":
public class UserDaoImpl implements UserDao{
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
@SuppressWarnings("unchecked")
@Override
public List listUser() {
return hibernateTemplate.find("from User");
}
@Override
public void saveUser(User user) {
hibernateTemplate.saveOrUpdate(user);
}
@Override
public List listUserWithEvent() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
return users;
}
}
Я получаю org.hibernate. LazyInitializationException - не удалось лениво инициализировать набор роли: data.model. User.events, никакая сессия или сессия были закрыты в строке с user.getEvents () .size ();
И наконец, что не менее важно, вот Тестовый класс, который я использую:
public class HibernateTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
System.out.println("New user...");
User user = new User();
user.setName("test");
Event event1 = new Event();
event1.setName("Birthday1");
event1.setUser(user);
Event event2 = new Event();
event2.setName("Birthday2");
event2.setUser(user);
udao.saveUser(user);
edao.saveEvent(event1);
edao.saveEvent(event2);
List users = udao.listUserWithEvent();
System.out.println("Events for users");
for (User u : users) {
System.out.println(u.getId() + ":" + u.getName() + " --");
for (Event e : u.getEvents())
{
System.out.println("\t" + e.getId() + ":" + e.getName());
}
}
((ConfigurableApplicationContext)ac).close();
}
}
и вот Исключение:
1621 [main] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372) at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119) at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248) at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38) at HibernateTest.main(HibernateTest.java:44) Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372) at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119) at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248) at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38) at HibernateTest.main(HibernateTest.java:44)
Вещи попробовали, но не работали:
// scope stuff Scope threadScope = new SimpleThreadScope(); ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory(); beanFactory.registerScope("request", threadScope); ac.refresh(); ...
... Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction(); tx.begin(); users = udao.listUserWithEvent(); ...
public List listUserWithEvent() { SessionFactory sf = hibernateTemplate.getSessionFactory(); Session s = sf.openSession(); Transaction tx = s.beginTransaction(); tx.begin(); List users = hibernateTemplate.find("from User"); for (User user : users) { System.out.println("LIST : " + user.getName() + ":"); user.getEvents().size(); } tx.commit(); return users; }
Я действительно вне идей к настоящему времени. Кроме того, использование listUser или listEvent просто хорошо работает.
Шаг вперед:
Благодаря Thierry я получил один шаг вперед (я думаю). Я создал класс MyTransaction, и сделайте мою целую работу там, получив все с пружины. Новые основные взгляды как это:
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
// getting dao
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
// gettting transaction template
TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate");
MyTransaction mt = new MyTransaction(udao, edao);
transactionTemplate.execute(mt);
((ConfigurableApplicationContext)ac).close();
}
К сожалению, теперь существует исключение нулевого указателя: user.getEvents () .size (); (в daoImpl).
Я знаю, что это не должно быть пустым (ни от вывода в консоли, ни от расположения дб).
Вот консольный вывод для получения дополнительной информации (я сделал проверку на user.getEvent () == пустой указатель и распечатал "СОБЫТИЕ, является ПУСТЫМ"):
New user... Hibernate: insert into User (name) values (?) Hibernate: insert into User (name) values (?) Hibernate: insert into Event (name, user_id) values (?, ?) Hibernate: insert into Event (name, user_id) values (?, ?) Hibernate: insert into Event (name, user_id) values (?, ?) List users: Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_ 1:User1 2:User2 List events: Hibernate: select event0_.id as id1_, event0_.name as name1_, event0_.user_id as user3_1_ from Event event0_ 1:Birthday1 for 1:User1 2:Birthday2 for 1:User1 3:Wedding for 2:User2 Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_ Events for users 1:User1 -- EVENT is NULL 2:User2 -- EVENT is NULL
Можно получить демонстрационный проект из http://www.gargan.org/code/hibernate-test1.tgz (это - проект затмения/знатока),
Решение (для консольных приложений)
Существует на самом деле два решения для этой проблемы - в зависимости от Вашей среды:
Для консольного приложения Вам нужен шаблон транзакции, который получает actutal логику дб и заботится о транзакции:
public class UserGetTransaction implements TransactionCallback{
public List users;
protected ApplicationContext context;
public UserGetTransaction (ApplicationContext context) {
this.context = context;
}
@Override
public Boolean doInTransaction(TransactionStatus arg0) {
UserDao udao = (UserDao) ac.getBean("myUserDAO");
users = udao.listUserWithEvent();
return null;
}
}
Можно использовать это путем вызова:
TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
UserGetTransaction mt = new UserGetTransaction(context);
transactionTemplate.execute(mt);
Для этого для работы необходимо определить шаблонный класс в течение пружины (т.е. в basic-db.xml):
Другое (возможное) решение
спасибо andi
PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager");
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);
transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
boolean success = false;
try {
new UserDataAccessCode().execute();
success = true;
} finally {
if (success) {
transactionManager.commit(status);
} else {
transactionManager.rollback(status);
}
}
Решение (для сервлетов)
Сервлеты не являются настолько большими из проблемы. Когда у Вас есть сервлет, можно просто запустить и связать транзакцию в начале функции и развязать ее снова в конце:
public void doGet(...) {
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
// Your code....
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
Я думаю, вам не следует использовать транзакционные методы сессий hibernate, пусть это делает spring.
Добавьте это в ваш spring conf:
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
и затем я бы изменил ваш тестовый метод, чтобы использовать шаблон транзакций spring:
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
// do your hibernate stuff in here : call save, list method, etc
}
}
}
как побочное замечание, ассоциации @OneToMany ленивы по умолчанию, поэтому вам не нужно аннотировать их ленивыми. (@*ToMany по умолчанию LAZY, @*ToOne по умолчанию EAGER)
EDIT: вот что происходит с точки зрения hibernate:
. ... то же самое со всеми операциями сохранения...
затем загружаем всех пользователей (запрос "from Users")
Вот некоторые моменты для улучшения вашего кода:
Поэтому, чтобы решить вышеуказанный вопрос, либо сохраняйте в одной транзакции, а загружайте в другой:
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
// save here
}
}
transactionTemplate.execute(new TransactionCallback() {
@Override
public Object doInTransaction(TransactionStatus status) {
// list here
}
}
}
либо устанавливайте обе стороны:
...
event1.setUser(user);
...
event2.setUser(user);
...
user.setEvents(Arrays.asList(event1,event2));
...
(Также не забудьте рассмотреть пункты улучшения кода выше, Set not List, типизация коллекций)
.Проблема в том, что ваш дао использует один сеанс гибернации но ленивая загрузка user.getName (я предполагаю, что это то место, где он выбрасывает) происходит вне этого сеанса - либо не в сеансе вообще, либо в другом сеансе. Обычно мы открываем сеанс гибернации до того, как делаем вызовы DAO, и не закрываем его, пока не закончим со всеми отложенными загрузками. Веб-запросы обычно заключаются в большой сеанс, поэтому этих проблем не возникает.
Обычно мы упаковываем наши dao и ленивые вызовы в SessionWrapper. Примерно так:
public class SessionWrapper {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public <T> T runLogic(Callable<T> logic) throws Exception {
Session session = null;
// if the session factory is already registered, don't do it again
if (TransactionSynchronizationManager.getResource(sessionFactory) == null) {
session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
try {
return logic.call();
} finally {
// if we didn't create the session don't unregister/release it
if (session != null) {
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.releaseSession(session, sessionFactory);
}
}
}
}
Очевидно, что SessionFactory - это та же самая SessionFactory, которая была введена в ваш дао.
В вашем случае вы должны заключить в эту логику все тело listUserWithEvent. Примерно так:
public List listUserWithEvent() {
return sessionWrapper.runLogic(new Callable<List>() {
public List call() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
}
});
}
Вам нужно будет внедрить экземпляр SessionWrapper в ваш daos.
Я пришел сюда за подсказкой относительно похожей проблемы. Я попробовал решение, упомянутое Тьерри, и оно не сработало. После этого я попробовал эти строки, и это сработало:
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
На самом деле то, что я делаю, - это пакетный процесс, который должен использовать существующие менеджеры / сервисы Spring. После загрузки контекста и выполнения нескольких вызовов я обнаружил знаменитую проблему «не удалось лениво инициализировать коллекцию». Эти 3 строчки решили это за меня.