Hibernate несовместим с SparkJava? - PullRequest
3 голосов
/ 30 марта 2019

У меня ошибка при использовании Hibernate с SparkJava в режиме отложенной загрузки.

Он работает правильно без SparkJava, но при использовании SparkJava пытается принудительно загрузить загрузку для отношения OneToMany.

- Модель

@Entity
@Table(name = "KU_SUPPLIER")
public class Supplier {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @NotEmpty(message = "Please provide a name")
    private String name;

    @OneToMany(mappedBy = "supplier")
    private List<Item> items;  // Should be lazy-loaded

    // Constructor / Getters / Setters
}

- DAO

public class SupplierDao implements Dao<Supplier> {

    private final SessionFactory sessionFactory;

    public SupplierDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    @SuppressWarnings("unchecked")
    public List<Supplier> findAll() {
        try (Session session = sessionFactory.openSession()) {
            return session.createQuery("FROM com.seafrigousa.model.Supplier").getResultList();
        }
    }
}

- Основной

// Working perfectly and lazy-load Items as desired    
supplierDao.findAll();

// The method will be called when a web browser goes to "localhost/suppliers"
// It throws org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: model.Supplier.items, could not initialize proxy - no Session
get("/suppliers", "application/json", supplierDao::findAll);

Я проверил, не закрывая сеанс из DAO, и увидел, что Hibernate выполнял запрос, как если бы он находился в режиме загрузки EAGER, поэтому он выполнял два выбора, один для поставщика и один для элемента.

Есть ли причина для такого поведения?

Спасибо!

1 Ответ

2 голосов
/ 01 апреля 2019

Я думаю, что здесь: get("/suppliers", "application/json", supplierDao::findAll); Вы сериализуете объект поставщика в JSON. Поле Items не помечено как исключенное из сериализации, поэтому получение его значения вызывает ленивую инициализацию вне сеанса (или избыточный и второй запрос для элементов, если сеанс не закрыт).

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

session.createQuery("FROM com.seafrigousa.model.Supplier s join fetch s.items").getResultList();

Используя gson в качестве сериализатора, у вас есть следующие опции:

  1. @Expose аннотация к полям, которые вы хотите сериализовать.

    @Entity
    @Table(name = "KU_SUPPLIER")
    public class Supplier {
    
        @Expose
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        @Expose
        @NotEmpty(message = "Please provide a name")
        private String name;
    
        @OneToMany(mappedBy = "supplier")
        private List<Item> items;  // Should be lazy-loaded
    
        // Constructor / Getters / Setters
    }
    

    со следующим инициированием gson

    Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    
  2. ExclusionStrategy с пользовательской аннотацией, например,

    public class IgnoreFieldExclusionStrategy implements ExclusionStrategy {
    
        @Override
        public boolean shouldSkipField(FieldAttributes fieldAttributes) {
            return fieldAttributes.getAnnotation(GsonIgnore.class) != null;
        }
    
        @Override
        public boolean shouldSkipClass(Class<?> aClass) {
            return false;
        }
    }
    

    с пользовательской аннотацией @GsonIgnore

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface GsonIgnore {}
    

    и инициация gson

    Gson gson = new GsonBuilder().addSerializationExclusionStrategy(new IgnoreFieldExclusionStrategy()).create();
    

    ваш класс будет выглядеть так

    @Entity
    @Table(name = "KU_SUPPLIER")
    public class Supplier {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private int id;
    
        @NotEmpty(message = "Please provide a name")
        private String name;
    
        @GsonIgnore
        @OneToMany(mappedBy = "supplier")
        private List<Item> items;  // Should be lazy-loaded
    
        // Constructor / Getters / Setters
    }
    

Если вам нужно было бы сериализовать Supplier с items в другом API, вы можете создать объект DTO для Supplier и сопоставить его с результатами, подобными этим:

package com.seafrigousa.dto

public class SupplierDTO {

    private int id;
    private String name;

    public SupplierDTO(int id, String name) {
        this.id = id;
        this.name = name;
   }

    // Getters / Setters
}

и запрос:

session.createQuery("select new com.seafrigousa.dto.SupplierDTO(s.id, s.name) FROM com.seafrigousa.model.Supplier s").getResultList();
...