Рубрики
Без рубрики

Общие исключения Спящего режима

Посмотрите на некоторые распространенные ошибки в Hibernate и как их решить

Автор оригинала: Chandra Prakash.

1. введение

В этом уроке мы обсудим некоторые распространенные исключения, с которыми мы можем столкнуться при работе с Hibernate.

Мы рассмотрим их цель и некоторые общие причины. Кроме того, мы рассмотрим их решения.

2. Обзор исключений гибернации

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

Эти исключения в основном распространяются на Исключение HibernateException . Однако, если мы используем Hibernate в качестве поставщика персистентности JPA, эти исключения могут быть обернуты в PersistenceException .

Оба этих базовых класса расширяются из RuntimeException . Поэтому все они непроверены. Следовательно, нам не нужно ловить или объявлять их в каждом месте, где они используются.

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

Давайте теперь рассмотрим каждый из них по очереди.

3. Ошибки Отображения

Объектно-реляционное сопоставление является основным преимуществом Hibernate. В частности, это освобождает нас от ручного написания инструкций SQL.

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

При указании этих сопоставлений мы можем допустить ошибки. Они могут быть в спецификации отображения. Или может быть несоответствие между объектом Java и соответствующей таблицей базы данных.

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

Давайте рассмотрим эти ошибки на нескольких примерах.

3.1. Исключение MappingException

Проблема с объектно-реляционным отображением приводит к возникновению исключения MappingException :

public void whenQueryExecutedWithUnmappedEntity_thenMappingException() {
    thrown.expectCause(isA(MappingException.class));
    thrown.expectMessage("Unknown entity: java.lang.String");

    Session session = sessionFactory.getCurrentSession();
    NativeQuery query = session
      .createNativeQuery("select name from PRODUCT", String.class);
    query.getResultList();
}

В приведенном выше коде метод createNativeQuery пытается сопоставить результат запроса с указанным типом Java String. Он использует неявное отображение класса String из Метамодели | для выполнения сопоставления.

Однако в классе String не указано никакого сопоставления. Поэтому Hibernate не знает, как сопоставить столбец name с String , и выдает исключение.

Для подробного анализа возможных причин и решений ознакомьтесь с разделом Исключение отображения Hibernate – Неизвестная сущность .

Аналогично, другие ошибки также могут вызвать это исключение:

  • Смешивание аннотаций по полям и методам
  • Не удалось указать @JoinTable для @ManyToMany ассоциации
  • Конструктор по умолчанию сопоставленного класса создает исключение во время обработки сопоставления

Кроме того, MappingException имеет несколько подклассов, которые могут указывать на конкретные проблемы с отображением:

  • AnnotationException – проблема с аннотацией
  • DuplicateMappingException – дублирование сопоставления для имени класса, таблицы или свойства
  • InvalidMappingException – отображение недопустимо
  • MappingNotFoundException – не удалось найти ресурс сопоставления
  • PropertyNotFoundException – ожидаемый метод getter или setter не может быть найден в классе

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

3.2. Исключение аннотации

Чтобы понять исключение AnnotationException, давайте создадим сущность без аннотации идентификатора в любом поле или свойстве:

@Entity
public class EntityWithNoId {
    private int id;
    public int getId() {
        return id;
    }

    // standard setter
}

Поскольку Hibernate ожидает , что каждая сущность будет иметь идентификатор , мы получим AnnotationException при использовании сущности:

public void givenEntityWithoutId_whenSessionFactoryCreated_thenAnnotationException() {
    thrown.expect(AnnotationException.class);
    thrown.expectMessage("No identifier specified for entity");

    Configuration cfg = getConfiguration();
    cfg.addAnnotatedClass(EntityWithNoId.class);
    cfg.buildSessionFactory();
}

