org.hibernate.LazyInitializationException: как правильно использовать функцию отложенной загрузки Hibernate - PullRequest
9 голосов
/ 30 апреля 2011

У меня возникли проблемы с загрузкой списка объектов из моей базы данных с использованием режима Hibernate и lazy = true.Надеюсь, что кто-то может помочь мне здесь.

У меня есть простой класс здесь, называемый UserAccount, который выглядит следующим образом:

public class UserAccount {
    long id;
    String username;
    List<MailAccount> mailAccounts = new Vector<MailAccount>();

    public UserAccount(){
        super();
    }

    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 List<MailAccount> getMailAccounts() {
        if (mailAccounts == null) {
            mailAccounts = new Vector<MailAccount>();
        }
        return mailAccounts;
    }

    public void setMailAccounts(List<MailAccount> mailAccounts) {
        this.mailAccounts = mailAccounts;
    }
}

Я отображаю этот класс в Hibernate через следующий файл сопоставления:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="test.account.UserAccount" table="USERACCOUNT">

        <id name="id" type="long" access="field">
            <column name="USER_ACCOUNT_ID" />
            <generator class="native" />
        </id>

        <property name="username" />

        <bag name="mailAccounts" table="MAILACCOUNTS" lazy="true" inverse="true" cascade="all">
            <key column="USER_ACCOUNT_ID"></key>
            <one-to-many class="test.account.MailAccount" />
        </bag>

    </class>
</hibernate-mapping>

Как видите, в элементе отображения пакетов для lazy установлено значение "true".

Сохранение данных в базе данных работает нормально:

Загрузка такжеработает, вызывая loadUserAccount(String username) (см. код ниже):

public class HibernateController implements DatabaseController {
    private Session                 session         = null;
    private final SessionFactory    sessionFactory  = buildSessionFactory();

    public HibernateController() {
        super();
    }

    private SessionFactory buildSessionFactory() {
        try {
            return new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public UserAccount loadUserAccount(String username) throws FailedDatabaseOperationException {
        UserAccount account = null;
        Session session = null;
        Transaction transaction = null;
        try {
            session = getSession();
            transaction = session.beginTransaction();
            Query query = session.createQuery("FROM UserAccount WHERE username = :uname").setParameter("uname", username));
            account = (UserAccount) query.uniqueResult();
            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
            throw new FailedDatabaseOperationException(e);
        } finally {
            if (session.isOpen()) {
                // session.close();
            }
        }

        return account;
    }

    private Session getSession() {
        if (session == null){
            session = getSessionFactory().getCurrentSession();
        }
        return session;
    }
}

Проблема заключается в следующем: когда я получаю доступ к элементам в списке "mailAccounts", я получаю следующее исключение:

org.hibernate.LazyInitializationException: не удалось лениво инициализировать коллекцию ролей: test.account.UserAccount.mailAccounts, ни один сеанс или сеанс не был закрыт

Я предполагаю, что причиной этого исключения является то, чтоСессия была закрыта (не знаю, почему и как), поэтому Hibernate не может загрузить список.Как вы можете видеть, я даже удалил вызов session.close() из метода loadUserAccount(), но сессия все еще кажется закрытой или замененной другим экземпляром.Если я установлю lazy=false, то все будет работать гладко, но это не то, что я хотел, потому что мне нужна функция загрузки данных «по требованию» из-за проблем с производительностью.

Итак, если я не уверенчто мой сеанс все еще действителен после завершения метода loadUserAccount(String username), какой смысл иметь эту функцию и как мне обойти это?

Спасибо за вашу помощь!

Ps: Яновичок в Hibernate, так что, пожалуйста, извините меня за noobishness.

Обновление: Вот мой config.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.password">foo</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mytable</property>
        <property name="hibernate.connection.username">user</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>

        <!-- Auto create tables -->
<!--        <property name="hbm2ddl.auto">create</property>-->

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Mappings -->
        <mapping resource="test/account/SmampiAccount.hbm.xml"/>
        <mapping resource="test/account/MailAccount.hbm.xml"/>
    </session-factory>
</hibernate-configuration>
hibernate

Ответы [ 3 ]

22 голосов
/ 30 апреля 2011

Ленивая загрузка работает или нет, не имеет ничего общего с границами транзакции.Требуется только, чтобы сессия была открыта.

Однако, когда сессия открыта, зависит от того, как вы на самом деле настроили SessionFactory, о чем вы нам не сказали!Конфигурация происходит за тем, что на самом деле делает SessionFactory.getCurrentSession()!Если вы позволяете ему работать с версией по умолчанию ThreadLocalSessionContext и ничего не делаете для управления жизненным циклом, он действительно по умолчанию закрывает сеанс при фиксации.(Отсюда распространенное представление о том, что расширение границ транзакций является «исправлением» для исключения отложенной загрузки.)

Если вы управляете собственным жизненным циклом сеанса с помощью sessionFactory.openSession() и session.close(), вы сможете выполнять отложенную загрузку.штраф в течение жизненного цикла сеанса, вне границ транзакции.В качестве альтернативы вы можете предоставить подкласс ThreadLocalSessionContext, который управляет жизненным циклом сеанса с желаемыми границами.Есть также легкодоступные альтернативы, такие как фильтр OpenSessionInView, который можно использовать в веб-приложениях для привязки жизненного цикла сеанса к жизненному циклу веб-запроса.

edit: Конечно, вы также можете просто инициализировать список внутрисделка, если это работает для вас.Я просто думаю, что это приводит к действительно неуклюжим API, когда вам нужна новая сигнатура метода какого-то параметра 'flag' для каждого уровня увлажнения вашей сущности.dao.getUser () dao.getUserWithMailAccounts () dao.getUserWIthMailAccountsAndHistoricalIds () и т. д.

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

Это зависит от ваших требованийи архитектура, насколько велик разговор на самом деле.

5 голосов
/ 30 апреля 2011

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

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

0 голосов
/ 30 апреля 2011

Вам необходимо заключить весь процесс в транзакцию.

Поэтому вместо запуска и фиксации транзакции в loadUserAccount сделайте это вне этого.

Например:

public void processAccount()
{
    getSession().beginTransaction();
    UserAccount userAccount = loadUserAccount("User");
    Vector accts = userAccount.getMailAccounts();  //This here is lazy-loaded -- DB requests will happen here
    getSession().getTransaction().commit();
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...