Як виправити 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;
    }
}

Будь ласка, введений в цей код, просто відфільтруйте ці моделі з типом id рівним 3 в заяві запиту лише на пару рядків вище.

Ще кілька читань:

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

проблема із закритим сеансом


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

2
Ні, це, мабуть, добре. Але прочитайте ще кілька, щоб мати можливість повністю контролювати свої сеанси та транзакції. Дійсно важливо знати основи, тому що всі технології вищого рівня, такі як Spring, Hibernate та інші, працюють саме на цій самій концепції.
goroncy

179

Якщо ви використовуєте Spring, позначте клас як @Transactional , то Spring буде керувати сеансом управління.

@Transactional
public class MyClass {
    ...
}

За допомогою @Transactionalбагатьох важливих аспектів, таких як поширення транзакцій, обробляється автоматично. У цьому випадку, якщо інший транзакційний метод називається, метод матиме можливість приєднатися до поточної транзакції, уникаючи винятку "без сеансу".

ПОПЕРЕДЖЕННЯ Якщо ви користуєтесь @Transactional, будь ласка, пам’ятайте про отриману поведінку. Дивіться цю статтю про загальні підводні камені. Наприклад, оновлення суб’єктів зберігаються, навіть якщо ви не телефонуєте прямоsave


21
Я не можу завищувати важливість цієї відповіді. Я серйозно рекомендую спробувати цей варіант спочатку.
sparkyspider

6
Також зауважте, що вам потрібно додати @EnableTransactionManagementдо своєї конфігурації, щоб активувати транзакції. " якщо інший транзакційний метод називається, метод матиме можливість приєднання до поточної транзакції ", така поведінка відрізняється для різних способів реалізації транзакцій, тобто проксі-інтерфейс проти проксі-класу або плетіння AspectJ. Зверніться до документації .
Ерік Хофер

1
Чи слід розуміти, що весняна Transactionalанотація таким чином радиться не лише для зміни транзакцій, але і для доступу лише до них?
Стефан

8
Я серйозно рекомендую використовувати цю анотацію на першокласному рівні лише для тестування. Реальний код повинен позначати кожен метод як транзакцію в класі окремо. Якщо всі методи в класі не вимагають відкритого з'єднання з транзакцією до бази даних.
m1ld

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

105

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

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

в hibernate.cfg.xml або persistent.xml

Проблема мати на увазі , з цією властивістю добре пояснено тут


8
Чи можете ви пояснити його значення також?
Мохіт Канвар

2
Мені також цікаво, що це робить. Це вирішило проблему, яку я мав, але я хотів би зрозуміти, чому.
Хассан

3
for persistent.xml:<property name="hibernate.enable_lazy_load_no_trans" value="true"/>
ACV

33
Ви усвідомлюєте, що hibernate.enable_lazy_load_no_transце анти-патерни , правда?
Влад Міхалча

6
НЕ ВИКОРИСТОВУЄТЬСЯ ЦІЙ ВЛАСНІСТЮ, ЯКЩО ВІДБУДУЮТЬ, ЩО ВИПРАВЛЯЄТЬСЯ Вашими трансакціями, ЦЕМУ ВЛАСНІСТЬ ВИКОНУЙТЕ НА ВИКОНАННЯ ТРАНЗАКЦІЙ, ПРОСТА ВЕСНА ЗАМЕЧАЄ ЗАЯВКУ
Youans

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 замість завантаження .

Родова операція

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);
     }

Клас об'єкта передачі особи

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());

Наявні дані

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

Запит та вихід, сформований у сплячій системі

Перебуває в сплячому режимі: виберіть customer0_.Id як Id1_0_0_, customer0_.City як City2_0_0_, customer0_.Name як Ім'я3_0_0_ від CustomerLab31 customer0_ де customer0_.Id =?

Ім'я клієнта -> Коді, CustomerCity -> LA


1

Якщо ви використовуєте Grail'sFramework, вирішити виключення лінивої ініціалізації можна просто за допомогою Lazyключового слова в певному полі класу домену.

Наприклад:

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

Додаткову інформацію можна знайти тут


1

У моєму випадку session.clear()ця проблема спричинила неправильне місце .


1

Це означає, що ви використовуєте JPA або перебуваєте в сплячому режимі у своєму коді та виконуєте операції по модифікації в БД, не роблячи транзакцій бізнес-логіки. Таким простим рішенням для цього є позначка вашого коду @Transactional



-2

Ви також можете вирішити це, додавши в файл * .hbm.xml lazy = false або ви можете запустити свій об'єкт у Hibernate.init (Object), коли ви отримаєте об'єкт від db


10
як правило, додавання ледачого = помилкового - це не дуже гарна ідея. тому лінивий за замовчуванням вірно
MoienGK

ОП заздалегідь чітко сказала, що йому цього не дозволяють.
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.