Кроме того, некоторые другие вероятные причины:

  • Генератор неизвестных последовательностей, используемый в аннотации @GeneratedValue
  • @Temporal аннотация, используемая с Java 8 Дата /| Время класс Целевая сущность отсутствует или отсутствует для
  • @ManyToOne или @OneToMany Необработанные классы коллекций
  • используются с аннотациями отношений @OneToMany или @ManyToMany Конкретные классы, используемые с аннотациями коллекции
  • @OneToMany , @ManyToMany или @ElementCollection как Hibernate ожидает интерфейсы коллекции

Чтобы устранить это исключение, мы должны сначала проверить конкретную аннотацию, указанную в сообщении об ошибке.

4. Ошибки Управления схемой

Автоматическое управление схемой базы данных-еще одно преимущество гибернации. Например, он может генерировать инструкции DDL для создания или проверки объектов базы данных.

Чтобы использовать эту функцию, нам нужно соответствующим образом установить свойство hibernate.hbm2ddl.auto .

Если при выполнении управления схемой возникают проблемы, мы получаем исключение. Давайте рассмотрим эти ошибки.

4.1. Исключение SchemaManagementException

Любая проблема, связанная с инфраструктурой при выполнении управления схемой, вызывает исключение SchemaManagementException .

Чтобы продемонстрировать, давайте проинструктируем Hibernate для проверки схемы базы данных:

public void givenMissingTable_whenSchemaValidated_thenSchemaManagementException() {
    thrown.expect(SchemaManagementException.class);
    thrown.expectMessage("Schema-validation: missing table");

    Configuration cfg = getConfiguration();
    cfg.setProperty(AvailableSettings.HBM2DDL_AUTO, "validate");
    cfg.addAnnotatedClass(Product.class);
    cfg.buildSessionFactory();
}

Поскольку таблица, соответствующая Product , отсутствует в базе данных, мы получаем исключение проверки схемы при построении S essionFactory .

Кроме того, существуют и другие возможные сценарии для этого исключения:

  • не удается подключиться к базе данных для выполнения задач управления схемой
  • схема отсутствует в базе данных

4.2. Исключение Принятия команд

Любая проблема с выполнением DDL, соответствующего определенной команде управления схемой, может вызвать исключение CommandAcceptanceException .

В качестве примера, давайте укажем неправильный диалект при настройке SessionFactory :

public void whenWrongDialectSpecified_thenCommandAcceptanceException() {
    thrown.expect(SchemaManagementException.class);
        
    thrown.expectCause(isA(CommandAcceptanceException.class));
    thrown.expectMessage("Halting on error : Error executing DDL");

    Configuration cfg = getConfiguration();
    cfg.setProperty(AvailableSettings.DIALECT,
      "org.hibernate.dialect.MySQLDialect");
    cfg.setProperty(AvailableSettings.HBM2DDL_AUTO, "update");
    cfg.setProperty(AvailableSettings.HBM2DDL_HALT_ON_ERROR,"true");
    cfg.getProperties()
      .put(AvailableSettings.HBM2DDL_HALT_ON_ERROR, true);

    cfg.addAnnotatedClass(Product.class);
    cfg.buildSessionFactory();
}

Здесь мы указали неправильный диалект: MySQLDialect. Кроме того, мы инструктируем Hibernate обновлять объекты схемы. Следовательно, операторы DDL, выполняемые Hibernate для обновления базы данных H2, завершатся ошибкой, и мы получим исключение.

Здесь мы указали неправильный диалект: MySQLDialect. Кроме того, мы инструктируем Hibernate обновлять объекты схемы. Следовательно, операторы DDL, выполняемые Hibernate для обновления базы данных H2, завершатся ошибкой, и мы получим исключение.

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

Аналогично, это некоторые другие распространенные причины этой ошибки:

  • Существует несоответствие в именах столбцов между отображением и базой данных
  • Два класса сопоставляются с одной и той же таблицей
  • Имя, используемое для класса или таблицы, является зарезервированным словом в базе данных, например USER
  • Пользователь, используемый для подключения к базе данных, не имеет необходимых привилегий

5. Ошибки выполнения SQL

