不可变值对象和JPA

问题描述:

是否有方法使用JPA映射不可变的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) 

我想用最后的字段和Hibernate作为JPA实现。

你赢了”使用标准的JPA注解和一个可嵌入的对象可以做到这一点,因为必须使用默认构造函数创建对象,并通过反射设置值。

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

+0

非常感谢。它工作得很好。 –

对于JPA能够通过反射创建对象,您必须有一个默认的构造函数,但它不必是公共的。我也喜欢让我的领域保持最终状态,但这可能会限制反思 - 你必须尝试。

我建议丢弃最后一个字段修改和增加私人默认构造一个简短的注释(所以你还是知道为什么无操作构造有下周):

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

    private EmailAddress() { 
     // for JPA 
    } 

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

谢谢。但我希望有更好的一个) –

+0

一个快速测试,我没有让我通过反射设置私人最终字段 - 所以你可以有'私人最终的字符串值;'在默认的构造函数做'this.value = null' - - 但Hibernate文档建议保留最终修饰符(也适用于类)以启用代理/延迟加载:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/persistent -classes.html#persistent-classes-pojo-final –

+0

我认为这将是很好的快速解决方案。但我更喜欢显式粘合剂来进行EE开发。 –

也许最简单的解决方案是使用像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; 
} 
} 

更详细的解释这段代码你可以找到here

+0

谢谢。我会看看。 –