Hibernate Lazy-Loading в среде Swing - PullRequest
       14

Hibernate Lazy-Loading в среде Swing

0 голосов
/ 04 октября 2018

Пару недель я занимался моим хобби-проектом, который, по сути, является помощником для викторины.После теста я собираюсь проверить, какие вопросы и ответы я получил правильно, введя часть текста вопроса и получив категорию (например, химия), вопросы, связанные с моей очередью поиска (например, NaCl,ДНК, pH) и ответы, связанные с этими вопросами.

Все они хранятся в базе данных MySQL следующим образом: Схема базы данных .

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

Учитывая следующий простой код:

Main.java

import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import org.mybizname.quizassistant.database.dao.impl.CategoryDaoImpl;
import org.mybizname.quizassistant.database.entity.impl.CategoryEntityImpl;
import org.mybizname.quizassistant.database.entity.impl.QuestionEntityImpl;
import org.mybizname.quizassistant.database.utilities.HibernateUtil;

public class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

                List<CategoryEntityImpl> categories = new CategoryDaoImpl().fetchAll();

                categories.forEach((category) -> {
                    Set<QuestionEntityImpl> questions = category.getQuestions();
                });

                HibernateUtil.shutdown();

                System.exit(0);
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
            }
        });
    }

}

HibernateUtil.java

import java.util.HashMap;
import java.util.Map;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Environment;
import org.mybizname.quizassistant.database.entity.impl.AnswerEntityImpl;
import org.mybizname.quizassistant.database.entity.impl.CategoryEntityImpl;
import org.mybizname.quizassistant.database.entity.impl.QuestionEntityImpl;

public class HibernateUtil {

    private static StandardServiceRegistry standardServiceRegistry;
    private static SessionFactory          sessionFactory;

    static {
        StandardServiceRegistryBuilder standardServiceRegistryBuilder = new StandardServiceRegistryBuilder();
        Map<String, String>            hibernateConfiguration         = new HashMap<>();

        hibernateConfiguration.put(Environment.DIALECT,                       "org.hibernate.dialect.MySQLDialect");
        hibernateConfiguration.put(Environment.DRIVER,                        "com.mysql.jdbc.Driver");
        hibernateConfiguration.put(Environment.URL,                           "jdbc:mysql://localhost:3306/quiz_assistant");
        hibernateConfiguration.put(Environment.USER,                          "root");
        hibernateConfiguration.put(Environment.PASS,                          "pwd");
        hibernateConfiguration.put(Environment.QUERY_TRANSLATOR,              "org.hibernate.hql.internal.classic.ClassicQueryTranslatorFactory");
        hibernateConfiguration.put(Environment.CURRENT_SESSION_CONTEXT_CLASS, "org.hibernate.context.internal.ThreadLocalSessionContext");
        hibernateConfiguration.put(Environment.SHOW_SQL,                      "true");
        hibernateConfiguration.put(Environment.FORMAT_SQL,                    "true");

        standardServiceRegistryBuilder.applySettings(hibernateConfiguration);

        standardServiceRegistry = standardServiceRegistryBuilder.build();

        MetadataSources metadataSources = new MetadataSources(standardServiceRegistry);

        metadataSources.addAnnotatedClass(CategoryEntityImpl.class);
        metadataSources.addAnnotatedClass(QuestionEntityImpl.class);
        metadataSources.addAnnotatedClass(AnswerEntityImpl.class);

        Metadata metadata = metadataSources.getMetadataBuilder().build();

        sessionFactory = metadata.getSessionFactoryBuilder().build();
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static Session openSession() {
        return sessionFactory.openSession();
    }

    public static Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    public static void shutdown() {
        if (sessionFactory != null) {
            StandardServiceRegistryBuilder.destroy(standardServiceRegistry);
        }
    }

}

GenericEntity.java

import java.io.Serializable;
import java.util.Date;

public interface GenericEntity extends Serializable {

    public Long getId();

    public void setId(Long id);

