Неизменные значения объектов и JPA - PullRequest
15 голосов
/ 28 августа 2011

Есть ли способ сопоставить неизменяемые объекты Value, такие как адрес электронной почты, с использованием JPA?

@Immutable
@Embeddable
public final class EmailAddress {
    private final String value;

    public EmailAddress(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        EmailAddress that = (EmailAddress) o;
        return value.equals(that.value);
    }

    @Override
    public int hashCode() {
        return value.hashCode();
    }
}

Теперь я получаю исключение при сохранении объекта

org.hibernate.InstantiationException: No default constructor for entity: com.domain.EmailAddress
    org.hibernate.tuple.PojoInstantiator.instantiate(PojoInstantiator.java:107)
    org.hibernate.tuple.component.AbstractComponentTuplizer.instantiate(AbstractComponentTuplizer.java:102)
    org.hibernate.type.ComponentType.instantiate(ComponentType.java:515)
    org.hibernate.type.ComponentType.deepCopy(ComponentType.java:434)
    org.hibernate.type.TypeHelper.deepCopy(TypeHelper.java:68)
    org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:302)
    org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
    org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129)
    org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:69)
    org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:179)
    org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
    org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
    org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:808)
    org.hibernate.impl.SessionImpl.persist(SessionImpl.java:782)
    org.hibernate.impl.SessionImpl.persist(SessionImpl.java:786)
    org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:672)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    java.lang.reflect.Method.invoke(Method.java:597)
    org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:240)
    $Proxy25.persist(Unknown Source)
    org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:360)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    java.lang.reflect.Method.invoke(Method.java:597)
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:368)
    org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:349)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    $Proxy26.save(Unknown Source)
    com.controller.UserController.create(UserController.java:64)

Я хочу использовать конечные поля и спящий режим какРеализация JPA.

Ответы [ 3 ]

13 голосов
/ 28 августа 2011

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

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

7 голосов
/ 24 октября 2011

Вероятно, самое простое решение, которое работает с немного более старыми версиями, такими как 3.5 Hibernate, - это реализовать org.hibernate.usertype.UserType.В нем довольно много методов, но для неизменяемых типов вы можете извлечь большинство из них в общий суперкласс:

package com.acme;

import java.io.Serializable;

import org.hibernate.usertype.UserType;

public abstract class AbstractImmutableType
  implements UserType {

 public AbstractImmutableType() {
  super();
 }

 public boolean isMutable() {
  return false;
 }

 public Serializable disassemble(Object value) {
  return (Serializable) value;
 }

 public Object assemble(Serializable cached, Object owner) {
  return cached;
 }

 public Object deepCopy(Object value) {
  return value;
 }

 public Object replace(Object original, Object target,
   Object owner) {
  return original;
 }

 public boolean equals(Object x, Object y) {
  if (x != null && y != null) {
   return x.equals(y);
  }
  // Two nulls are equal as well
  return x == null && y == null;
 }

 public int hashCode(Object x) {
  if (x != null) {
   return x.hashCode();
  }
  return 0;
 }
}

И вы можете использовать его так:

package com.acme;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

public class CurrencyType extends AbstractImmutableType {

 public static final String TYPE = "com.acme.CurrencyType";

 private static final int[] SQL_TYPES = {
    Types.VARCHAR
 };

 public CurrencyType() {
  super();
 }

 public Object nullSafeGet(ResultSet rs, String[] names,
    Object owner) throws SQLException {
  String value = rs.getString(names[0]);
  if (rs.wasNull()) {
   return null;
  }
  return Currency.valueOf(value);
 }

 public void nullSafeSet(PreparedStatement st, Object value,
    int index) throws SQLException {
  if (value != null) {
   st.setString(index, ((Currency)value).getCode());
  } else {
   st.setNull(index, SQL_TYPES[0]);
  }
 }

 public Class<?> returnedClass() {
  return Currency.class;
 }

 public int[] sqlTypes() {
  return SQL_TYPES;
 }
}

Более подробное объяснениедля этого кода вы можете найти здесь

3 голосов
/ 28 августа 2011

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

Я бы предложил сбросить модификатор final и добавить частный конструктор по умолчанию с короткимкомментарий (так что вы все еще знаете, почему этот конструктор без операции появится на следующей неделе):

public final class EmailAddress {
    private String value; // no final modifier

    private EmailAddress() {
        // for JPA
    }

    public EmailAddress(String value) {
        this.value = value;
    }
...
}
...