2014-12-04 2 views
3

Примечание: См. Мой собственный ответ на этот вопрос для примера того, как я решил эту проблему.Spring MVC + Hibernate: невозможно инициализировать прокси-сервер - нет Сессия

Я получаю следующее исключение в моей Spring MVC 4 + Hibernate 4 проекта:

org.hibernate.LazyInitializationException: не удалось инициализировать лениво коллекцию роли: com.mysite.Company.acknowledgements, не может инициализировать прокси - не сессии

После прочтения много других вопросов по поводу этого вопроса, я понимаю, почему это происходит исключение, но я не уверен, как это исправить в хорошем смысле. Я делаю следующее:

  1. My Spring MVC контроллер вызывает метод в службе
  2. Метод обслуживания вызывает метод в классе DAO
  3. метод в классе DAO выбирает объект сущности через Hibernate и возвращает его на службу вызова
  4. служба возвращает сгружен объект контроллера
  5. контроллер передает объект в виде (JSP)
  6. мнение пытается перебирать на многие-ко-многим Asso рирует, который лениво загружен (и, таким образом, прокси-объект)
  7. Исключение выбрасывается, так как сеанс закрыт в этой точке, и прокси-сервер не может загрузить связанные данные

я ранее работал с PHP и доктрины2, и этот способ делать вещи не вызывал проблем. Я пытаюсь выяснить, лучший способ решить эту проблему, потому что решения, которые я нашел до сих пор, кажется, не так велики:

  • нагрузки ассоциация жадностью, потенциально погрузочные много ненужных данных
  • Вызов Hibernate.initialize(myObject.getAssociation()); - это означает, что мне нужно перебирать ассоциации, чтобы их инициализировать (я думаю), и только тот факт, что я должен это делать, вроде делает ленивую загрузку менее аккуратной.
  • Использование фильтра Spring, чтобы оставить сеанс открытым в взгляды, но я сомневаюсь, что это хорошо делать?

Я пытался использовать @Transactional в своей службе, но без везения. Это имеет смысл, потому что я пытаюсь получить доступ к данным, которые еще не загружены после мой метод обслуживания возвращается. В идеале я хотел бы иметь доступ к любой ассоциации из моего представления. Я предполагаю, что недостатком инициализации ассоциаций в моей службе является то, что я должен явно определять, какие данные мне нужны, но это зависит от контекста (контроллера), в котором используется служба. Я не уверен, могу ли я сделать это в своем контроллере, не теряя абстракции, которую предоставляет уровень DBAL. Я надеюсь, что в этом есть смысл. В любом случае, было бы здорово, если бы мне не приходилось всегда явно определять, какие данные я хочу быть доступными для своего представления, но просто позвольте этому представлению делать что-то. Если это невозможно, я просто ищу наиболее элегантное решение проблемы.

Ниже приведен мой код.

Посмотреть

<%@ page contentType="text/html;charset=UTF-8" language="java" %> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 

<h1><c:out value="${company.name}" /> (ID: <c:out value="${company.id}" />)</h1> 

<c:forEach var="acknowledgement" items="${company.acknowledgements}"> 
    <p><c:out value="${acknowledgement.name}" /></p> 
</c:forEach> 

Контроллер

@Controller 
public class ProfileController { 
    @Autowired 
    private CompanyService companyService; 

    @RequestMapping("/profile/view/{id}") 
    public String view(Model model, @PathVariable int id) { 
     Company company = this.companyService.get(id); 
     model.addAttribute("company", company); 

     return "viewCompanyProfile"; 
    } 
} 

Сервис

@Service 
public class CompanyServiceImpl implements CompanyService { 
    @Autowired 
    private CompanyDao companyDao; 

    @Override 
    public Company get(int id) { 
     return this.companyDao.get(id); 
    } 
} 

DAO

@Repository 
@Transactional 
public class CompanyDaoImpl implements CompanyDao { 
    @Autowired 
    private SessionFactory sessionFactory; 

    @Override 
    public Company get(int id) { 
     return (Company) this.sessionFactory.getCurrentSession().get(Company.class, id); 
    } 
} 

предприятие компании

@Entity 
@Table(name = "company") 
public class Company { 
    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    private int id; 

    // Other fields here 

    @ManyToMany 
    @JoinTable(name = "company_acknowledgement", joinColumns = @JoinColumn(name = "company_id"), inverseJoinColumns = @JoinColumn(name = "acknowledgement_id")) 
    private Set<Acknowledgement> acknowledgements; 

    public Set<Acknowledgement> getAcknowledgements() { 
     return acknowledgements; 
    } 

