Как исправить org.hibernate.LazyInitializationException - не удалось инициализировать прокси - нет сеанса


188

Я получаю следующее исключение:

Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
    at sei.persistence.wf.entities.Element_$$_jvstc68_47.getNote(Element_$$_jvstc68_47.java)
    at JSON_to_XML.createBpmnRepresantation(JSON_to_XML.java:139)
    at JSON_to_XML.main(JSON_to_XML.java:84)

когда я пытаюсь позвонить из основного в следующие строки:

Model subProcessModel = getModelByModelGroup(1112);
System.out.println(subProcessModel.getElement().getNote());

getModelByModelGroup(int modelgroupid)Сначала я реализовал метод следующим образом:

public static Model getModelByModelGroup(int modelGroupId, boolean openTransaction) {

    Session session = SessionFactoryHelper.getSessionFactory().getCurrentSession();     
    Transaction tx = null;

    if (openTransaction) {
        tx = session.getTransaction();
    }

    String responseMessage = "";

    try {
        if (openTransaction) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new Exception("Non esiste ");
            }

            model = (Model)arrModels[0];
        }

        if (openTransaction) {
            tx.commit();
        }

        return model;

   } catch(Exception ex) {
       if (openTransaction) {
           tx.rollback();
       }
       ex.printStackTrace();
       if (responseMessage.compareTo("") == 0) {
           responseMessage = "Error" + ex.getMessage();
       }
       return null;
    }
}

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

public static Model getModelByModelGroup(int modelGroupId) {
    Session session = null;
    boolean openSession = session == null;
    Transaction tx = null;
    if (openSession) {
        session = SessionFactoryHelper.getSessionFactory().getCurrentSession(); 
        tx = session.getTransaction();
    }
    String responseMessage = "";

    try {
        if (openSession) {
            tx.begin();
        }
        Query query = session.createQuery("from Model where modelGroup.id = :modelGroupId");
        query.setParameter("modelGroupId", modelGroupId);

        List<Model> modelList = (List<Model>)query.list(); 
        Model model = null;

        for (Model m : modelList) {
            if (m.getModelType().getId() == 3) {
                model = m;
                break;
            }
        }

        if (model == null) {
            Object[] arrModels = modelList.toArray();
            if (arrModels.length == 0) {
                throw new RuntimeException("Non esiste");
            }

            model = (Model)arrModels[0];

            if (openSession) {
                tx.commit();
            }
            return model;
        } catch(RuntimeException ex) {
            if (openSession) {
                tx.rollback();
            }
            ex.printStackTrace();
            if (responseMessage.compareTo("") == 0) {
                responseMessage = "Error" + ex.getMessage();
            }
            return null;        
        }
    }
}

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

Ответы:


93

Здесь неправильно то, что ваша конфигурация управления сеансом настроена на закрытие сеанса при фиксации транзакции. Проверьте, есть ли у вас что-то вроде:

<property name="current_session_context_class">thread</property>

в вашей конфигурации.

Чтобы преодолеть эту проблему, вы можете изменить конфигурацию фабрики сеансов или открыть другой сеанс и только потом запрашивать эти лениво загруженные объекты. Но я бы предложил здесь инициализировать эту ленивую коллекцию в самой getModelByModelGroup и вызвать:

Hibernate.initialize(subProcessModel.getElement());

когда вы все еще в активной сессии.

И последнее. Дружеский совет. У вас есть что-то вроде этого в вашем методе:

for (Model m : modelList) {
    if (m.getModelType().getId() == 3) {
        model = m;
        break;
    }
}

Пожалуйста, вставьте этот код, просто отфильтруйте те модели с идентификатором типа, равным 3, в запросе, всего несколькими строками выше.

Еще немного чтения:

заводская конфигурация сеанса

проблема с закрытой сессией


1
Спасибо! Я решил свою проблему, используя openSession () вместо getCurrentSession (), поскольку одна из ссылок, которые вы мне дали, предложила это, но теперь я боюсь, что это неправильно
Blerta Dhimitri

2
Нет, это, наверное, хорошо. Но прочитайте еще немного, чтобы иметь возможность полностью контролировать свои сеансы и транзакции. Очень важно знать основы, потому что все технологии более высокого уровня, такие как Spring, Hibernate и другие, работают по одной и той же концепции.
Горонцы

179

Если вы используете Spring, пометьте класс как @Transactional , то Spring будет обрабатывать управление сессиями.

@Transactional
public class MyClass {
    ...
}