Когда мы вставляем, обновляем, удаляем или запрашиваем данные с помощью Hibernate, он выполняет инструкции DML для базы данных с помощью JDBC . Этот API вызывает исключение SQLException , если операция приводит к ошибкам или предупреждениям.

Hibernate преобразует это исключение в Исключение JDBC или один из его подходящих подклассов:

  • Исключение ConstraintViolationException
  • Исключение данных
  • Исключение JDBCConnectionException
  • LockAcquisitionException
  • Пессимистическое исключение
  • Исключение QueryTimeoutException
  • Исключение SQLGrammarException
  • GenericJDBCException

Давайте обсудим распространенные ошибки.

5.1. Исключение JDBCException

Исключение JDBC всегда вызывается определенным оператором SQL. Мы можем вызвать метод getSQL , чтобы получить оскорбительную инструкцию SQL.

Кроме того, мы можем получить базовое SQLException с помощью метода getSQLException .

5.2. Исключение SQLGrammarException

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

Например, отсутствующая таблица может привести к этой ошибке при запросе данных :

public void givenMissingTable_whenQueryExecuted_thenSQLGrammarException() {
    thrown.expect(isA(PersistenceException.class));
    thrown.expectCause(isA(SQLGrammarException.class));
    thrown.expectMessage("SQLGrammarException: could not prepare statement");

    Session session = sessionFactory.getCurrentSession();
    NativeQuery query = session.createNativeQuery(
      "select * from NON_EXISTING_TABLE", Product.class);
    query.getResultList();
}

Кроме того, мы можем получить эту ошибку при сохранении данных, если таблица отсутствует:

public void givenMissingTable_whenEntitySaved_thenSQLGrammarException() {
    thrown.expect(isA(PersistenceException.class));
    thrown.expectCause(isA(SQLGrammarException.class));
    thrown
      .expectMessage("SQLGrammarException: could not prepare statement");

    Configuration cfg = getConfiguration();
    cfg.addAnnotatedClass(Product.class);

    SessionFactory sessionFactory = cfg.buildSessionFactory();
    Session session = null;
    Transaction transaction = null;
    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
        Product product = new Product();
        product.setId(1);
        product.setName("Product 1");
        session.save(product);
        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
        closeSessionFactoryQuietly(sessionFactory);
    }
}

Некоторые другие возможные причины:

  • Используемая стратегия именования не сопоставляет классы с правильными таблицами
  • Столбец, указанный в @JoinColumn ,не существует

5.3. Исключение ConstraintViolationException

A ConstraintViolationException указывает, что запрошенная операция DML привела к нарушению ограничения целостности. Мы можем получить имя этого ограничения, вызвав метод getConstraintName .

Распространенной причиной этого исключения является попытка сохранить дубликаты записей:

public void whenDuplicateIdSaved_thenConstraintViolationException() {
    thrown.expect(isA(PersistenceException.class));
    thrown.expectCause(isA(ConstraintViolationException.class));
    thrown.expectMessage(
      "ConstraintViolationException: could not execute statement");

    Session session = null;
    Transaction transaction = null;

    for (int i = 1; i <= 2; i++) {
        try {
            session = sessionFactory.openSession();
            transaction = session.beginTransaction();
            Product product = new Product();
            product.setId(1);
            product.setName("Product " + i);
            session.save(product);
            transaction.commit();
        } catch (Exception e) {
            rollbackTransactionQuietly(transaction);
            throw (e);
        } finally {
            closeSessionQuietly(session);
        }
    }
}

Кроме того, сохранение значения null в столбце NOT NULL в базе данных может вызвать эту ошибку.

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

5.4. Исключение данных

Исключение данных указывает на то, что оценка инструкции SQL привела к какой-либо незаконной операции, несоответствию типов или неправильной мощности.

Например, использование символьных данных в числовом столбце может привести к этой ошибке:

