J'ai un objet parent avec id-composite (anciennement db - ne peut pas le modifier). J'ai un objet enfant qui est une relation bidirectionnelle un-à-plusieurs (parent-à-enfants). Le mappage est correct, car je peux charger une instance de l'une ou l'autre entité et naviguer correctement à travers la relation. Mon problème vient quand je stocke le parent et il cascades à l'enfant. Le dialecte Postgres émet une requête de la forme:Composite id in hibernate + postgres breaks dû à l'ordre des colonnes retourné
"insert into nom_table (colonne1, colonne2, colonne3, column4) valeurs (valeur1, valeur2, valeur3, valeur4) retour *"
Ce qui est une belle raccourci postgres pour retourner toutes les valeurs de la ligne juste insérée. Cependant, les colonnes reviennent dans un ordre arbitraire défini par la base de données, bien qu'il s'agisse d'un ensemble de résultats standard avec toutes les métadonnées de colonne incluses. Cependant, postgres semble supposer que les colonnes qui reviennent sont dans un ordre arbitraire.
La table en question possède un champ btime et mtime mis à jour via un trigger sur insert. Les deux sont des colonnes d'horodatage. Ce sont les deux premières colonnes qui reviennent. J'ai passé un moment à essayer de déboguer Hibernate, mais c'est un processus lent. Je crois que ce qui se passe est que la première colonne d'horodatage est supposée être la colonne d'ID générée, et elle échoue quand elle essaye de convertir la chaîne d'horodatage en Long. En fait, l'identifiant généré apparaît comme la 4ème colonne.
Caused by: org.postgresql.util.PSQLException: Bad value for type long : 2010-02-21 18:11:19.774362
at org.postgresql.jdbc2.AbstractJdbc2ResultSet.toLong(AbstractJdbc2ResultSet.java:2796)
at org.postgresql.jdbc2.AbstractJdbc2ResultSet.getLong(AbstractJdbc2ResultSet.java:2019)
at org.hibernate.id.IdentifierGeneratorFactory.get(IdentifierGeneratorFactory.java:104)
at org.hibernate.id.IdentifierGeneratorFactory.getGeneratedIdentity(IdentifierGeneratorFactory.java:92)
at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:98)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:57)
Je crois que cela est en quelque sorte lié à l'aide d'une clé composite comme je l'ai utilisé une configuration presque identique pour les colonnes de btime et mtime dans une autre application, qui a un schéma spécifique à mise en veille prolongée qui utilise généré longues ids partout . Parce que btime et mtime proviennent d'une table parente que toutes les autres tables de la base de données héritent, il n'y a aucun moyen de changer l'ordre des colonnes. Le résultat net est que l'insertion parent et l'insertion enfant en cascade réussissent, mais qu'Hibernate lève une exception après le chargement des champs générés pour l'entité enfant. Cela ressemble beaucoup à un bug en hibernation, et c'est celui qui m'a arrêté froid. J'espère que quelqu'un connaît une solution de contournement ou une correction de bogue.
J'utilise hibernate-3.3.1-GA comme distribué par SpringSource dans le paquet des dépendances avec le dernier ressort 3.0.1 version
CREATE TABLE parent_stat (
btime timestamp DEFAULT NOW() NOT NULL, -- Birth Time or Creation Time
mtime timestamp DEFAULT NOW() NOT NULL, -- Modified Time
enabled boolean DEFAULT true NOT NULL
);
CREATE TABLE portal.parent_persistent (
version int DEFAULT 1 NOT NULL -- Version Number
);
CREATE TABLE portal.customers
(
customer_id int NOT NULL,
zone_id int NOT NULL,
<other properties go here>
CONSTRAINT pk_customers PRIMARY KEY (zone_id, customer_id)
) INHERITS (portal.parent_stat, portal.parent_persistent);
CREATE TABLE portal.users
(
user_id bigserial NOT NULL,
customer_zone_id int NOT NULL,
customer_id int NOT NULL,
<more properties here>
CONSTRAINT pk_users_user_id PRIMARY KEY (user_id),
CONSTRAINT fk_users_customers FOREIGN KEY (customer_zone_id, customer_id) REFERENCES customers(zone_id, customer_id) ON UPDATE RESTRICT ON DELETE RESTRICT
) INHERITS (portal.parent_stat, portal.parent_persistent);
<hibernate-mapping>
<class name="CustomerImpl" proxy="Customer" schema="portal" table="customers">
<composite-id name="key" class="CustomerKeyImpl">
<key-property name="zoneId" type="int" column="zone_id"/>
<key-property name="customerId" type="int" column="customer_id"/>
</composite-id>
&version;
&auditable;
<set name="users" lazy="true" inverse="true" order-by="lower(email) asc" cascade="save-update,delete">
<key>
<column name="customer_zone_id"/>
<column name="customer_id"/>
</key>
<one-to-many class="UserImpl"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping default-lazy="true">
<class name="UserImpl" proxy="User" schema="portal" table="users">
<id name="id" type="java.lang.Long" column="user_id">
<generator class="identity"/>
</id>
&version;
&auditable;
<many-to-one name="customer" class="CustomerImpl" not-null="true" cascade="save-update">
<column name="customer_zone_id"/>
<column name="customer_id"/>
</many-to-one>
</class>
</hibernate-mapping>
L'entité version et l'entité auditable sont définis comme suit :
<version name="version" column="version" unsaved-value="null" type="java.lang.Long"/>
et
<property name="created" type="java.util.Calendar" column="btime" generated="insert" insert="false" update="false"/>
<property name="modified" type="java.util.Calendar" column="mtime" generated="always" insert="false" update="false"/>
Enfin, la pr stockée suivante ocedure est configuré pour s'exécuter avant l'insertion sur les deux tables, ce qui correspond à la mise à jour du mtime.
CREATE OR REPLACE FUNCTION touchrow() RETURNS TRIGGER AS $$
DECLARE
mtime timestamp NOT NULL DEFAULT NOW();
BEGIN
NEW.mtime := mtime;
RAISE DEBUG 'mtime=%', NEW.mtime;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Tous les cette fonctionnalité, y compris les tables parent et la procédure stockée pour la mise à jour du mtime ont été utilisés dans d'autres applications. La seule différence est la clé composite de l'objet parent.
Remarque: Je peux stocker l'objet parent sans référence à l'enfant sans difficulté. Si je regarde mes journaux sql, je peux voir que hibernate émet un select séparé après avoir effectué l'insertion dans ce cas - je suppose qu'il s'agit de la différence entre la sauvegarde en cascade et non, ou la différence entre la clé primaire composite et la clé étrangère composite. Le dialecte n'émet que le "insert ...renvoie la syntaxe * sur l'objet enfant, et il le fait si je sauve le parent en premier, puis ajoute l'enfant avant d'enregistrer l'enfant, ou si je laisse juste le parent cascader à l'enfant (ou vice versa)
J'ai un peu plus d'informations Je ne sais toujours pas comment le bit "RETURNING *" est ajouté à la requête Au moment où la méthode store() s'exécute, l'instruction préparée est déjà créée , et je n'ai pas compris où les déclarations préparées se construisent.Cependant, j'ai trouvé que la source du problème est que IdentityGenerator suppose que l'ensemble de résultats contiendra seulement l'identifiant, ou qu'il sera au moins la première colonne. Plus de code dans la colonne suivante – ideasculptor
Dans executeAndExtract (PreparedStatement insert), le nom suivant est appelé: IdentifierGeneratorFactory.getGeneratedIdentity (rs, persister.getIdentifierType()); qui essaie de forcer la première colonne dans le jeu de résultats au type (un long, dans mon cas). Il a vraiment besoin de tirer le nom et le type de persister et ensuite obtenir la colonne d'identité par nom au lieu de position. Assez facile d'écrire du code pour le faire, mais je n'ai aucune idée de comment l'injecter dans la base de code à court de manipulations de classpath. – ideasculptor
Veuillez corriger votre question, ne pas utiliser les commentaires. –