Строка строкой строки в JPA
У меня есть таблица db со столбцом типа данных char (20). Мне не разрешено менять его на varchar.
Я пишу объект JPA, сопоставленный с этой таблицей. Я хотел бы, чтобы поле строки, представляющее этот столбец в моем классе сущности, всегда содержало обрезанное значение, а не 20-значное значение, заполненное пробелами, которые существуют в db.
Я не вижу никакого простого способа сделать это. (аннотация будет качаться!). На данный момент я просто возвращаю урезанное значение из моего getter(), но это похоже на kludge.
Поиск Google не предлагает никакой помощи. Любые идеи?
Ответы
Ответ 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>