Почему одна и та же запись базы данных представлена несколькими экземплярами JPA bean?

Сегодня я столкнулся с неожиданным поведением EclipseLink. (Я не знаю, связано ли это с EclipseLink или это то же самое для всех провайдеров JPA.)

Я предположил, что извлечения управляемого Боба JPA всегда возвращают ссылки на один и тот же экземпляр объекта при выполнении внутри той же транзакции (используя тот же EntityManager).

Если это верно, я не знаю, почему я получаю ошибку при выполнении следующего тестового набора:

@Test
public void test_1() {
  EntityManager em = newEntityManager();
  em.getTransaction().begin();

  // Given:
  Product prod = newProduct();

  // When:
  em.persist(prod);
  em.flush();      
  Product actual =
    em.createQuery("SELECT x from Product x where x.id = " 
    + prod.getId(), Product.class).getSingleResult();

  // Then:
  assertThat(actual).isSameAs(prod); // <-- FAILS

  em.getTransaction().commit();
}

Оператор, помеченный «FAILS», выдает следующее AssertionError:

java.lang.AssertionError: 
Expecting:
  <demo.Product@35dece42>
and actual:
  <demo.Product@385dfb63>
to refer to the same object

Интересно, что следующий слегка измененный тест успешно:

@Test
public void test_2() {
  EntityManager em = newEntityManager();
  em.getTransaction().begin();

  // Given:
  Product prod = newProduct();

  // When:
  em.persist(prod);
  em.flush();      
  Product actual = em.find(Product.class, prod.getId());

  // Then:
  assertThat(actual).isSameAs(prod); // <-- SUCCEEDS

  em.getTransaction().commit();
}

Очевидно, что существует разница между поиском и запросом объектов.

Это ожидаемое поведение? И почему?

—Редактировать—

Я думаю, что нашел источник проблемы: Productимеет идентификатор типа ProductId.

Вот соответствующий код:

@Entity
@Table(name = "PRODUCT")
public class Product implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @Column(name = "ID", nullable = false)
  @Converter(name = "productIdConverter", converterClass = ProductIdConverter.class)
  @Convert("productIdConverter")
  private ProductId id;

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

[...]
}

Примечания @Convertand @Converterявляются специфичными для EclipseLink.
В отличие от конвертеров JPA 2.1 их можно размещать в полях идентификаторов.

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

Я думаю, что я должен подать ошибку для этого.

Метки

1 ответ

  1. Я нашел причину проблемы и решение.

    Мы используем пользовательский ID класс (ProductId) дляProduct, вместе с пользовательским (eclipselink-specific) конвертер-класс ProductIdConverter, который имеет плохую реализацию convertObjectValueToDataValue(...)метода.

    Вот соответствующий код:

      /**
       * Convert the object's representation of the value to the databases' data representation.
       */
      @Override
      public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
        if (objectValue == null) {
          return null;
        }    
        Long longValue = ((ProductId) objectValue).getLong();
        return longValue;
      }
    

    Обратите внимание, что метод возвращает Longэкземпляры (или null).

    Но так как мы используем Oracle в качестве серверной части базы данных и объявили столбец идентификатора продукта as NUMBER, драйвер JDBC отображает значение столбца as BigDecimal. Это означает, что мы должны убедиться, что наши convertObjectValueToDataValue(...)также возвращает BigDecimalэкземпляры.

    Поэтому правильная реализация:

      /**
       * Convert the object's representation of the value to the databases' data representation.
       */
      @Override
      public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
        if (objectValue == null) {
          return null;
        }    
        Long longValue = ((ProductId) objectValue).getLong();
        return BigDecimal.valueOf(longValue);
      }
    

    Теперь этот метод возвращает только BigDecimalэкземпляры.