    public Date getRegistrationTimestamp();

    public void setRegistrationTimestamp(Date registrationTimestamp);

}

AbstractGenericEntity.java

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

public abstract class AbstractGenericEntity implements GenericEntity {

    @Override
    abstract public Long getId();

    @Override
    abstract public void setId(Long id);

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "registrationTimestamp", length = 19)
    protected Date registrationTimestamp;

    @Override
    public Date getRegistrationTimestamp() {
        return this.registrationTimestamp;
    }

    @Override
    public void setRegistrationTimestamp(Date registrationTimestamp) {
        this.registrationTimestamp = registrationTimestamp;
    }

}

GenericDao.java

import java.util.List;

public interface GenericDao<T> {

    public void persist(T entity);

    public void update(T entity);

    public void remove(T entity);

    public void removeAll();

    public List<T> fetchAll();

    public List<T> fetchAllByExample(T entity);

    public T findById(Long id);

    public Class<T> getType();

}

AbstractGenericDao.java

import java.lang.reflect.ParameterizedType;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.mybizname.quizassistant.database.utilities.HibernateUtil;

public abstract class AbstractGenericDao<T> implements GenericDao<T> {

    private final Class<T> type;

    public AbstractGenericDao() {
        type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    @Override
    public void persist(T entity) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void update(T entity) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void remove(T entity) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public void removeAll() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public List<T> fetchAll() {
        List<T> entities;

        Session          session         = HibernateUtil.getCurrentSession();
        Transaction      transaction     = session.beginTransaction();
        CriteriaBuilder  criteriaBuilder = session.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery   = criteriaBuilder.createQuery(getType());

        criteriaQuery.from(getType());

        entities = session.createQuery(criteriaQuery).getResultList();

        transaction.commit();

        return entities;
    }

    @Override
    public List<T> fetchAllByExample(T entity) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public T findById(Long id) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public Class<T> getType() {
        return type;
    }

}

Категория EntityImpl

import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.mybizname.quizassistant.database.entity.AbstractGenericEntity;

@Entity
@Table(name = "category", catalog = "quiz_assistant", uniqueConstraints = @UniqueConstraint(columnNames = "name"))
public class CategoryEntityImpl extends AbstractGenericEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    @Column(name = "name", unique = true, nullable = false)
    private String name;

    @Column(name = "shortDescription")
    private String shortDescription;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "category")
    private Set<QuestionEntityImpl> questions = new HashSet<>();

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "category")
    private Set<AnswerEntityImpl> answers = new HashSet<>();

    public CategoryEntityImpl() {

    }

    public CategoryEntityImpl(String name, Date registrationTimestamp) {
        this.name                  = name;
        this.registrationTimestamp = registrationTimestamp;
    }

    public CategoryEntityImpl(String name, String shortDescription, Date registrationTimestamp, Set<QuestionEntityImpl> questions, Set<AnswerEntityImpl> answers) {
        this.name                  = name;
        this.shortDescription      = shortDescription;
        this.registrationTimestamp = registrationTimestamp;
        this.questions             = questions;
        this.answers               = answers;
    }

    @Override
    public Long getId() {
        return this.id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getShortDescription() {
        return this.shortDescription;
    }

    public void setShortDescription(String shortDescription) {
        this.shortDescription = shortDescription;
    }

    public Set<QuestionEntityImpl> getQuestions() {
        return this.questions;
    }

    public void setQuestions(Set<QuestionEntityImpl> questions) {
        this.questions = questions;
    }

    public Set<AnswerEntityImpl> getAnswers() {
        return this.answers;
    }

    public void setAnswers(Set<AnswerEntityImpl> answers) {
        this.answers = answers;
    }

}

QuestionEntityImpl

import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.mybizname.quizassistant.database.entity.AbstractGenericEntity;

