【问题标题】:Trim string field in JPA修剪 JPA 中的字符串字段
【发布时间】:2011-08-09 04:33:32
【问题描述】:

我有一个数据类型为 char(20) 的列的 db 表。我不允许将其更改为 varchar。

我正在编写一个映射到该表的 JPA 实体。我希望在我的实体类中表示此列的字符串字段始终包含修剪后的值,而不是 db 中存在的用空格填充的 20 个字符的值。

我看不出有什么简单的方法可以做到这一点。 (注释会摇滚!)。目前我只是从我的 getter() 返回一个修剪后的值,但这感觉像是一个杂物。

谷歌搜索对此没有任何帮助。有什么想法吗?

【问题讨论】:

  • 只是想知道,如果您使用@ColumnDefinition 并将列定义为VARCHAR,即使它是CHAR,会发生什么。结果很可能是相同的或错误的。
  • 注释@ColumnDefinition 仅在生成数据库模式时有用。如果您不介意使用纯 Hibernate 并偏离 JPA 标准,您可以使用 Hibernate @ColumnTransformer,前提是您有一个数据库函数来完成工作您可以在 Hibernate 参考中找到如何做到这一点:docs.jboss.org/hibernate/core/3.6/reference/en-US/html/…
  • 我看不出您的原始解决方案有什么问题。您当前的方法是正确的。正如您在建议的答案中看到的那样,它不会比这更简单。唯一更好的解决方案是:将数据库字段更改为VARCHAR,您说不能。
  • @edalorzo 如果我们谈论一两个属性,我同意。如果这是一种出现在整个大型代码库中的模式,那么使用专用侦听器会更可靠
  • @sean patrick floyd 是的,它出现在整个大型代码库中。

标签: java jpa


【解决方案1】:

或者你可以使用生命周期注解:

@Entity
public class MyEntity {

    @PostLoad
    protected void repair(){
        if(myStringProperty!=null)myStringProperty=myStringProperty.trim();
    }

    private String myStringProperty;
    public String getMyStringProperty() {
        return myStringProperty;
    }
    public void setMyStringProperty(String myStringProperty) {
        this.myStringProperty = myStringProperty;
    }

}

如果这发生在多个实体上,您可以创建自定义注释并编写专用的 EntityListener。

注释

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Trim {}

听众

public class TrimListener {

    private final Map<Class<?>, Set<Field>> trimProperties = 
        new HashMap<Class<?>, Set<Field>>();

    @PostLoad
    public void repairAfterLoad(final Object entity) throws Exception {
        for (final Field fieldToTrim : getTrimProperties(entity.getClass())) {
            final String propertyValue = (String) fieldToTrim.get(entity);
            if (propertyValue != null)
                fieldToTrim.set(entity, propertyValue.trim());
        }
    }

    private Set<Field> getTrimProperties(Class<?> entityClass) throws Exception {
        if (Object.class.equals(entityClass))
            return Collections.emptySet();
        Set<Field> propertiesToTrim = trimProperties.get(entityClass);
        if (propertiesToTrim == null) {
            propertiesToTrim = new HashSet<Field>();
            for (final Field field : entityClass.getDeclaredFields()) {
                if (field.getType().equals(String.class)
                    && field.getAnnotation(Trim.class) != null) {
                    field.setAccessible(true);
                    propertiesToTrim.add(field);
                }
            }
            trimProperties.put(entityClass, propertiesToTrim);
        }
        return propertiesToTrim;
    }

}

现在用@Trim注释所有相关的字符串字段,并将监听器注册为你的persistence.xml中的默认实体监听器:

<persistence-unit ..>
    <!-- ... -->
    <default-entity-listeners>
      com.somepackage.TrimListener
      and.maybe.SomeOtherListener
    </default-entity-listeners>
</persistence-unit>

 

