Spécifiez manuellement la valeur d'une clé primaire dans la colonne JPA @GeneratedValue

je vais avoir une Entité qui a une clé primaire / champ id comme suit:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Cela fonctionne bien. J'utilise EclipseLink pour créer le schéma DDL, et la colonne est correctement créée comme suit:

`id` bigint(20) NOT NULL AUTO_INCREMENT

cependant, j'ai plusieurs entités pour lesquelles je veux spécifier le PK moi-même (c'est une petite application qui transfère des données d'une ancienne base de données à la nouvelle que nous construisons). Si je spécifie L'ID du POJO (en utilisant setId(Long id)) et persister, EclipseLink ne pas save (c'est à dire l'enregistrement est enregistré, mais l'id est généré automatiquement par eclipseLink).

Est-il un moyen de spécifier manuellement la valeur d'une colonne qui a une @GeneratedValue ?

voici quelques réflexions sur le sujet:

j'ai essayé de contourner le problème en n'utilisant pas @GeneratedValue du tout, mais simplement définir manuellement la colonne à Auto_augmenter. Cependant, cela me force à fournir manuellement un IDs toujours, puisque EclipseLink valide la clé primaire (donc elle ne peut pas être nulle, zéro, ou un nombre négatif). Le message de l'exception lit que je dois préciser eclipselink.id_validation, cependant, cela ne semble pas faire de différence (j'ai annoté @PrimaryKey(validation = IdValidation.NONE) mais toujours le même message).