public void givenQueryWithDataTypeMismatch_WhenQueryExecuted_thenDataException() {
    thrown.expectCause(isA(DataException.class));
    thrown.expectMessage(
      "org.hibernate.exception.DataException: could not prepare statement");

    Session session = sessionFactory.getCurrentSession();
    NativeQuery query = session.createNativeQuery(
      "select * from PRODUCT where id='wrongTypeId'", Product.class);
    query.getResultList();
}

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

5.5. Исключение JDBCConnectionException

Исключение соединения JDBC указывает на проблемы при взаимодействии с базой данных.

Например, сбой базы данных или сети может привести к возникновению этого исключения.

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

Чтобы решить эту проблему, мы должны сначала убедиться, что хост базы данных присутствует и что он работает. Затем мы должны убедиться, что для подключения к базе данных используется правильная аутентификация. Наконец, мы должны проверить, правильно ли установлено значение тайм-аута в пуле соединений.

5.6. Исключение QueryTimeoutException

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

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

Чтобы устранить эту проблему, мы можем увеличить время ожидания запроса для длительных запросов несколькими способами:

  • Установите элемент timeout в @NamedQuery или @NamedNativeQuery аннотации
  • Вызовите setHint метод интерфейса запроса
  • Вызовите метод setTimeout интерфейса Transaction
  • Вызовите метод setTimeout интерфейса Query

6. Ошибки, Связанные с Состоянием Сеанса

Давайте теперь рассмотрим ошибки, связанные с ошибками использования сеанса гибернации.

6.1. Исключение NonUniqueObjectException

Hibernate не позволяет использовать два объекта с одинаковым идентификатором в одном сеансе.

Если мы попытаемся связать два экземпляра одного и того же класса Java с одним и тем же идентификатором в одном сеансе, мы получим NonUniqueObjectException . Мы можем получить имя и идентификатор сущности, вызвав методы getEntityName() и getIdentifier () .

Чтобы воспроизвести эту ошибку, давайте попробуем сохранить два экземпляра Product с одним и тем же идентификатором в сеансе:

