148 votes

Hibernate JPA Séquence (non-Id)

Est-il possible d'utiliser une base de données de séquence pour certaines colonne qui n'est pas l'identifiant/ne fait pas partie d'un composite identifiant?

Je suis de l'utilisation d'hibernate en tant que fournisseur jpa, et j'ai une table qui a des colonnes qui sont des valeurs générées (à l'aide d'une séquence), même s'ils ne font pas partie de l'identificateur.

Ce que je veux, c'est d'utiliser une séquence pour créer une nouvelle valeur pour une entité, d'où la colonne pour la séquence est PAS (une partie de) la clé primaire:

@Entity
@Table(name = "MyTable")
public class MyEntity {

    //...
    @Id //... etc
    public Long getId() {
        return id;
    }

   //note NO @Id here! but this doesn't work...
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
    @SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
    @Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
    public Long getMySequencedValue(){
      return myVal;
    }

}

Puis, quand je fais ceci:

em.persist(new MyEntity());

l'id est généré, mais l' mySequenceVal de la propriété seront également générés par mon fournisseur JPA.

Juste pour mettre les choses au clair: je veux Hibernate pour générer de la valeur pour l' mySequencedValue de la propriété. Je sais Hibernate peut gérer la base de données des valeurs générées, mais je ne veux pas utiliser un déclencheur ou toute autre chose d'autre que Hibernate lui-même pour générer la valeur de ma propriété. Si Hibernate peut générer des valeurs des clés primaires, pourquoi ne peut-il pas générer, pour un simple propriété?

83voto

Morten Berg Points 308

À la recherche de réponses à ce problème, je suis tombé sur ce lien: http://forum.hibernate.org/viewtopic.php?p=2405140

Il semble que Hibernate/JPA n'est pas capable de créer automatiquement une valeur pour votre non-id-propriétés. @GeneratedValue-annotation est uniquement utilisé en conjonction avec @Id pour créer des auto-nombres.

@Généré par l'annotation juste indique à Hibernate de la base de données à la production de cette valeur elle-même.

La solution (ou autour de) a proposé, dans le cadre de ce forum est de créer une entité séparée avec un Id généré, quelque chose comme ceci:

@Entity
public class GeneralSequenceNumber {
@Id
@GeneratedValue(...)
 privé le numéro de Long;
}

@Entity 
public class MyEntity {
 @Id ..
 private Long id;

@OneToOne(...)
 privé GeneralSequnceNumber myVal;
}

57voto

Sergey Vedernikov Points 2462

J'ai trouvé que l' @Column(columnDefinition="serial") fonctionne parfaitement mais seulement pour PostgreSQL. Pour moi, c'était la solution idéale, car le deuxième entité est "moche".

24voto

Rumal Points 23

Je sais que c'est une très vieille question, mais il a montré tout d'abord sur les résultats et la jpa a beaucoup changé depuis la question.

La bonne façon de le faire est maintenant avec l' @Generated d'annotation. Vous pouvez définir la séquence, définir la valeur par défaut dans la colonne de cette séquence et de les mapper la colonne:

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)

14voto

alasdairg Points 1518

Hibernate certainement la prend en charge. À partir de la documentation:

"A généré propriétés sont des propriétés qui ont leurs valeurs générées par la base de données. Généralement, les applications Hibernate nécessaires pour actualiser les objets qui contiennent toutes les propriétés pour lesquelles la base de données a été générer des valeurs. Marquage des propriétés généré, cependant, permet à l'application de déléguer cette responsabilité à Hiberner. Essentiellement, chaque fois que Hibernate questions de SQL d'INSERTION ou de mise à JOUR d'une entité qui a défini des propriétés générées, il émet un sélectionnez ensuite pour récupérer les valeurs générées."

Pour les propriétés générées sur insérer uniquement, de votre propriété, de la cartographie (.hbm.xml) ressemblerait à:

<property name="foo" generated="insert"/>

Pour les propriétés générées lors de l'insertion et de mise à jour de votre propriété cartographie (.hbm.xml) ressemblerait à:

<property name="foo" generated="always"/>

Malheureusement, je ne sais pas JPA, donc je ne sais pas si cette fonctionnalité est exposée via JPA (je soupçonne peut-être pas)

Sinon, vous devriez être en mesure d'exclure la propriété d'insertions et mises à jour, et puis "manuellement" session d'appel.refresh( obj ); après avoir inséré/mis à jour pour charger la valeur générée à partir de la base de données.

Voici comment vous pouvez exclure la propriété d'être utilisés dans les instructions insert et update:

<property name="foo" update="false" insert="false"/>

Encore une fois, je ne sais pas si JPA expose ces Hibernate fonctionnalités, mais Hibernate ne les soutiennent.

5voto

Sebastian Götz Points 70