Благодаря использованию @Transactionalмногие важные аспекты, такие как распространение транзакций, обрабатываются автоматически. В этом случае, если вызван другой транзакционный метод, у метода будет возможность присоединиться к текущей транзакции, избегая исключения «без сеанса».

ПРЕДУПРЕЖДЕНИЕ. Если вы используете @Transactional, пожалуйста, имейте в виду, что в результате. Смотрите эту статью для распространенных ошибок. Например, обновления сущностей сохраняются, даже если вы явно не вызываетеsave


21
Я не могу переоценить важность этого ответа. Я серьезно рекомендую попробовать этот вариант в первую очередь.
sparkyspider

6
Также обратите внимание, что вы должны добавить @EnableTransactionManagementв свою конфигурацию, чтобы включить транзакции. « если вызывается другой транзакционный метод, метод будет иметь возможность присоединения к текущей транзакции », это поведение отличается для разных способов реализации транзакций, т.е. интерфейсный прокси или прокси класса или AspectJ. Обратитесь к документации .
Эрик Хофер

1
Должны ли мы понимать, что Transactionalпоэтому рекомендуется использовать аннотацию Spring не только для изменения транзакций, но и для доступа только к тем?
Стефан

8
Я бы настоятельно рекомендовал использовать эту аннотацию в верхней части класса только для тестирования. Реальный код должен помечать каждый метод как транзакцию в классе отдельно. Если не все методы в классе потребуют открытого соединения с транзакцией к базе данных.
11

1
Разве не безопасно поставить @Transactional (readOnly = true) вместо просто @Transactional?
Хамедз

105

Вы можете попробовать установить

<property name="hibernate.enable_lazy_load_no_trans">true</property>

в hibernate.cfg.xml или persistence.xml

Проблема, которую нужно иметь в виду с этим свойством, хорошо объяснена здесь


8
Можете ли вы объяснить его значение?
Мохит Канвар

2
Мне также любопытно, что это делает. Это помогло решить проблему, но я хотел бы понять, почему.
Хасан

3
для persistence.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV


6
НЕ ИСПОЛЬЗУЙТЕ ДАННУЮ СОБСТВЕННОСТЬ, ЕСЛИ ВЕСНА УПРАВЛЯЕТ ВАШИМИ СДЕЛАМИ, НАСТОЯЩАЯ СОБСТВЕННОСТЬ ПРИВЕДЕТ К ВЗРЫВУ СДЕЛКИ, ПРОСТО ВЕСНА
ОТКЛЮЧИТ ЗАЯВКУ

54

Лучший способ справиться с этимLazyInitializationException - использовать JOIN FETCHдирективу:

Query query = session.createQuery(
    "from Model m " +
    "join fetch m.modelType " +
    "where modelGroup.id = :modelGroupId"
);

В любом случае, НЕ используйте следующие анти-паттерны, как предлагается в некоторых ответах:

Иногда проекция DTO является лучшим выбором, чем выборка объектов, и таким образом вы не получите ничего LazyInitializationException.


Как я могу определить, какой вызов имеет проблемы? Мне трудно идентифицировать вызов. Там в любом случае ? Для целей тестирования я использовал FetchType=EAGER, но это не правильное решение, верно?
Шантарам Тупе

Просто используйте логирование. И EAGER это плохо, да.
Влад Михальча

Затем вы должны использовать DTO или инициализировать все ассоциации, прежде чем покинуть @Transactionalслужбу.
Влад Михальча

2
Мы должны пропагандировать лучшую корпоративную практику, но не быстрое решение.
etlds

21

Я получал ту же ошибку для отношений один ко многим для аннотации ниже.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL)

После добавления fetch = FetchType.EAGER изменилось, как показано ниже, у меня это сработало.

@OneToMany(mappedBy="department", cascade = CascadeType.ALL, fetch=FetchType.EAGER)

26
Да, это может исправить это, но теперь вы загружаете все дерево данных. Это будет иметь негативное влияние на производительность в большинстве случаев
astro8891


9

Это исключение из-за того session.getEntityById(), что при вызове сеанс будет закрыт. Таким образом, вам нужно повторно присоединить сущность к сеансу. Либо простое решение - это просто настройка default-lazy="false" для вас, entity.hbm.xmlили, если вы используете аннотации, просто добавьте @Proxy(lazy=false)свой класс сущности.


5

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

Query query = session.createQuery("from Model m join fetch m.element where modelGroup.id = :modelGroupId")