【讨论】:

  • +1 这个答案更相关。提醒我学习如何构造 Annotation 和 EntityListener。
  • 看起来是最好的解决方案,会试一试。 tyvm.
  • @SeanPatrickFloyd (+1) 已经尝试过了,大部分情况下都可以,但是最后一个声明不正确,AFAIK - 无法在持久性单元声明中定义实体侦听器。需要在 orm.xml(或类似文件)中定义监听器,就像 here 解释的那样。
  • 或在实体上使用@EntityListeners(YourListener.class) 注释
  • @kostja 好笑,看来你是对的。我从 EJB3 in Action 一书中获取了它,但我在任何官方文档或 XSD 中都找不到它
【解决方案2】:

接受的答案(使用 JPA 实体侦听器/@Trim 注释)是一个危险的答案。 对检索到的实体调用 setter 似乎会将实体标记为脏。当我自己在根实体级别(使用 Spring3 / hibernate)尝试此操作时,它触发了对相关实体的大量无关更新,否则这些更新在事务期间不会被修改。这在生产中确实是一团糟,追查到这个原因需要时间。

最后,我选择采用更直接的方法,即按需手动修剪每个字段(在自定义实体到域映射器中,或在实体 getter 中),类似于 Edwin 的回答。

【讨论】:

  • 我在我的 JPA 实体上使用@EntityListeners(YourListener.class)@PostLoad 来内化字符串(即myStringField.set(myEntity, ((String)myStringField.get(myEntity)).intern()))。所以我的方法相当于肖恩帕特里克弗洛伊德的解决方案。但我从未见过你提到的“大量无关更新”。
  • @JulienKronegg 您的带注释的 JPA 实体是否与另一个实体相关(可能在一对多关系的“多”端)?还是你总是更新这些带注释的实体?前者是我的场景。因此,尽管我尽了最大努力以其他方式对其进行配置,但所有带注释的子实体都在更新,即使父实体是唯一被更改的实体。在我删除 @Trim 注释的那一刻,这种行为就停止了。
  • 我正在使用这个监听器来内部化大约 300 个 JPA 实体上的字符串,所以当然其中一些是一对多关系的“多”端。但即使在如此大的对象图上,我也从未注意到您提到的“大量无关更新”。由于没有注意到,我没有进一步调查。
【解决方案3】:

这是一个古老的问题,但它对我得到答案非常有用。就我而言,最简单的方法是创建一个简单的“javax.persistence.Converter”,如下所示:

@Converter
public class StringTrimConverter implements AttributeConverter<String, String> {

    @Override
    public String convertToDatabaseColumn(String attribute) {
        return attribute;
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        return dbData.trim();
    }

}

你可以这样使用它:

@Entity
@Table(name = "ViewAddress")
public class PostalAddress extends DbObject { 
    @Convert(converter = StringTrimConverter.class)
    private String street;
    @Convert(converter = StringTrimConverter.class)
    private String number;
    (...)

它工作得很好。

【讨论】:

  • 简单易行的解决方案 :) 谢谢
  • 您的回答帮助了我。除此之外,有时,如果数据库中的值为空。我们会在 Trim 期间导致空指针异常。下面的代码一直有效return dbData!=null ? dbData.trim() : null;
【解决方案4】:

在 getter 方法上添加注解,将 @Acesss 设置为 AccessType.Property 并使用 String.trim() 方法修剪那里的字段。

或者干脆把 trim 放在 getter 方法中,并始终通过它访问字段。没有比这更简单的了。

如果你不介意使用纯 Hibernate 并偏离 JPA 标准,你可以使用 Hibernate @ColumnTransformer,前提是你有一个数据库函数来完成工作

您可以在 Hibernate 参考中找到如何操作:

http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/mapping.html#mapping-column-read-and-write

希望对你有帮助!

【讨论】:

  • 是的——我已经这样做了。我正在使用 OpenJPA,它确实具有类似的功能,但使用特定于供应商的功能对我们来说比使用 getter 解决方法更糟糕。
【解决方案5】:

您使用的是什么 JPA 提供程序?

如果您使用 EclipseLink,CHAR 字段默认会被修剪。您可以通过会话 trimStrings 属性禁用此功能(确保您没有设置此功能)。

