Архитектура, чтобы избежать Hibernate LazyInitializationExceptions - PullRequest
3 голосов
/ 29 мая 2011

Я в начале своего проекта.Поэтому я пытаюсь разработать архитектуру, которая исключает Hibernate LazyInitializationException.Пока что мое applicationContext.xml имеет:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation">
        <value>/WEB-INF/hibernate.cfg.xml</value>
    </property>
    <property name="configurationClass">
        <value>org.hibernate.cfg.AnnotationConfiguration</value>
    </property>        
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>        
            <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>     
        </props>
    </property>
    <property name="eventListeners">
        <map>
            <entry key="merge">
                <bean class="org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener"/>
            </entry>
        </map>
    </property>
</bean>

<bean id="dao" class="info.ems.hibernate.HibernateEMSDao" init-method="createSchema">
    <property name="hibernateTemplate">
        <bean class="org.springframework.orm.hibernate3.HibernateTemplate">
            <property name="sessionFactory" ref="sessionFactory"/>
            <property name="flushMode">
                <bean id="org.springframework.orm.hibernate3.HibernateAccessor.FLUSH_COMMIT" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/>                    
            </property>
        </bean>
    </property>        
    <property name="schemaHelper">
        <bean class="info.ems.hibernate.SchemaHelper">                                
            <property name="driverClassName" value="${database.driver}"/>
            <property name="url" value="${database.url}"/>
            <property name="username" value="${database.username}"/>
            <property name="password" value="${database.password}"/>
            <property name="hibernateDialect" value="${hibernate.dialect}"/>   
            <property name="dataSourceJndiName" value="${database.datasource.jndiname}"/>
        </bean>                
    </property>
</bean>       

hibernate.cfg.xml:

<hibernate-configuration>
    <session-factory>       
        <mapping class="info.ems.models.User" />
        <mapping class="info.ems.models.Role" />
    </session-factory>
</hibernate-configuration>

The Role.java:

@Entity
@Table(name="ROLE")
@Access(AccessType.FIELD)
public class Role implements Serializable {

    private static final long serialVersionUID = 3L;

