Подтвердить что ты не робот

Строка строкой строки в JPA

У меня есть таблица db со столбцом типа данных char (20). Мне не разрешено менять его на varchar.

Я пишу объект JPA, сопоставленный с этой таблицей. Я хотел бы, чтобы поле строки, представляющее этот столбец в моем классе сущности, всегда содержало обрезанное значение, а не 20-значное значение, заполненное пробелами, которые существуют в db.

Я не вижу никакого простого способа сделать это. (аннотация будет качаться!). На данный момент я просто возвращаю урезанное значение из моего getter(), но это похоже на kludge.

Поиск Google не предлагает никакой помощи. Любые идеи?

4b9b3361

Ответ 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 и зарегистрируйте Listener как прослушиватель сущностей по умолчанию в вашем persistence.xml:

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

 

Ответ 2

Принятый ответ (с использованием JPA-сущ. сущ./@Trim-аннотации) является опасным. Вызов установщика на получаемом объекте означает, что объект помечен как грязный. Когда я пробовал это самостоятельно на уровне корневого объекта (используя Spring3/hibernate), он вызвал тонны посторонних обновлений для связанных объектов, которые в противном случае не были изменены во время транзакции. Это был настоящий беспорядок в производстве, и отслеживание его до этого было причиной, требующей времени.

В конце я решил пойти с более простым подходом к обрезке каждого из полей вручную по запросу (в настраиваемом объектно-ориентированном преобразователе или в получателе объектов), аналогичном ответу Эдвина.

Ответ 3

Какой поставщик JPA вы используете?

Если вы используете EclipseLink CHAR, по умолчанию обрезаются поля. Вы можете отключить это через свойство trimStrings сессии (убедитесь, что вы не установили это).

Ответ 4

Я использую этот метод, который делает обрезку прозрачной без использования аннотаций в каждом поле строки. В том же пакете, в котором у вас есть класс сеанса factory (тот, который вы используете для получения сеансов, например 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;
    }

}

И что ему не нужно создавать пользовательские аннотации в beans, он будет "волшебным образом" обрезать строки всякий раз, когда вы получаете объекты из базы данных.

Ответ 5

Поместите аннотацию в метод getter, установите @Acesss в AccessType.Property и обрезайте поле там, используя метод String.trim().

Или просто поместите обрезку в методе геттера и всегда получайте доступ к полю через него. Это не будет проще, чем это.

Если вы не против использования чистого Hibernate и отклонения от стандарта JPA, вы можете использовать Hibernate @ColumnTransformer при условии, что у вас есть функция базы данных для работы

Вы можете найти, как это сделать в ссылке Hibernate:

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

Я надеюсь, что это поможет!

Ответ 6

Все, что вам нужно сделать, это поместить это на ваш контроллер, и он работает так, как вам предложат, вам не нужен слушатель или что-то в этом

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

Ответ 7

Если в запросе вашего домена указано, что он нуждается в обрезанной информации, вам необходимо сохранить данные в обрезаемом значении. Я не вижу в этом ничего плохого.

Модель домена
Объектная модель домен, который включает в себя как поведение и данные. (PEAA - Мартин Фаулер)

Если вам явно необходимо обеспечить соблюдение бизнес-правила на уровне базы данных, одним из вариантов является выбор записи триггера, вы можете использовать встроенный метод SQL-коррекции. Но это будет похоже на использование ракеты для взлома яйца.

Ответ 8

Принятый ответ работает, за исключением регистрации слушателя в файле 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>