Trim champ de chaîne dans JPA
J'ai une table db avec une colonne de type de données char (20). Je n'ai pas le droit de le changer en varchar.
J'écris une entité JPA mappée à cette table. Je voudrais que le champ de chaîne représentant cette colonne dans ma classe d'entité contienne toujours la valeur rognée, pas la valeur de 20 caractères rembourrée avec des espaces qui existe dans la base de données.
Je ne vois aucun moyen facile de le faire. (une annotation serait roche!). Pour le moment, je retourne juste une valeur rognée de mon getter (), mais ceci se sent comme une bidouille.
Une recherche google n'offre aucune aide à ce sujet. Des idées?
7 réponses
Ou vous pouvez utiliser des annotations de cycle de vie:
@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;
}
}
Si cela se produit sur plusieurs entités, vous pouvez créer une annotation personnalisée et écrire un EntityListener dédié.
Annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Trim {}
Auditeur
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;
}
}
Annotez maintenant tous les champs de chaîne pertinents avec @Trim
et enregistrez l'écouteur en tant qu'écouteur d'entité par défaut dans votre persistance.xml:
<persistence-unit ..>
<!-- ... -->
<default-entity-listeners>
com.somepackage.TrimListener
and.maybe.SomeOtherListener
</default-entity-listeners>
</persistence-unit>
La réponse acceptée (en utilisant l'annotation JPA entity listeners / @Trim) est dangereuse. L'appel du setter sur l'entité récupérée semble marquer l'entité comme sale. Lorsque j'ai essayé cela moi-même au niveau de l'entité racine (en utilisant Spring3 / hibernate), cela a déclenché des tonnes de mises à jour étrangères aux entités associées qui n'ont pas été modifiées pendant la transaction. C'était un vrai gâchis dans la production, et le suivi jusqu'à ce que cela soit la cause a pris du temps.
À la fin je j'ai opté pour l'approche plus simple consistant à rogner manuellement chacun des champs à la demande (dans un mappeur d'entité à domaine personnalisé, ou dans le getter d'entité) similaire à la réponse D'Edwin.
Quel fournisseur JPA utilisez-vous?
Si vous utilisez EclipseLink les champs CHAR sont rognés par défaut. Vous pouvez le désactiver via la propriété session trimStrings (assurez-vous de ne pas l'avoir défini).
J'utilise cette méthode, ce qui rend le rognage transparent sans avoir à utiliser d'annotations dans chaque champ de chaîne.
Dans le même package que vous avez votre classe session factory (celle que vous utilisez pour obtenir des Sessions, par exemple org.blablabla.yourpackage.etc.SessionGetter.getSession()
, vous devez créer un fichier nommé package-info.java
et y placer ce contenu:
@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;
Ensuite, vous créez la classe StringUserType
dans ce même paquet:
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;
}
}
Et c'est tout, pas besoin de créer des annotations personnalisées dans vos beans, il va" magiquement " couper les chaînes chaque fois que vous obtenez les objets de la base de données.
Mettez l'annotation sur la méthode getter, définissez @Acesss sur AccessType.Propriété et couper le champ à L'aide de chaîne.trim() méthode.
Ou simplement mettre la garniture dans la méthode getter et toujours accéder au champ à travers elle. Cela ne va pas être plus simple que cela.
Si cela ne vous dérange pas d'utiliser pure Hibernate et de dévier de la norme JPA, vous pouvez utiliser Hibernate @ ColumnTransformer à condition que vous ayez une fonction de base de données pour faire le travail de
Vous pouvez trouver comment pour le faire dans la référence Hibernate:
J'espère que cela aide!
Tout ce que vous avez à faire est de mettre ceci sur votre contrôleur et cela fonctionne comme prévu vous n'avez pas besoin d'un écouteur ou quoi que ce soit de cela
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
Si votre exigence de domaine indique qu'elle a besoin d'informations rognées, vous devez stocker les données dans la valeur rognée. Je ne vois rien de mal à cela.
Modèle De Domaine
Un modèle d'objet de l' domaine qui intègre les deux comportements et de données. (PEAA-Martin Fowler)
Si vous devez explicitement appliquer la règle métier au niveau de la base de données, une option est que vous avez le choix d'écrire un déclencheur, vous pouvez utiliser la méthode SQL trim intégrée. Mais il sera comme utiliser une fusée pour casser un œuf.