Обрезать строковое поле в JPA - PullRequest
30 голосов
/ 20 апреля 2011

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

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

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

Поиск Google не предлагает никакой помощи по этому вопросу. Есть идеи?

Ответы [ 8 ]

24 голосов
/ 20 апреля 2011

Или вы можете использовать аннотации жизненного цикла:

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

}

Теперь аннотируйте все соответствующие поля String с помощью @Trim и зарегистрируйте прослушиватель как прослушиватель сущности по умолчанию в вашем файле persistence.xml:

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

16 голосов
/ 07 ноября 2013

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

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

2 голосов
/ 08 июня 2019

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

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

}

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

2 голосов
/ 20 апреля 2011

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

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

1 голос
/ 08 июня 2016

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

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
1 голос
/ 20 апреля 2011

Поместите аннотацию в метод получения, установите @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

Надеюсь, это поможет!

0 голосов
/ 20 апреля 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...