@Entity
@Table(name = "question", catalog = "quiz_assistant")
public class QuestionEntityImpl extends AbstractGenericEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "categoryId", nullable = false)
    private CategoryEntityImpl category;

    @Column(name = "identifier", nullable = false)
    private String identifier;

    @Column(name = "text", nullable = false)
    private String text;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
    private Set<AnswerEntityImpl> answers = new HashSet<>();

    public QuestionEntityImpl() {

    }

    public QuestionEntityImpl(CategoryEntityImpl category, String identifier, String text, Date registrationTimestamp) {
        this.category              = category;
        this.identifier            = identifier;
        this.text                  = text;
        this.registrationTimestamp = registrationTimestamp;
    }

    public QuestionEntityImpl(CategoryEntityImpl category, String identifier, String text, Date registrationTimestamp, Set<AnswerEntityImpl> answers) {
        this.category              = category;
        this.identifier            = identifier;
        this.text                  = text;
        this.registrationTimestamp = registrationTimestamp;
        this.answers               = answers;
    }

    @Override
    public Long getId() {
        return this.id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    public CategoryEntityImpl getCategory() {
        return this.category;
    }

    public void setCategory(CategoryEntityImpl category) {
        this.category = category;
    }

    public String getIdentifier() {
        return this.identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public String getText() {
        return this.text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public Set<AnswerEntityImpl> getAnswers() {
        return this.answers;
    }

    public void setAnswers(Set<AnswerEntityImpl> answers) {
        this.answers = answers;
    }

}

AnswerEntityImpl

import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.mybizname.quizassistant.database.entity.AbstractGenericEntity;

@Entity
@Table(name = "answer", catalog = "quiz_assistant")
public class AnswerEntityImpl extends AbstractGenericEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "categoryId", nullable = false)
    private CategoryEntityImpl category;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "questionId", nullable = false)
    private QuestionEntityImpl question;

    @Column(name = "identifier", nullable = false)
    private String identifier;

    @Column(name = "text", nullable = false)
    private String text;

    public AnswerEntityImpl() {

    }

    public AnswerEntityImpl(CategoryEntityImpl category, QuestionEntityImpl question, String identifier, String text, Date registrationTimestamp) {
        this.category              = category;
        this.question              = question;
        this.identifier            = identifier;
        this.text                  = text;
        this.registrationTimestamp = registrationTimestamp;
    }

    @Override
    public Long getId() {
        return this.id;
    }

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    public CategoryEntityImpl getCategory() {
        return this.category;
    }

    public void setCategory(CategoryEntityImpl category) {
        this.category = category;
    }

    public QuestionEntityImpl getQuestion() {
        return this.question;
    }

    public void setQuestion(QuestionEntityImpl question) {
        this.question = question;
    }

    public String getIdentifier() {
        return this.identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public String getText() {
        return this.text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

Зависимости POM:

<dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.3.6.Final</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.12</version>
    </dependency>
</dependencies>

Есть еще CategoryDaoImpl.java , QuestionDaoImpl.java и AnswerDaoImpl.java , которые просто пустыи просто расширяем AbstractGenericDao.java .

Теперь, в Main.java , я получаю список категорий из базы данных:

List<CategoryEntityImpl> categories = new CategoryDaoImpl().fetchAll();

А потом, когда яя пытаюсь перебрать их через:

categories.forEach((category) -> {
    Set<QuestionEntityImpl> questions = category.getQuestions();
});

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

Exception in thread "AWT-EventQueue-0" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.mybizname.quizassistant.database.entity.impl.CategoryEntityImpl.questions, could not initialize proxy - no Session

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

    categories.forEach((category) -> {
            System.out.println(category.getName());

            Session session = HibernateUtil.getCurrentSession();
            Transaction tx = session.beginTransaction();
            session.lock(category, LockMode.NONE);
            Set<QuestionEntityImpl> questions = category.getQuestions();
            questions.forEach((question) -> {
                System.out.println("\t" + question.getText());
            });
            tx.commit();
        });

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

Некоторая помощьбыть оцененнымЗаранее спасибо.

...