Return-Path: X-Original-To: apmail-openjpa-users-archive@minotaur.apache.org Delivered-To: apmail-openjpa-users-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id CC18110BF8 for ; Tue, 23 Apr 2013 15:02:29 +0000 (UTC) Received: (qmail 86635 invoked by uid 500); 23 Apr 2013 15:02:29 -0000 Delivered-To: apmail-openjpa-users-archive@openjpa.apache.org Received: (qmail 86604 invoked by uid 500); 23 Apr 2013 15:02:29 -0000 Mailing-List: contact users-help@openjpa.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: users@openjpa.apache.org Delivered-To: mailing list users@openjpa.apache.org Received: (qmail 86594 invoked by uid 99); 23 Apr 2013 15:02:29 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 23 Apr 2013 15:02:29 +0000 X-ASF-Spam-Status: No, hits=-0.0 required=5.0 tests=RCVD_IN_DNSWL_NONE,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (athena.apache.org: local policy) Received: from [194.109.24.28] (HELO smtp-vbr8.xs4all.nl) (194.109.24.28) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 23 Apr 2013 15:02:24 +0000 Received: from remote.huizemolenaar.nl (D57D0452.static.ziggozakelijk.nl [213.125.4.82]) (authenticated bits=0) by smtp-vbr8.xs4all.nl (8.13.8/8.13.8) with ESMTP id r3NF21ls092859 (version=TLSv1/SSLv3 cipher=AES128-SHA bits=128 verify=FAIL) for ; Tue, 23 Apr 2013 17:02:02 +0200 (CEST) (envelope-from henno@huizemolenaar.nl) Received: from HMS.hm.local ([fe80::6051:4a91:4c0d:d963]) by HMS.hm.local ([fe80::6051:4a91:4c0d:d963%10]) with mapi; Tue, 23 Apr 2013 17:02:05 +0200 From: Henno Vermeulen To: "'users@openjpa.apache.org'" Date: Tue, 23 Apr 2013 17:02:05 +0200 Subject: RE: repeatable autogenerate names for key constraints Thread-Topic: repeatable autogenerate names for key constraints Thread-Index: Ac4/TGLVVNzzhciHRzWr2R2CJOya6QAC+rZQADYJUEA= Message-ID: <1C448C478A6B4743AF19DBC3C3DCE13203EADF381343@HMS.hm.local> References: <1C448C478A6B4743AF19DBC3C3DCE13203EADF381285@HMS.hm.local> <1C448C478A6B4743AF19DBC3C3DCE13203EADF381296@HMS.hm.local> In-Reply-To: <1C448C478A6B4743AF19DBC3C3DCE13203EADF381296@HMS.hm.local> Accept-Language: nl-NL Content-Language: nl-NL X-MS-Has-Attach: X-MS-TNEF-Correlator: acceptlanguage: nl-NL Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 X-Virus-Scanned: by XS4ALL Virus Scanner X-Virus-Checked: Checked by ClamAV on apache.org I finally solved the problem by using a custom MappingDefaults class which = extends the default PersistenceMappingDefaults and creates identifiers for = the primary and foreign keys. Unfortunately I encountered a few technical problems along the way which I = could only figure out by using Java debugging with the OpenJPA's source cod= e. By default the mapping defaults uses a setting of DefaultMissingInfo =3D tr= ue which somehow causes the MappingDefaults.getPrimaryKeyIdentifier method = to be ignored. (And it takes quite some time to figure this out...) On the = other hand when using DefaultMissingInfo =3D false I get an exception where= it cannot handle a @MappedSuperclass that has a generated primary key colu= mn: org.apache.openjpa.util.MetaDataException: For "nl.hm.olga.core.entity.Abst= ractEntity.id", expected 1 column(s), but found 0. So I am using DefaultMissingInfo =3D true again and work around the issue b= y nothing that the Primary key is created inside ClassStrategy.map which is= pluggable. So I created an extension of FullClassStrategy where the only w= ay to get what I wanted was to copy/paste OpenJPA internal code which uses = package-private elements so I have to use the org.apache.openjpa.jdbc.meta.= strats package for my own class. Anyway, here is the code: Persistence.xml: KeyConstraintNamesMappingDefaults: package nl.hm.olga.core.dao.openjpa; import org.apache.openjpa.jdbc.identifier.DBIdentifier; import org.apache.openjpa.jdbc.meta.ClassMapping; import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.meta.MappingDefaults; import org.apache.openjpa.jdbc.meta.ValueMapping; import org.apache.openjpa.jdbc.meta.strats.PrimaryKeyConstraintNameFullClas= sStrategy; import org.apache.openjpa.jdbc.schema.ForeignKey; import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.persistence.jdbc.PersistenceMappingDefaults; /** * A {@link MappingDefaults} implementation which extends the * {@link PersistenceMappingDefaults} and generates predictable primary key= and * foreign key constraint names. By default OpenJPA uses the * {@link PersistenceMappingDefaults} which delegates creating the constrai= nt * names to the database. The names generated on Microsoft SQL Server are n= ot * predictable because they contain a seemingly random number. *=20 *