Bien que ce soit un vieux thread, je veux partager ma solution et espère avoir quelques commentaires sur ce. Soyez averti que je l'ai testé cette solution avec ma base de données locale dans certains cas de test JUnit. Ce n'est donc pas une fonction productive jusqu'à présent.

J'ai résolu le problème pour ma par l'introduction d'une annotation personnalisée appelée Séquence ayant aucun bien. C'est juste un repère pour les champs qui doivent être affectés d'une valeur d'une incrémenté de la séquence.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}

À l'aide de cette annotation j'ai marqué mes entités.

public class Area extends BaseEntity implements ClientAware, IssuerAware
{
    @Column(name = "areaNumber", updatable = false)
    @Sequence
    private Integer areaNumber;
....
}

Pour garder les choses de base de données indépendant, j'ai introduit une entité appelée SequenceNumber qui détient la séquence actuelle de la valeur et de la taille de l'incrément. J'ai choisi celui de la classe unique de la clé, de sorte que chaque classe d'entité recevra sa propre séquence.

@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
    @Id
    @Column(name = "className", updatable = false)
    private String className;

    @Column(name = "nextValue")
    private Integer nextValue = 1;

    @Column(name = "incrementValue")
    private Integer incrementValue = 10;

    ... some getters and setters ....
}

La dernière étape et la plus difficile, c'est un PreInsertListener qui gère le numéro d'ordre d'affectation. Notez que j'ai utilisé le printemps comme récipient à grains.

@Component
public class SequenceListener implements PreInsertEventListener
{
    private static final long serialVersionUID = 7946581162328559098L;
    private final static Logger log = Logger.getLogger(SequenceListener.class);

    @Autowired
    private SessionFactoryImplementor sessionFactoryImpl;

    private final Map<String, CacheEntry> cache = new HashMap<>();

    @PostConstruct
    public void selfRegister()
    {
        // As you might expect, an EventListenerRegistry is the place with which event listeners are registered
        // It is a service so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);

        // add the listener to the end of the listener chain
        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
    }

    @Override
    public boolean onPreInsert(PreInsertEvent p_event)
    {
        updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());

        return false;
    }

    private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
    {
        try
        {
            List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);

            if (!fields.isEmpty())
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Intercepted custom sequence entity.");
                }

                for (Field field : fields)
                {
                    Integer value = getSequenceNumber(p_entity.getClass().getName());

                    field.setAccessible(true);
                    field.set(p_entity, value);
                    setPropertyState(p_state, p_propertyNames, field.getName(), value);

                    if (log.isDebugEnabled())
                    {
                        LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.error("Failed to set sequence property.", e);
        }
    }

    private Integer getSequenceNumber(String p_className)
    {
        synchronized (cache)
        {
            CacheEntry current = cache.get(p_className);

            // not in cache yet => load from database
            if ((current == null) || current.isEmpty())
            {
                boolean insert = false;
                StatelessSession session = sessionFactoryImpl.openStatelessSession();
                session.beginTransaction();

                SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);

                // not in database yet => create new sequence
                if (sequenceNumber == null)
                {
                    sequenceNumber = new SequenceNumber();
                    sequenceNumber.setClassName(p_className);
                    insert = true;
                }

                current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
                cache.put(p_className, current);
                sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());

                if (insert)
                {
                    session.insert(sequenceNumber);
                }
                else
                {
                    session.update(sequenceNumber);
                }
                session.getTransaction().commit();
                session.close();
            }

            return current.next();
        }
    }

    private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
    {
        for (int i = 0; i < propertyNames.length; i++)
        {
            if (propertyName.equals(propertyNames[i]))
            {
                propertyStates[i] = propertyState;
                return;
            }
        }
    }

    private static class CacheEntry
    {
        private int current;
        private final int limit;

        public CacheEntry(final int p_limit, final int p_current)
        {
            current = p_current;
            limit = p_limit;
        }

        public Integer next()
        {
            return current++;
        }

        public boolean isEmpty()
        {
            return current >= limit;
        }
    }
}

Comme vous pouvez le voir dans le code ci-dessus le port d'écoute utilisé un SequenceNumber instance par classe d'entité et se réserve un couple de numéros de séquence définie par le incrementValue de la SequenceNumber entité. Si il manque des numéros de séquence, il charge le SequenceNumber entité de la classe cible et se réserve incrementValue les valeurs pour les appels. De cette façon, je n'ai pas besoin d'interroger la base de données chaque fois qu'une séquence de valeur est nécessaire. Remarque le statelesssession n'est ouvert pour la réservation de la prochaine série de numéros de séquence. Vous ne pouvez pas utiliser la même session de l'entité cible est actuellement persisté car cela conduirait à une ConcurrentModificationException dans le EntityPersister.

Espérons que cela aide quelqu'un.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X