4

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

Например:

Если вы пытаетесь получить ObjectB из ObjectA, где ObjectB является внешним ключом в ObjectA.

Запрос:

SELECT objA FROM ObjectA obj JOIN FETCH obj.objectB objB

3

Здесь есть несколько хороших ответов, которые обрабатывают эту ошибку в широком смысле. С Spring Security я столкнулся с особой ситуацией, в которой было быстрое, хотя, возможно, и не оптимальное решение.

Во время авторизации пользователя (сразу после входа в систему и прохождения аутентификации) я тестировал сущность пользователя на предмет определенных полномочий в пользовательском классе, который расширяет SimpleUrlAuthenticationSuccessHandler.

Моя пользовательская сущность реализует UserDetails и имеет набор отложенных ролей, которые выдавали исключение «org.hibernate.LazyInitializationException - не удалось инициализировать исключение прокси - без сеанса». Изменение этого набора с "fetch = FetchType.LAZY" на "fetch = FetchType.EAGER" исправило это для меня.



2

Столкнулся с тем же исключением в другом случае использования.

введите описание изображения здесь

Вариант использования: Попробуйте прочитать данные из БД с помощью DTO-проекции.

Решение: используйте метод get вместо load .

Общая операция

public class HibernateTemplate {
public static Object loadObject(Class<?> cls, Serializable s) {
    Object o = null;
    Transaction tx = null;
    try {
        Session session = HibernateUtil.getSessionFactory().openSession();
        tx = session.beginTransaction();
        o = session.load(cls, s); /*change load to get*/
        tx.commit();
        session.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return o;
}

}

Постоянство Класс

public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "Id")
private int customerId;

@Column(name = "Name")
private String customerName;

@Column(name = "City")
private String city;

//constructors , setters and getters

}

Интерфейс CustomerDAO

public interface CustomerDAO 
     {
   public CustomerTO getCustomerById(int cid);
     }

Класс объекта Entity Transfer

public class CustomerTO {

private int customerId;

private String customerName;

private String city;

//constructors , setters and getters

}

Фабричный класс

public class DAOFactory {

static CustomerDAO customerDAO;
static {
    customerDAO = new HibernateCustomerDAO();
}

public static CustomerDAO getCustomerDAO() {
    return customerDAO;
}

}

DAO для конкретного объекта

public class HibernateCustomerDAO implements CustomerDAO {

@Override
public CustomerTO getCustomerById(int cid) {
    Customer cust = (Customer) HibernateTemplate.loadObject(Customer.class, cid);
    CustomerTO cto = new CustomerTO(cust.getCustomerId(), cust.getCustomerName(), cust.getCity());
    return cto;
}

}

Получение данных: тестовый класс

CustomerDAO cdao = DAOFactory.getCustomerDAO();
CustomerTO c1 = cdao.getCustomerById(2);
System.out.println("CustomerName -> " + c1.getCustomerName() + " ,CustomerCity -> " + c1.getCity());

Представить данные

введите описание изображения здесь

Запрос и вывод, сгенерированные системой Hibernate

Спящий режим: выберите customer0_.Id как Id1_0_0_, customer0_.City как City2_0_0_, customer0_.Name как Name3_0_0_ из CustomerLab31 customer0_, где customer0_.Id =?

CustomerName -> Cody, CustomerCity -> LA


1

Если вы используете Grail'sFramework, просто устранить исключение отложенной инициализации с помощьюLazy ключевого слова в определенном поле в классе домена.

Например:

class Book {
    static belongsTo = [author: Author]
    static mapping = {
        author lazy: false
    }
}

Найти дополнительную информацию здесь


1

В моем случае неуместна session.clear() эта проблема .


1

Это означает, что вы используете JPA или hibernate в своем коде и выполняете операцию изменения в БД без выполнения транзакции бизнес-логики. Простое решение для этого - пометить ваш кусок кода @Transactional



-2

Вы также можете решить эту проблему, добавив lazy = false в ваш файл * .hbm.xml, или вы можете инициировать ваш объект в Hibernate.init (Object), когда вы получаете объект из db.


10
вообще добавление lazy = false не очень хорошая идея. именно поэтому ленивый по умолчанию
верен

ОП четко сказал заранее, что ему не разрешено это делать.
GingerHead

-2

Сделайте следующие изменения в servlet-context.xml

    <beans:property name="hibernateProperties">
        <beans:props>

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

        </beans:props>
    </beans:property>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.