* Note: {@link #getPrimaryKeyIdentifier(ClassMapping, Table)} is normally * ignored when running with the default MappingTool option of * DefaultMissingInfo=3Dtrue. This can be solved by using the * {@link PrimaryKeyConstraintNameFullClassStrategy}. *=20 * @author Henno Vermeulen */ public class KeyConstraintNamesMappingDefaults extends PersistenceMappingDefaults { @Override public DBIdentifier getPrimaryKeyIdentifier(ClassMapping cm, Table table) = { return DBIdentifier.preCombine(table.getIdentifier(), "PK"); } @Override public ForeignKey getForeignKey(ValueMapping vm, DBIdentifier name, Table local, Table foreign, boolean inverse) { return setForeignKeyIdentifier( super.getForeignKey(vm, name, local, foreign, inverse), local, name); } /** * {@inheritDoc} *=20 *

* Handles (at least) inverse foreign keys of join tables, i.e. it refers * from the join table back to the owner table. In this case * local is the join table and foreign is the * owner table. */ @Override public ForeignKey getJoinForeignKey(FieldMapping fm, Table local, Table foreign) { return setForeignKeyIdentifier( super.getJoinForeignKey(fm, local, foreign), local, foreign.getIdentifier()); } private ForeignKey setForeignKeyIdentifier(ForeignKey foreignKey, Table table, DBIdentifier refersTo) { foreignKey.setIdentifier(getForeignKeyIdentifier(table, refersTo)); return foreignKey; } private DBIdentifier getForeignKeyIdentifier(Table table, DBIdentifier refersTo) { DBIdentifier identifier =3D DBIdentifier.combine(table.getIdentifier(), refersTo.getName()); return DBIdentifier.preCombine(identifier, "FK"); } } PrimaryKeyConstraintNameFullClassStrategy: package org.apache.openjpa.jdbc.meta.strats; import static java.util.Arrays.asList; /** * Custom {@link ClassStrategy} which ensures the {@link MappingDefaults} i= s * called to generate a name for the primary key. *=20 *

* Note: we had to copy/paste OpenJPA code so this is not guaranteed to wor= k for * later versions unless we do the copy/paste/adjust again. See * {@link #checkCompatibility()} and the source code of {@link FullClassStrategy}. *=20 * @author Henno Vermeulen */ public class PrimaryKeyConstraintNameFullClassStrategy extends FullClassStrategy { private static final long serialVersionUID =3D 1L; private static final List COMPATIBLE_OPENJPA_VERSION_NUMBERS =3D asList("2.2.0", "2.2.1"); private static final Localizer _loc =3D Localizer .forPackage(FullClassStrategy.class); public PrimaryKeyConstraintNameFullClassStrategy() { super(); checkCompatibility(); } private void checkCompatibility() { if (!COMPATIBLE_OPENJPA_VERSION_NUMBERS .contains(OpenJPAVersion.VERSION_NUMBER)) { throw new IllegalStateException( "Please verify compatibility of this class with OpenJPA " + OpenJPAVersion.VERSION_NUMBER + " (see Javadoc). Known compatible versions are: " + COMPATIBLE_OPENJPA_VERSION_NUMBERS); } } /** * Overridden to replace adapt in the following code with tru= e * so that the MappingDefaults is always checked to create the primary key * name regardless of the value of adapt. (adapt * is set to false by default and will be set to true by using the * DefaultMissing=3Dtrue option but this gives another * exception.) *=20 *

	 * DBIdentifier pkname =3D DBIdentifier.NULL;
	 * if (adapt)
	 * 	pkname =3D
	 * 			cls.getMappingRepository().getMappingDefaults()
	 * 					.getPrimaryKeyIdentifier(cls, table);
	 * 
*/ @Override public void map(boolean adapt) { if (cls.getEmbeddingMetaData() !=3D null) throw new MetaDataException(_loc.get("not-full", cls)); ClassMapping sup =3D cls.getMappedPCSuperclassMapping(); ClassMappingInfo info =3D cls.getMappingInfo(); if (sup !=3D null && info.isJoinedSubclass()) throw new MetaDataException(_loc.get("not-full", cls)); info.assertNoJoin(cls, true); info.assertNoForeignKey(cls, !adapt); info.assertNoIndex(cls, false); info.assertNoUnique(cls, false); // find class table Table table =3D info.getTable(cls, adapt); // find primary key column Column[] pkCols =3D null; if (cls.getIdentityType() =3D=3D cls.ID_DATASTORE) { Column id =3D new Column(); DBDictionary dict =3D cls.getMappingRepository().getDBDictionary(); DBIdentifier idName =3D DBIdentifier.newColumn("id", dict !=3D null ? dict.delimitAll() : false); id.setIdentifier(idName); id.setJavaType(JavaTypes.LONG); id.setComment("datastore id"); if (cls.getIdentityStrategy() =3D=3D ValueStrategies.AUTOASSIGN) id.setAutoAssigned(true); id.setNotNull(true); pkCols =3D info.getDataStoreIdColumns(cls, new Column[] { id }, table, adapt); cls.setPrimaryKeyColumns(pkCols); cls.setColumnIO(info.getColumnIO()); } cls.setTable(table); // add a primary key if we don't have one already PrimaryKey pk =3D table.getPrimaryKey(); if (pk =3D=3D null) { DBIdentifier pkname =3D DBIdentifier.NULL; if (true) pkname =3D cls.getMappingRepository().getMappingDefaults() .getPrimaryKeyIdentifier(cls, table); pk =3D table.addPrimaryKey(pkname); pk.setLogical(!adapt); if (pkCols !=3D null) pk.setColumns(pkCols); } // set joinable if (cls.getIdentityType() =3D=3D ClassMapping.ID_DATASTORE) cls.setJoinable(cls.getPrimaryKeyColumns()[0], new IdentityJoinable(cls)); } } -----Oorspronkelijk bericht----- Van: Henno Vermeulen [mailto:henno@huizemolenaar.nl]=20 Verzonden: maandag 22 april 2013 14:56 Aan: 'users@openjpa.apache.org' Onderwerp: RE: repeatable autogenerate names for key constraints Another issue I found with the @ForeignKey annotation: it doesn't work with= inheritance when using InheritanceType.TABLE_PER_CLASS because this would = generate the same foreign key name for each subclass but they must be uniqu= e in the database! -----Oorspronkelijk bericht----- Van: Henno Vermeulen [mailto:henno@huizemolenaar.nl]=20 Verzonden: maandag 22 april 2013 13:28 Aan: 'users@openjpa.apache.org' Onderwerp: repeatable autogenerate names for key constraints When I let OpenJPA generate my database schema on sql server, the generated= foreign keys have names such as PK__booking___3213E83F5626D20A FK__booking_A__ACTIV__6E886B80 When I later generate the schema again I get different names, e.g. PK__booking___3213E83F7C97F46A FK__booking_A__EVENT__1311456E I use a database schema comparison tool to create a migration script for a = newer version of my application. Because of the different names it lists ve= ry many changes that are not really changes at all. Is it possible to let OpenJPA automatically generate the same names for the= same primary/foreign keys? Perhaps it is possible to override the default = strategy with a few lines of Java code? Is there another approach you can s= uggest? All of my entities inherit an autogenerated primary key from a single mappe= d superclass. PS. I know of the @ForeignKey annotation but it has a few drawbacks: - adds unnecessary boilerplate code (name can be autogenerated wit= hout numbers) that takes time to maintain. - Not sure if it works for mappings such as element collections an= d Maps - Obviously won't work for primary keys, especially not if it is d= efined in one place in a mapped superclass