不可变值对象和 JPA

发布于 2024-12-02 04:59:19 字数 4187 浏览 2 评论 0原文

有没有办法使用 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 实现。

Is there way to map immutable Value objects like email address using 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();
    }
}

Now I get exception on entity save

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)

I want use final fields and hibernate as JPA implementation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

追风人 2024-12-09 04:59:20

可能适用于较旧版本(例如 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;
 }
}

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

Probably the easiest solution which works with a little older versions like 3.5 of Hibernate is to implement org.hibernate.usertype.UserType.
There is quite a few methods in it but for immutable types you can extract most of them to common superclass:

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;
 }
}

And you can use it like this:

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;
 }
}

Longer explanation for this code you can find here

雨轻弹 2024-12-09 04:59:20

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

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

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

    private EmailAddress() {
        // for JPA
    }

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

For JPA to be able to create objects via reflection, you have to have a default constructor, but it doesn't have to be public. I also like to keep my fields final, but this might be too restrictive for reflection -- you'll have to try.

I'd suggest dropping the final field modifier and adding a private default constructor with a short comment (so you still know why that no-op constructor is there next week):

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

    private EmailAddress() {
        // for JPA
    }

    public EmailAddress(String value) {
        this.value = value;
    }
...
}
写下不归期 2024-12-09 04:59:19

您将无法使用标准 JPA 注释和可嵌入对象来执行此操作,因为必须使用默认构造函数创建该对象,并通过反射设置值。

不过,您可以使用 Hibernate 自定义类型。阅读Hibernate 参考文档的这一部分,其中有一个示例 Money 类型,它是使用带参数的构造函数实例化的,因此可以是不可变的。

You won't be able to do that using standard JPA annotations and an embeddable object, because the object will have to be created using a default constructor, and the value set via reflection.

You could however use a Hibernate custom type. Read this part of the Hibernate reference documentation, where there is an example Money type, which is instantiated using a constructor with arguments, and could thus be immutable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文