【讨论】:

  • 在下面查看我的评论...我正在使用 OpenJPA,它确实具有类似的功能,但使用特定于供应商的功能对我们来说比使用 getter 解决方法更糟糕。
【解决方案6】:

我正在使用这种方法,它使修剪变得透明,而无需在每个字符串字段中使用注释。 在您拥有会话工厂类的同一个包中(用于获取会话的那个,例如org.blablabla.yourpackage.etc.SessionGetter.getSession(),您必须创建一个名为package-info.java 的文件并将此内容放入其中:

@TypeDefs({
        @TypeDef(name = "trimmedStringType",
                defaultForType = String.class,
                typeClass = StringUserType.class)
})
package org.blablabla.yourpackage.etc;

import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;

然后在同一个包中创建类StringUserType

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.usertype.EnhancedUserType;

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

public class StringUserType implements EnhancedUserType, Serializable {

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

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

    @Override
    public Class returnedClass() {
        return String.class;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        if (x == y) {
            return true;
        }
        if (x == null || y == null) {
            return false;
        }
        String dtx = (String) x;
        String dty = (String) y;
        return dtx.equals(dty);
    }

    @Override
    public int hashCode(Object object) throws HibernateException {
        return object.hashCode();
    }


    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] names, SessionImplementor session, Object owner)
            throws HibernateException, SQLException {
        Object s = StandardBasicTypes.STRING.nullSafeGet(resultSet, names, session, owner);
        if (s == null) {
            return null;
        }
        return s.toString().trim();
    }

    @Override
    public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index, SessionImplementor session)
            throws HibernateException, SQLException {
        if (value == null) {
            StandardBasicTypes.STRING.nullSafeSet(preparedStatement, null, index, session);
        } else {
            StandardBasicTypes.STRING.nullSafeSet(preparedStatement, value.toString().trim(), index, session);
        }
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object value) throws HibernateException {
        return cached;
    }

    @Override
    public Object replace(Object original, Object target, Object owner) throws HibernateException {
        return original;
    }

    @Override
    public String objectToSQLString(Object object) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String toXMLString(Object object) {
        return object.toString();
    }

    @Override
    public Object fromXMLString(String string) {
        return string;
    }

}

就是这样,无需在您的 bean 中创建自定义注释,只要您从数据库中获取对象,它就会“神奇地”修剪字符串。

【讨论】:

    【解决方案7】:

    除了在 persistence.xml 中注册侦听器之外,已接受的答案有效。我用 orm.xml 做到了。

    <?xml version="1.0" encoding="UTF-8"?>
    <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm 
    http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
                 version="2.1">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.example.TrimListener">
             </entity-listener>
            </entity-listeners>
        </persistence-unit-defaults>
      </persistence-unit-metadata>
    </entity-mappings>
    

    【讨论】:

      【解决方案8】:

      你所要做的就是把它放在你的控制器上,它会按预期工作,你不需要监听器或任何类似的东西

      @InitBinder
      public void initBinder(WebDataBinder binder) {
          binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
      }
      

      【讨论】:

      • 谢谢这对我不起作用。我用修剪代替。
      【解决方案9】:

      如果您的域要求声明它需要修剪信息,您需要将数据存储在修剪值中。我看不出有什么问题。

      领域模型
      对象模型 包含两种行为的域 和数据。 (PEAA - 马丁福勒)

      如果您明确必须在数据库级别强制执行业务规则,一个选项是您可以选择编写触发器,您可以使用内置的 SQL trim 方法。但这就像用火箭打鸡蛋一样。

      【讨论】:

      • 我认为你误解了我的问题。这些值输入为例如“abc”。因为该字段是 CHAR 而不是 VARCHAR,所以值被返回为例如“abc”。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-26
      • 1970-01-01
      • 1970-01-01
      • 2017-10-16
      • 2022-01-01
      • 1970-01-01
      相关资源
      最近更新 更多