不可变值对象和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
类型,该类型使用带参数的构造函数实例化,因此可以是不可变的。
对于JPA能够通过反射创建对象,您必须有一个默认的构造函数,但它不必是公共的。我也喜欢让我的领域保持最终状态,但这可能会限制反思 - 你必须尝试。
我建议丢弃最后一个字段修改和增加私人默认构造一个简短的注释(所以你还是知道为什么无操作构造有下周):
public final class EmailAddress {
private String value; // no final modifier
private EmailAddress() {
// for JPA
}
public EmailAddress(String value) {
this.value = value;
}
...
}
谢谢。但我希望有更好的一个) –
一个快速测试,我没有让我通过反射设置私人最终字段 - 所以你可以有'私人最终的字符串值;'在默认的构造函数做'this.value = null' - - 但Hibernate文档建议保留最终修饰符(也适用于类)以启用代理/延迟加载:http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/persistent -classes.html#persistent-classes-pojo-final –
我认为这将是很好的快速解决方案。但我更喜欢显式粘合剂来进行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
谢谢。我会看看。 –
非常感谢。它工作得很好。 –