    public void setAcknowledgements(Set<Acknowledgement> acknowledgements) { 
     this.acknowledgements = acknowledgements; 
    } 

    // Other getters and setters here 
} 

Квитирование объект

@Entity 
public class Acknowledgement { 
    @Id 
    private int id; 

    // Other fields + getters and setters here 

} 

MVC-диспетчерская-servlet.xml (часть его)

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> 
     <property name="dataSource" ref="dataSource" /> 
     <property name="packagesToScan"> 
      <list> 
       <value>com.mysite.company.entity</value> 
      </list> 
     </property> 
     <property name="hibernateProperties"> 
      <props> 
       <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop> 
      </props> 
     </property> 
    </bean> 

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
     <property name="dataSource" ref="dataSource" /> 
    </bean> 

    <tx:annotation-driven /> 

Заранее благодарен!

ответ

2

Простейшим и наиболее прозрачным решением является шаблон OSIV. Как я уверен, вы знаете, есть много обсуждения об этом (анти) шаблоне и альтернативах как на этом сайте, так и в другом месте, поэтому больше не нужно переходить к этому. Например:?

Why is Hibernate Open Session in View considered a bad practice?

Однако, на мой взгляд, не все критические замечания OSIV совершенно точно (например, сделка не будет совершать до вашей точки зрения оказывается действительно Если вы используете реализацию Spring затем https://stackoverflow.com/a/10664815/1356423)

Кроме того, имейте в виду, что в JPA 2.1 введена концепция Fetch Graphs, которая дает вам больше контроля над загружаемым. Я еще не пробовал, но, возможно, это, наконец, решение этой долговременной проблемы!

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

+0

Благодарим вас за ответ. Я опробовал подход графа объектов, и для этого это приятное и чистое решение. Он решил проблему, и я напишу ответ с моим решением всем, кто заинтересован. Тем не менее, это похоже на то, что реализация графа сущностей [не является достаточно гибкой (пока)] (http://stackoverflow.com/questions/27360966/using-jpa-entity-graph-with-complex-conditions). Но это решило проблему! – Andy0708

1

Мой голос идет на Hibernate.initialize(myObject.getAssociation()) в уровне услуг (что также означает @Transactional должны быть перемещены из DAO к методам обслуживания)

ИМХО, методы обслуживания должны вернуть все данные, необходимые для вызывающей стороны. Если вызывающий абонент хочет отобразить некоторую ассоциацию в представлении, это обязанность службы предоставлять эти данные. Это означает, что у вас может быть несколько методов, которые, очевидно, делают одно и то же (экземпляр Company), но в зависимости от вызывающего могут быть получены различные ассоциации.

В одном проекте у нас был класс конфигурации, в котором содержалась информация о том, какие ассоциации должны быть получены, и у нас был единственный метод службы, который также принял этот класс в качестве параметра. Этот подход означает, что у нас есть только один метод, который достаточно гибкий, чтобы поддерживать всех абонентов.

@Override 
public Company get(int id, FetchConfig fc) { 
    Company result = this.companyDao.get(id); 
    if (fc.isFetchAssociation1()) { 
     Hibernate.initialize(result.getAssociation1()); 
    } 
    ... 
    return result; 
} 
+0

Я думал, что должен быть способ предотвратить это, но ваше решение имеет смысл - и я согласен с тем, что методы службы должны возвращать данные, требуемые вызывающим. Но что, если у меня есть «вложенные ассоциации», например. если 'company.someAssociation' также имеет ассоциацию, которую я хотел бы получить? Если мне нужно вызвать 'Hibernate.initialize()' во всех ассоциациях, которые я хотел бы получить, не буду ли я заканчивать делать много циклов с помощью объектов данных в своих сервисах, просто чтобы убедиться, что все данные, которые я хочу получить? По-моему, это похоже на что-то, что ORM должно сделать для меня. – Andy0708

+0

Да, вы правы в этом, с вложенными убийствами, это может стать громоздким и уродливым кодом. Но, по моему опыту, вам редко нужны ассоциации с более чем второго уровня глубины в одной службе. Так что код может быть умеренно уродливым, а не слишком большим. Альтернативный подход, как вы сказали, заявил, чтобы связать сеанс для всего запроса, который не звучит правильно. Правда, вероятно, где-то посередине. –

+0

Почему вы предпочитаете это решение через OSIV? Вы пишете больше кода, чтобы иметь одну и ту же проблему выбора N + 1 (что является основным аргументом против OSIV). –

10

Как @Predrag Maric указанных способы написания услуг с различной инициализацией для assotiations сущностей могут быть вариантом.

OpenSessionInView - это обсуждаемый шаблон, который может быть решением в вашем случае.

Другой вариант установить

<prop key="hibernate.enable_lazy_load_no_trans">true</prop>

недвижимость. Он предназначен для решения проблемы org.hibernate.LazyInitializationException и доступен с момента спящего режима 4.1.6.

А что это за свойство? Он просто сигнализирует Hibernate, что должен открыть новый сеанс, если сеанс, который установлен внутри текущего , не инициализированный прокси закрыт. Вы должны знать, что если у вас есть любой другой открытый сеанс, который также используется для управления текущей транзакцией , этот недавно открытый сеанс будет другим, и он может не участвовать в текущей транзакции, если это не JTA. Потому что этого TX побочного эффекта вы должны быть осторожны с возможными побочными эффектами в вашей системе.

Найти больше информации здесь: http://blog.harezmi.com.tr/hibernates-new-feature-for-overcoming-frustrating-lazyinitializationexceptions и Solve Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans

+0

Спасибо, не знал об этой функции. –

+0

О, я раньше не видел никакой информации об этом. Благодаря! – Andy0708

+0

Большое спасибо, отлично работает! –

1

Для других, которые ищут решения, вот как я решил проблему.

Как указал Алан Хэй, JPA 2.1+ поддерживает диаграммы сущностей, которые в конечном итоге решают мою проблему. Чтобы использовать его, я меняю свой проект на использование класса javax.persistence.EntityManager вместо SessionFactory, который затем передаст мне текущий сеанс. Вот как я настроил его в моем диспетчерский конфигурации сервлета (некоторые вещи исключены):

<beans xmlns="http://www.springframework.org/schema/beans" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:context="http://www.springframework.org/schema/context" 
     xmlns:mvc="http://www.springframework.org/schema/mvc" 
     xmlns:jee="http://www.springframework.org/schema/jee" 
     xmlns:tx="http://www.springframework.org/schema/tx" 
     xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd 
     http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans.xsd 
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 
     http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd 
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> 

    <jee:jndi-lookup id="dataSource" jndi-name="java:/comp/env/jdbc/postgres" expected-type="javax.sql.DataSource"/> 

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
     <property name="dataSource" ref="dataSource" /> 
     <property name="packagesToScan" value="dk.better.company.entity, dk.better.user.entity" /> 
     <property name="jpaVendorAdapter"> 
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> 
     </property> 
     <property name="jpaProperties"> 
      <props> 
       <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQL9Dialect</prop> 
       <prop key="hibernate.show_sql">true</prop> 
      </props> 
     </property> 
    </bean> 

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
     <property name="entityManagerFactory" ref="entityManagerFactory" /> 
    </bean> 

    <tx:annotation-driven /> 
</beans> 

Ниже приведен пример класса DAO.

@Transactional 
public class CompanyDaoImpl implements CompanyDao { 
    @PersistenceContext 
    private EntityManager entityManager; 

    @Override 
    public Company get(int id) { 
     EntityGraph<Company> entityGraph = this.entityManager.createEntityGraph(Company.class); 
     entityGraph.addAttributeNodes("acknowledgements"); 

     Map<String, Object> hints = new HashMap<String, Object>(); 
     hints.put("javax.persistence.loadgraph", entityGraph); 

     return this.entityManager.find(Company.class, id, hints); 
    } 
} 

Я обнаружил, что если я не использовал @PersistenceContext но Autowired вместо этого, то сеанс базы данных не был закрыт при отображении просмотра и обновление данных не было отражено в последующих запросах.

Для получения дополнительной информации о графах сущностей, я рекомендую вам прочитать следующие статьи:

http://www.thoughts-on-java.org/2014/03/jpa-21-entity-graph-part-1-named-entity.html

http://www.thoughts-on-java.org/2014/04/jpa-21-entity-graph-part-2-define.html

0

Trigger в слое службы требует отложенной загрузки метода размера Комплекса.

Инструменты:

public class HibernateUtil { 
    /** 
    * Lazy = true when the trigger size method is equal to lazy = false (load all attached) 
    */ 
    public static void triggerSize(Collection collection) { 
     if (collection != null) { 
      collection.size(); 
     } 
    } 
} 

в методе обслуживания:

Apple apple = appleDao.findById('xxx'); 
HibernateUtil.triggerSize(apple.getColorSet()); 
return apple; 

затем использовать apple в контроллер, все в порядке!

Смежные вопросы