Ленивая загрузка свойства и проблема session.get - PullRequest
1 голос
/ 15 сентября 2008

В Hibernate у нас есть два класса со следующими классами с отображением JPA:

package com.example.hibernate

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Foo {
  private long id;
  private Bar bar;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }

  @ManyToOne(fetch = FetchType.LAZY)
  public Bar getBar() {
    return bar;
  }

  public void setBar(Bar bar) {
    this.bar = bar;
  }
}

package com.example.hibernate

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


public class Bar {
  private long id;
  private String title;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }


  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  } 
}

Теперь, когда мы загружаем из базы данных объект из класса Foo, используя сессию, получаем, например,:

Foo foo = (Foo) session.get (Foo.class, 1 / * или некоторый другой идентификатор, который существует в БД * /); Член Bar в foo - это прокси-объект (в нашем случае javassist proxy, но он может быть cglib в зависимости от используемого вами поставщика байт-кода), который не инициализируется. Если затем вы используете session.get для извлечения объекта Bar, который является членом только что загруженного класса Foo (мы находимся в том же сеансе), Hibernate не выполняет другой запрос к БД и извлекает объект из кэша сеанса (первого уровня) , Проблема в том, что это прокси для класса Bar, который не инициализирован, и попытка вызвать этот объект getId () вернет 0, а getTitle () вернет ноль. Наше текущее решение довольно уродливо и проверяет, является ли объект, возвращаемый из get, прокси-сервером, вот код (формирует общую реализацию DAO):

@SuppressWarnings("unchecked")
@Override
@Transactional(readOnly = true)
public <T extends IEntity> T get(Class<T> clazz, Serializable primaryKey) throws DataAccessException {
  T entity = (T) currentSession().get(clazz, primaryKey);
  if (entity != null) {
    if (LOG.isWarnEnabled()) {
      LOG.warn("Object not found for class " + clazz.getName() + " with primary key " + primaryKey);
    }
  } else if (entity instanceof HibernateProxy){ // TODO: force initialization due to Hibernate bug
    HibernateProxy proxy = (HibernateProxy)entity;
    if (!Hibernate.isInitialized(proxy)) {
      Hibernate.initialize(proxy);
    }
    entity = (T)proxy.getHibernateLazyInitializer().getImplementation();
  }
  return entity;
}

Есть ли лучший способ сделать это, не смог найти решение на форуме Hibernate и не нашел проблему в JIRA Hibernate.

Примечание: мы не можем просто использовать foo.getBar () (которая будет правильно инициализировать прокси), чтобы получить объект класса Bar, потому что операция session.get для извлечения объекта Bar не знает (или заботится об этом) что класс Bar также является ленивым членом объекта Foo, который был только что получен.

Ответы [ 5 ]

2 голосов
/ 23 сентября 2008

У меня была похожая проблема:

  • Я сделал Session.save (nastyItem), чтобы сохранить объект в сеансе. Тем не менее, я не заполнил покупателя свойства, который отображается как update = "false" insert = "false" (это часто случается, когда у вас есть составной первичный ключ, тогда вы сопоставляете многие-к-одному как insert = " false "update =" false ")
  • Я запросил загрузку списка элементов, и элемент, который я только что сохранил, оказался частью набора результатов
  • что теперь не так? Hibernate видит, что элемент уже был в кеше, и Hibernate не заменяет (возможно, чтобы не сломать мою предыдущую ссылку nastyItem) его новое загруженное значение, но использует MY nastyItem, который я сам поместил в кэш сеанса. Хуже того, теперь ленивая загрузка покупателя прервана: она содержит ноль.

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

0 голосов
/ 07 октября 2008

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

Hibernate должен обнаруживать автоматически всякий раз, когда ему нужно извлечь или записать в БД. Если вы запускаете getProperty () из прокси-сервера, hibernate или любой другой поставщик jpa должен получить соответствующую строку из базы данных.

Единственная ситуация, в которой я не уверен, что hibernate является умной достаточно, если вы выполните save (), а затем выполните get () с идентификатором сохраненного объекта, может возникнуть проблема save () не сбрасывал объект в db.

0 голосов
/ 15 сентября 2008

Я не могу воспроизвести поведение, которое вы видите. Вот мой код:

@Entity
public class Foo {
    private Long id; private String name; private Bar bar;

    public Foo() { }
    public Foo(String name) { this.name = name; }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    @Basic
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @ManyToOne(fetch = FetchType.LAZY)
    public Bar getBar() { return bar; }
    public void setBar(Bar bar) { this.bar = bar; }
}

@Entity
public class Bar {
    private Long id; private String name;

    public Bar() { }
    public Bar(String name) { this.name = name; }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    @Basic
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

    public void testGets() {
        SessionFactory sf = new AnnotationConfiguration()
            .addPackage("hibtest")
                .addAnnotatedClass(Foo.class)
                .addAnnotatedClass(Bar.class)
            .configure().buildSessionFactory();
        Session session = null;
        Transaction txn = null;

        // Create needed data
        try {
            session = sf.openSession();
            txn = session.beginTransaction();

            // Create a Bar
            Bar bar = new Bar("Test Bar");
            session.save(bar);

            // Create a Foo
            Foo foo = new Foo("Test Foo");
            session.save(foo);

            foo.setBar(bar);

            txn.commit();
        } catch (HibernateException ex) {
            if (txn != null) txn.rollback();
            throw ex;
        } finally {
            if (session != null) session.close();
        }

        // Try the fetch
        try {
            session = sf.openSession();
            Foo foo = (Foo) session.get(Foo.class, 1L);
            Bar bar = (Bar) session.get(Bar.class, 1L);
            System.out.println(bar.getName());
        } finally {
            if (session != null) session.close();
        }
    }

И все работает нормально, как и следовало ожидать.

0 голосов
/ 15 сентября 2008

Вам действительно нужно делать ленивую загрузку?
Не могли бы вы вместо этого установить FetchType в EAGER, чтобы он всегда загружался (правильно) с помощью объединения?

0 голосов
/ 15 сентября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...