修剪JPA中的字符串字段

时间:2011-04-20 04:26:23

标签: java jpa

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

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

我看不到任何简单的方法。 (注释会晃动!)。目前我只是从我的getter()返回一个修剪过的值,但这感觉就像一个kludge。

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

9 个答案:

答案 0 :(得分:24)

或者您可以使用生命周期注释:

@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注释所有相关的String字段,并将监听器注册为persistence.xml中的默认实体监听器:

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

答案 1 :(得分:16)

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

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

答案 2 :(得分:5)

这是一个古老的问题,但对我来说很有用。就我而言,最简单的方法是创建一个简单的“ 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;
    (...)

效果很好。

答案 3 :(得分:2)

您使用的是哪个JPA提供商?

如果您正在使用EclipseLink,则默认情况下会修剪CHAR字段。您可以通过会话trimStrings属性禁用它(确保您没有设置它)。

答案 4 :(得分:2)

我正在使用这种方法,这使得修剪透明,而不必在每个字符串字段中使用注释。 在您拥有会话工厂类的同一个包中(用于获取会话的会话工具类,例如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中创建自定义注释,只要从数据库中获取对象,它就会“神奇地”修剪字符串。

答案 5 :(得分:2)

可接受的答案有效,除了在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>

答案 6 :(得分:1)

将注释放在getter方法上,将@Acesss设置为AccessType.Property并使用String.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

我希望有所帮助!

答案 7 :(得分:1)

你需要做的就是将它放在你的控制器上,它按预期运行你不需要听众或其他任何东西

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

答案 8 :(得分:0)

如果您的域名要求声明需要修剪信息,则需要将数据存储在修剪后的值中。我认为没有错。

  

域名模型
  一个对象模型   包含两种行为的域   和数据。 (PEAA - Martin Fowler)

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