    @Id
    @Column(name="ROLE_ID", updatable=false, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private long id;

    @Column(name="USERNAME")
    private String username;

    @Column(name="ROLE")
    private String role;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

И пользователь.java:

@Entity
@Table(name = "USER")
@Access(AccessType.FIELD)
public class User implements UserDetails, Serializable {

    private static final long serialVersionUID = 2L;

    @Id
    @Column(name = "USER_ID", updatable=false, nullable=false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "USERNAME")
    private String username;

    @Column(name = "PASSWORD")
    private String password;

    @Column(name = "NAME")
    private String name;

    @Column(name = "EMAIL")
    private String email;

    @Column(name = "LOCKED")
    private boolean locked;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = Role.class)
    @JoinTable(name = "USER_ROLE", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") })
    private Set<Role> roles;

    @Override
    public GrantedAuthority[] getAuthorities() {
        List<GrantedAuthorityImpl> list = new ArrayList<GrantedAuthorityImpl>(0);
        for (Role role : roles) {
            list.add(new GrantedAuthorityImpl(role.getRole()));
        }
        return (GrantedAuthority[]) list.toArray(new GrantedAuthority[list.size()]);
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return !isLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isLocked() {
        return locked;
    }

    public void setLocked(boolean locked) {
        this.locked = locked;
    }

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

HibernateEMSDao имеет два метода для сохранения и загрузки пользователя из базы данных:

public void saveUser(final User user) {     
    getHibernateTemplate().execute(new HibernateCallback() {

        @Override
        public Object doInHibernate(Session session) throws HibernateException, SQLException {
            session.flush();
            session.setCacheMode(CacheMode.IGNORE);
            session.save(user);
            session.flush();
            return null;
        }
    });     
}

public User getUser(final Long id) {
    return (User) getHibernateTemplate().execute(new HibernateCallback() {

        @Override
        public Object doInHibernate(Session session) throws HibernateException, SQLException {
            return session.get(User.class, id);
        }
    });
}

Теперь я проверил, что если я реализую HibernateEMSDao#getUser как:

public User getUser(final Long id) {
    getHibernateTemplate().load(User.class, id);        
}

Я получаю LazyInitializationExcaption - сессия закрыта.Но первый способ работает нормально.Поэтому мне нужно предложение, чтобы избежать этого исключения в ближайшем будущем.Любая небольшая информация является заметной.

Спасибо и всего наилучшего.

Примечание. Я получил эту ошибку после перезапуска сервера.

Редактировать: добавленный код:

public void saveUser(final User user) {     
    Session session = getSession();
    Transaction transaction = session.beginTransaction();
    session.save(user);
    transaction.commit();
    session.close();
}
public User getUser(final Long id) {
    Session session = getSession();
    session.enableFetchProfile("USER-ROLE-PROFILE");
    User user = (User) session.get(User.class, id);
    session.disableFetchProfile("USER-ROLE-PROFILE");
    session.close();
    return user;
}

Ответы [ 2 ]

4 голосов
/ 29 мая 2011

Работа с отложенной загрузкой является постоянной проблемой при работе с Hibernate, JPA или ORM в целом.

Речь идет не только о предотвращении исключения LazyInitializationException, но и об эффективной обработке запросов.Даже при использовании общих DAO стратегия должна извлекать как можно больше только тех данных, которые вам действительно нужны.

Книга Pro JPA 2 от Mike Keith от Apress посвящает этому целый раздел, но, похоже,быть универсальным решением, которое всегда работает.

Время от времени это может помочь сделать соединения FETCH.Это означает, что вы не используете метод find менеджера сущностей, а используете JPQL (или HQL, если это ваш яд) запросы для всего.Ваши DAO могут содержать несколько разных методов, которые таким образом выводят граф сущностей на разные уровни.Данные обычно выбираются достаточно эффективно, но в большинстве случаев вы можете получить слишком много данных.

Другое решение, предложенное Майком Китом, также использует extended persistence context.В этом случае контекст (сеанс Hibernate) не связан с транзакцией, но остается открытым.Таким образом, объекты остаются подключенными, и ленивая загрузка работает, как и ожидалось.

Вы должны в конечном итоге закрыть расширенный контекст.Одним из способов сделать это является управление им сессионным компонентом с отслеживанием состояния, который связан с некоторой областью действия, например областью запроса или областью диалога.Таким образом, bean-компонент будет автоматически уничтожен в конце этой области, и он, в свою очередь, автоматически закроет контекст.

Однако он не без собственных проблем.Открытый контекст будет по-прежнему потреблять память, и его сохранение в течение более длительного периода времени (обычно дольше, чем область запроса) может создать серьезные риски для нехватки памяти.Если вы знаете , вы имеете дело только с несколькими объектами, это нормально, но вы должны быть осторожны.

Другая проблема, связанная с зависимой загрузкой, - это хорошо известный запрос 1 + Nпроблема.Повторение списка результатов даже среднего размера может привести к отправке сотен или тысяч запросов в БД.Я думаю, мне не нужно объяснять, что это может полностью разрушить вашу производительность.

Эта проблема с запросом 1 + N иногда может быть решена, если в значительной степени полагаться на кэширование второго уровня.Если количество сущностей не так велико и если они не обновляются так часто, то убедитесь, что они все кэшированы (с использованием кеша сущностей Hibernate или JPA 2-го уровня), что может значительно уменьшить эту проблему.Но ... это два больших "если".И если ваша основная сущность ссылается только на одну сущность, которая не кэшируется, вы снова получите сотни запросов.

Еще один подход заключается в использовании поддержки fetch profile в Hibernate, которая может частичносочетаться с другими подходами.Справочное руководство имеет раздел по этому вопросу здесь: http://docs.jboss.org/hibernate/core/3.5/reference/en/html/performance.html#performance-fetching-profiles

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

2 голосов
/ 29 мая 2011

saveUser не должен очищать сессию. Промывка сеанса действительно должна быть редкой. Пусть Hibernate позаботится об этом, и ваше приложение будет более эффективным.

Установка режима кэширования в таком месте также очень странная. Зачем ты это делаешь?

Что касается объяснения того, почему вы получаете исключение при использовании load, а не при использовании get: это потому, что нагрузка предполагает, что вы знаете, что сущность существует. Вместо того чтобы выполнять запрос на выборку для получения пользовательских данных из базы данных, он просто возвращает прокси-сервер, который будет получать данные при первом вызове метода для объекта. Если сеанс закрывается при первом вызове метода, Hibernate больше не может получить данные и выдает исключение. load следует использовать редко, кроме как для инициирования некоторого отношения к существующему объекту без необходимости получать его данные. Используйте get в других случаях.

Моя общая стратегия, позволяющая избежать исключения LazyInitializationException:

  • по возможности использовать прикрепленные объекты.
  • документ, график которого загружается методами, возвращающими отдельные объекты, и модульное тестирование того, что этот график действительно загружен
  • предпочитают merge над upadate и saveOrUpdate. Эти методы могут оставить график объектов с некоторыми прикрепленными объектами, а другие отсоединенными, в зависимости от каскадов. merge не страдает от этой проблемы.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...