Pour clarifier: J'utilise EclipseLink (2.4.0) comme fournisseur de persistance et je ne peux pas m'en écarter (de grandes parties du projet dépendent d'eclipselink des indices de requête spécifiques, des annotations et des classes).


MODIFIER (En Réponse à l'réponses):

Personnaliser Le Séquençage: j'ai essayé d'implémenter mon propre séquençage. J'ai essayé le subclassing DefaultSequence, mais EclipseLink me dira Internal Exception: org.eclipse.persistence.platform.database.MySQLPlatform could not be found. Mais j'ai vérifié: La classe est sur le chemin de la classe.

Donc j'ai sous-classé une autre classe, NativeSequence:

public class MyNativeSequence extends NativeSequence {

    public MyNativeSequence() {
        super();
    }

    public MyNativeSequence(final String name) {
        super(name);
    }


    @Override
    public boolean shouldAlwaysOverrideExistingValue() {
        return false;
    }

    @Override
    public boolean shouldAlwaysOverrideExistingValue(final String seqName) {
        return false;
    }

}

cependant, ce que j'obtiens est le suivantes:

javax.persistence.RollbackException: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commitInternal(EntityTransactionImpl.java:102)
    ...
Caused by: Exception [EclipseLink-7197] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Null or zero primary key encountered in unit of work clone [de.dfv.datenbank.domain.Mitarbeiter[ id=null ]], primary key [null]. Set descriptors IdValidation or the "eclipselink.id-validation" property.
    at org.eclipse.persistence.exceptions.ValidationException.nullPrimaryKeyInUnitOfWorkClone(ValidationException.java:1451)
    ...

(trace de pile raccourcie pour plus de clarté). C'est le même message que j'ai eu avant. Ne devrais-je pas classer NativeSequence? Si c'est le cas, je ne sais pas quoi mettre en œuvre pour les méthodes abstraites dans la séquence ou la Suite Standard.

Il peut également être intéressant de noter, que, tout simplement, sous-classement (sans écraser toutes les méthodes) de la classe fonctionne comme prévu. Cependant, returing false shouldAlwaysOverrideExistingValue(...) ne générera pas une seule valeur du tout (je suis passé par le programme et getGeneratedValue() n'est pas appelé une seule fois).

aussi, quand j'insère comme 8 entités d'un certain type dans une transaction il en résulte 11 enregistrements dans la base de données (qu'est-ce que l'enfer?!).

MODIFIER (2012-09-01): Je n'ai toujours pas de Solution pour le problème, implémenter ma propre séquence ne l'a pas résolu. Ce dont j'ai besoin, c'est d'un moyen de ne pas définir un Id explicitement (donc il sera généré automatiquement) et de pouvoir définir un Id explicitement (donc il sera utilisé pour création de l'enregistrement dans la base de données).

j'ai essayé de définir la colonne comme auto_ increment myself et ommit @GeneratedValue, mais la Validation va intervenir et ne me permettra pas de sauver une telle entité. Si je spécifie une valeur != 0 et != zéro, mysql se plaindra pour une clé primaire dupliquée.

je suis à cours d'idées et d'options pour essayer. Tout? (à partir d'un bounty)

28
demandé sur scravy 2012-08-17 12:21:47

6 réponses

si vous utilisez le séquençage de TABLE, alors EclipseLink vous permettra de surcharger la valeur (ou la séquence si votre base de données supporte ceci, MySQL ne le fait pas).

pour L'identité, Je ne suis même pas sûr que MySQL vous permettra de fournir votre propre identité, vous pourriez vouloir vérifier cela. En général, je ne recommanderais jamais L'utilisation de L'identité car elle ne supporte pas la préallocation.

il y a quelques problèmes à permettre à L'identité de fournir l'identité ou non. La première est que deux différents insert SQL devra être généré en fonction de la valeur id, car pour L'identité l'id ne peut pas être dans l'insert du tout. Vous souhaiterez peut-être enregistrer un bogue pour avoir des identifiants fournis par L'utilisateur du support D'identité.

vous devriez toujours être en mesure de le faire fonctionner avec votre propre sous-classe de séquence, ou peut-être sous-classe MySQLPlatform. Vous définiriez votre sous-classe MySQLPlatform en utilisant le " eclipselink.la cible de la base de données" unité de persistance de la propriété.

7
répondu James 2012-09-04 14:04:13

Cela fonctionne avec eclipselink. Il va créer une table séparée pour la séquence, mais cela ne devrait pas poser de problème.

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="id", insertable=true, updatable=true, unique=true, nullable=false)
private Long id;

GenerationType.AUTO choisira la stratégie de génération idéale. Puisque le champ est spécifié comme insertable et actualisable, une stratégie de génération de TABLE sera utilisée. Cela signifie que eclipselink générera une autre table contenant la valeur de séquence courante et générera la séquence elle-même au lieu de la déléguer à la base de données. Puisque la colonne est déclaré insertable, si id est null en persistant, eclipselink générera l'id. Sinon, l'id sera utilisé.

7
répondu poison 2012-09-08 15:14:34

solution axée sur la base de données à votre problème:

  1. créer une colonne auxiliaire, nullable dans votre table. Il tiendra assignés manuellement id:

    CREATE TABLE `test_table`
    (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `manual_id` bigint(20) NULL,
      `some_other_field` varchar(200) NOT NULL,
      PRIMARY KEY(id)
    );
    
  2. mappez cette colonne à un champ normal dans votre entité:

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name="manual_id")
    private Integer manualId;
    
  3. Créer un déclencheur qui met à table id pour le manuel d'identification attribué s'il n'est pas null:

    DELIMITER //
    CREATE TRIGGER `test_table_bi` BEFORE INSERT ON `test_table`
      FOR EACH ROW 
      BEGIN
        IF NEW.`manual_id` IS NOT NULL THEN 
          SET NEW.`id` = NEW.`manual_id`;
        END IF;
    END;//
    DELIMITER;    
    
  4. Toujours utiliser le manualId lorsque vous avez besoin de attribuer un identifiant personnalisé. Le déclenchement de faire de la magie pour vous:

    testEntiy.setManualId(300);
    entityManager.persist(testEntity);
    
  5. après la phase d'importation de la base de données, retirez simplement le déclencheur, la colonne auxiliaire et c'est mapping.

    DROP TRIGGER `test_table_bi`;
    ALTER TABLE `test_table` DROP COLUMN `manual_id`;
    

Avertissement

Si vous spécifiez manuellement un id supérieur à celui actuel AUTO_INCREMENT valeur, la prochaine généré id passera à la valeur de l'id assigné manuellement plus 1, par exemple:

INSERT INTO `test_table` (manual_id, some_other_field) VALUES (50, 'Something');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (90, 'Something 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 2');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (40, 'Something 3');
INSERT INTO `test_table` (manual_id, some_other_field) VALUES (NULL, 'Something else 3');

exercera les résultats:

+----+-----------+------------------+
| id | manual_id | some_other_field |
+----+-----------+------------------+
| 50 |        50 | Something        |
| 51 |      NULL | Something else   |
| 90 |        90 | Something 2      |
| 91 |      NULL | Something else 2 |
| 40 |        40 | Something 3      |
| 92 |      NULL | Something else 3 |
+----+-----------+------------------+

Pour éviter les problèmes, il est fortement recommandé de régler l' AUTO_INCREMENT colonne pour commencer avec un nombre supérieur à tous les ID existants dans votre base de données précédente, par exemple:

ALTER TABLE `test_table` AUTO_INCREMENT = 100000;
6
répondu Anthony Accioly 2017-02-09 10:24:02

il se peut que je manque quelque chose d'évident, mais pourquoi ne pas simplement définir une autre entité avec le même @Table(name=".....") annotation, avec les mêmes champs, mais faire l'id NON généré? Ensuite, vous pouvez utiliser cette entité pour le code qui copie les données de L'ancienne base de données à la nouvelle, et celui avec L'Id généré peut être utilisé pour les créations normales qui nécessitent la génération d'id.

Je ne peux pas vous dire si cela fonctionne avec EclipseLink, mais nous utilisons hibernation ici et il ne semble pas à l'esprit.

3
répondu DuncanKinnear 2012-09-06 03:05:45

en utilisant GenerationType.Séquence avec PostgreSQL et EclipseLink travaillé pour moi.

1)

@GeneratedValue(strategy = GenerationType.IDENTITY) 

par

@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="step_id_seq")
@SequenceGenerator(name="step_id_seq", sequenceName="step_id_seq")

Maintenant, vous pouvez appeler la séquence à l'aide de NativeQuery:

return ((Vector<Integer>) em.createNativeQuery("select nextval('step_id_seq')::int").getSingleResult()).get(0);

et définissez L'Id retourné à votre entité avant l'appel EntityManager.méthode de persist ().

j'Espère qu'il n'est pas trop tard!

1
répondu szarza 2013-06-13 15:06:40

rechercher le Générateur D'Id personnalisé

http://blog.anorakgirl.co.uk/2009/01/custom-hibernate-sequence-generator-for-id-field/

peut-être que cela pourrait aider.

0
répondu Dangling Piyush 2012-08-17 08:36:49