web-dev-qa-db-fra.com

Bypass GeneratedValue dans Hibernate (fusionner les données pas en db?)

Mon problème est le même que celui décrit dans [1] ou [2] . Je dois définir manuellement une valeur générée automatiquement par défaut ( pourquoi? Importer d'anciennes données). Comme décrit dans [1] en utilisant la fonction entity = em.merge(entity) d'Hibernate fera l'affaire.

Malheureusement pour moi, ce n'est pas le cas. Je ne reçois ni erreur ni autre avertissement. L'entité est juste ne va pas apparaître dans la base de données . J'utilise Spring et Hibernate EntityManager 3.5.3-Final.

Des idées?

37
Jan

cela fonctionne sur mon projet avec le code suivant:

@XmlAttribute
@Id
@Basic(optional = false)
@GeneratedValue(strategy=GenerationType.IDENTITY, generator="IdOrGenerated")
@GenericGenerator(name="IdOrGenerated",
                  strategy="....UseIdOrGenerate"
)
@Column(name = "ID", nullable = false)
private Integer id;

et

import org.hibernate.id.IdentityGenerator;
...
public class UseIdOrGenerate extends IdentityGenerator {
private static final Logger log = Logger.getLogger(UseIdOrGenerate.class.getName());

@Override
public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
    if (obj == null) throw new HibernateException(new NullPointerException()) ;

    if ((((EntityWithId) obj).getId()) == null) {
        Serializable id = super.generate(session, obj) ;
        return id;
    } else {
        return ((EntityWithId) obj).getId();

    }
}

où vous définissez essentiellement votre propre générateur d'ID (basé sur la stratégie d'identité), et si l'ID n'est pas défini, vous déléguez la génération au générateur par défaut.

Le principal inconvénient est qu'il vous limite à Hibernate en tant que fournisseur JPA ... mais il fonctionne parfaitement avec mon projet MySQL

45
Kevin

Une autre implémentation, beaucoup plus simple.

Celui-ci fonctionne avec les deux configuration basée sur des annotations ou basée sur xml: il s'appuie sur des métadonnées d'hibernation pour obtenir la valeur id de l'objet. Remplacez SequenceGenerator par IdentityGenerator (ou tout autre générateur) selon votre configuration. (La création d'un décorateur au lieu d'un sous-classement, en passant le générateur d'ID décoré en paramètre à ce générateur, est laissée au lecteur comme exercice).

public class UseExistingOrGenerateIdGenerator extends SequenceGenerator {
    @Override
    public Serializable generate(SessionImplementor session, Object object)
                        throws HibernateException {
        Serializable id = session.getEntityPersister(null, object)
                      .getClassMetadata().getIdentifier(object, session);
        return id != null ? id : super.generate(session, object);
    }
}

Réponse à l'exercice (en utilisant un motif de décoration, comme demandé), pas vraiment testé:

public class UseExistingOrGenerateIdGenerator implements IdentifierGenerator, Configurable {

    private IdentifierGenerator defaultGenerator;

    @Override
    public void configure(Type type, Properties params, Dialect d) 
                        throws MappingException;
        // For example: take a class name and create an instance
        this.defaultGenerator = buildGeneratorFromParams(
                params.getProperty("default"));
    }

    @Override
    public Serializable generate(SessionImplementor session, Object object)
                        throws HibernateException {
        Serializable id = session.getEntityPersister(null, object)
                      .getClassMetadata().getIdentifier(object, session);
        return id != null ? id : defaultGenerator.generate(session, object);
    }
}
49
Laurent Grégoire

Mise à jour de la réponse de Laurent Grégoire pour l'hibernate 5.2 car elle semble avoir un peu changé.

public class UseExistingIdOtherwiseGenerateUsingIdentity extends IdentityGenerator {

    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
        return id != null ? id : super.generate(session, object);
    }
}

et utilisez-le comme ceci: (remplacez le nom du paquet)

@Id
@GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "{package}.UseExistingIdOtherwiseGenerateUsingIdentity")
@GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
@Column(unique = true, nullable = false)
protected Integer id;
20
Cadell Christo

Si vous utilisez le org.hibernate.id.UUIDGenerator D'hibernate pour générer un identifiant de chaîne, je vous suggère d'utiliser:

public class UseIdOrGenerate extends UUIDGenerator {


    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
        Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
        return id != null ? id : super.generate(session, object);
    }
}
6
forhas

Je donne ici une solution qui a fonctionné pour moi:
créez votre propre générateur d'identifiant/générateur de séquence

public class FilterIdentifierGenerator extends IdentityGenerator implements IdentifierGenerator{

@Override
public Serializable generate(SessionImplementor session, Object object)
        throws HibernateException {
    // TODO Auto-generated method stub
    Serializable id = session.getEntityPersister(null, object)
            .getClassMetadata().getIdentifier(object, session);
    return id != null ? id : super.generate(session, object);
}

}

modifier votre entité comme:

@Id
@GeneratedValue(generator="myGenerator")
@GenericGenerator(name="myGenerator", strategy="package.FilterIdentifierGenerator")
@Column(unique=true, nullable=false)
private int id;
...

et lors de l'enregistrement au lieu d'utiliser persist() utilisez merge() ou update()

6
rakesh

Selon le thread Désactiver sélectivement la génération d'un nouvel ID sur les forums Hibernate, merge() n'est peut-être pas la solution (du moins pas seul) et vous devrez peut-être utiliser un - générateur personnalisé (c'est le deuxième lien que vous avez publié).

Je n'ai pas testé cela moi-même donc je ne peux pas le confirmer mais je recommande de lire le fil des forums d'Hibernate.

2
Pascal Thivent

Pour tous ceux qui cherchent à faire cela, ce qui précède fonctionne bien. Juste une recommandation pour obtenir l'identifiant de l'objet plutôt que d'avoir l'héritage pour chaque classe d'entité (juste pour l'ID), vous pouvez faire quelque chose comme:

import org.hibernate.id.IdentityGenerator;

public class UseIdOrGenerate extends IdentityGenerator {

    private static final Logger log = Logger.getLogger(UseIdOrGenerate.class
            .getName());

    @Override
    public Serializable generate(SessionImplementor session, Object object)
            throws HibernateException {
        if (object == null)
            throw new HibernateException(new NullPointerException());

        for (Field field : object.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(Id.class)
                    && field.isAnnotationPresent(GeneratedValue.class)) {
                boolean isAccessible = field.isAccessible();
                try {
                    field.setAccessible(true);
                    Object obj = field.get(object);
                    field.setAccessible(isAccessible);
                    if (obj != null) {
                        if (Integer.class.isAssignableFrom(obj.getClass())) {
                            if (((Integer) obj) > 0) {
                                return (Serializable) obj;
                            }
                        }
                    }
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        return super.generate(session, object);
    }
}
2
Jonathan

Vous avez besoin d'une transaction en cours.

Si votre transaction est gérée manuellement:

entityManager.getTransaction().begin();

(bien sûr, n'oubliez pas de vous engager)

Si vous utilisez des transactions déclaratives, utilisez la déclaration appropriée (via des annotations, très probablement)

Définissez également le niveau de journalisation de la mise en veille prolongée sur debug (log4j.logger.org.hibernate=debug) dans votre log4j.properties afin de suivre ce qui se passe plus en détail.

1
Bozho