【问题标题】:Immutable Value objects and JPA不可变值对象和 JPA
【发布时间】:2011-11-05 13:07:54
【问题描述】:

有没有办法使用 JPA 映射不可变的 Value 对象,例如电子邮件地址?

@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)

我想使用 final 字段和休眠作为 JPA 实现。

【问题讨论】:

    标签: java hibernate jpa domain-driven-design final


    【解决方案1】:

    可能适用于 Hibernate 3.5 等较旧版本的最简单的解决方案是实现 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;
     }
    }
    

    此代码的详细说明您可以找到here

    【讨论】:

      【解决方案2】:

      使用标准 JPA 注释和可嵌入对象将无法做到这一点,因为必须使用默认构造函数创建对象,并通过反射设置值。

      但是,您可以使用 Hibernate 自定义类型。阅读this part of the Hibernate reference documentation,其中有一个示例Money 类型,它使用带参数的构造函数进行实例化,因此可以是不可变的。

      【讨论】:

        【解决方案3】:

        为了让 JPA 能够通过反射创建对象,您必须有一个默认构造函数,但它不必是公共的。我也喜欢让我的字段保持最终状态,但这可能对反思过于严格——你必须尝试一下。

        我建议删除 final 字段修饰符并添加一个带有简短注释的私有默认构造函数(这样你仍然知道为什么下周会有那个无操作构造函数):

        public final class EmailAddress {
            private String value; // no final modifier
        
            private EmailAddress() {
                // for JPA
            }
        
            public EmailAddress(String value) {
                this.value = value;
            }
        ...
        }
        

        【讨论】:

        • 谢谢。但我希望有更好的)
        • 我做了一个快速测试,让我通过反射设置私有 final 字段——所以你可以有 private final String value; 并在默认构造函数中做 this.value = null——但 Hibernate 文档建议继续使用final 修饰符(也用于类)以启用代理/延迟加载:docs.jboss.org/hibernate/core/3.6/reference/en-US/html/…
        • 我认为这将是一个不错的快速解决方案。但我更喜欢用于 EE 开发的显式绑定器。
        猜你喜欢
        • 2011-03-14
        • 1970-01-01
        • 1970-01-01
        • 2020-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多