public void 
givenSessionContainingAnId_whenIdAssociatedAgain_thenNonUniqueObjectException() {
    thrown.expect(isA(NonUniqueObjectException.class));
    thrown.expectMessage(
      "A different object with the same identifier value was already associated with the session");

    Session session = null;
    Transaction transaction = null;

    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Product product = new Product();
        product.setId(1);
        product.setName("Product 1");
        session.save(product);

        product = new Product();
        product.setId(1);
        product.setName("Product 2");
        session.save(product);

        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

Мы получим NonUniqueObjectException, как и ожидалось.

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

6.2. Исключение StaleStateException

Hibernate выдает StaleStateException s, когда проверка номера версии или метки времени не выполняется. Это указывает на то, что сеанс содержал устаревшие данные.

Иногда это оборачивается в OptimisticLockException .

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

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

public void whenUpdatingNonExistingObject_thenStaleStateException() {
    thrown.expect(isA(OptimisticLockException.class));
    thrown.expectMessage(
      "Batch update returned unexpected row count from update");
    thrown.expectCause(isA(StaleStateException.class));

    Session session = null;
    Transaction transaction = null;

    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Product product = new Product();
        product.setId(15);
        product.setName("Product1");
        session.update(product);
        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

Некоторые другие возможные сценарии:

  • мы не указали правильную стратегию несохраненной стоимости для объекта
  • два пользователя попытались удалить одну и ту же строку почти одновременно
  • мы вручную задаем значение в поле автоматически сгенерированный идентификатор или версия

7. Ошибки Ленивой Инициализации

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

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

Давайте рассмотрим это исключение и различные способы его устранения.

7.1. Исключение LazyInitializationException

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

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

Во-вторых, мы можем получить эту ошибку с помощью Spring Data , если мы используем метод getOne . Этот метод лениво извлекает экземпляр.

Есть много способов решить это исключение.

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

Во-вторых, мы можем держать сеанс открытым до тех пор, пока представление не будет отображено. Это известно как ” Открытая сессия в представлении “, и это анти-шаблон. Мы должны избегать этого, так как у него есть несколько недостатков.

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

Наконец, мы можем инициализировать необходимые ассоциации на бизнес-уровнях. Мы обсудим это в следующем разделе.

7.2. Инициализация Соответствующих ленивых отношений на Бизнес-уровне

Существует множество способов инициализации ленивых отношений.

Один из вариантов-инициализировать их, вызвав соответствующие методы в сущности. В этом случае Hibernate выдаст несколько запросов к базе данных, что приведет к снижению производительности. Мы называем это проблемой “N+1 SELECT”.

Во-вторых, мы можем использовать Fetch Join для получения данных в одном запросе. Однако для этого нам нужно написать пользовательский код.

Наконец, мы можем использовать графики сущностей для определения всех атрибутов, которые будут извлечены . Мы можем использовать аннотации @NamedEntityGraph, @NamedAttributeNode и @NamedEntitySubgraph для декларативного определения графа сущностей. Мы также можем определить их программно с помощью API JPA. Затем мы извлекаем весь график за один вызов, указав его в операции выборки .

8. Проблемы с транзакциями

Транзакции определяют единицы работы и изоляцию между параллельными действиями. Мы можем разграничить их двумя различными способами. Во-первых, мы можем определить их декларативно, используя аннотации. Во-вторых, мы можем управлять ими программно, используя Hibernate Transaction API .

Кроме того, Hibernate делегирует управление транзакциями менеджеру транзакций. Если транзакция не может быть запущена, зафиксирована или откатана по какой-либо причине, Hibernate создает исключение.

Обычно мы получаем Исключение TransactionException или Исключение IllegalArgumentException в зависимости от менеджера транзакций.

В качестве иллюстрации давайте попробуем зафиксировать транзакцию, которая была отмечена для отката:

public void 
givenTxnMarkedRollbackOnly_whenCommitted_thenTransactionException() {
    thrown.expect(isA(TransactionException.class));
    thrown.expectMessage(
        "Transaction was marked for rollback only; cannot commit");

    Session session = null;
    Transaction transaction = null;
    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Product product = new Product();
        product.setId(15);
        product.setName("Product1");
        session.save(product);
        transaction.setRollbackOnly();

        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

Аналогично, другие ошибки также могут вызвать исключение:

  • Смешивание декларативных и программных транзакций
  • Попытка запустить транзакцию, когда другая транзакция уже активна в сеансе
  • Попытка фиксации или отката без запуска транзакции
  • Попытка совершить или откатить транзакцию несколько раз

9. Проблемы параллелизма

Hibernate поддерживает две стратегии блокировки для предотвращения несогласованности базы данных из – за одновременных транзакций – оптимистическую и пессимистическую . Оба они вызывают исключение в случае конфликта блокировки.

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

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

public void whenDeletingADeletedObject_thenOptimisticLockException() {
    thrown.expect(isA(OptimisticLockException.class));
    thrown.expectMessage(
        "Batch update returned unexpected row count from update");
    thrown.expectCause(isA(StaleStateException.class));

    Session session = null;
    Transaction transaction = null;

    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Product product = new Product();
        product.setId(12);
        product.setName("Product 12");
        session.save(product1);
        transaction.commit();
        session.close();

        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
        product = session.get(Product.class, 12);
        session.createNativeQuery("delete from Product where id=12")
          .executeUpdate();
        // We need to refresh to fix the error.
        // session.refresh(product);
        session.delete(product);
        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

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

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

  • Держите операции обновления как можно короче
  • Обновляйте представления сущностей в клиенте как можно чаще
  • Не кэшируйте сущность или любой объект значения, представляющий ее
  • Всегда обновляйте представление сущности на клиенте после обновления

10. Заключение

В этой статье мы рассмотрели некоторые распространенные исключения, возникающие при использовании Hibernate. Кроме того, мы исследовали их вероятные причины и решения.

Как обычно, полный исходный код можно найти на GitHub .