You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@openjpa.apache.org by Henno Vermeulen <he...@huizemolenaar.nl> on 2013/04/22 13:27:35 UTC
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 very 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 suggest?
All of my entities inherit an autogenerated primary key from a single mapped superclass.
PS. I know of the @ForeignKey annotation but it has a few drawbacks:
- adds unnecessary boilerplate code (name can be autogenerated without numbers) that takes time to maintain.
- Not sure if it works for mappings such as element collections and Maps
- Obviously won't work for primary keys, especially not if it is defined in one place in a mapped superclass
RE: repeatable autogenerate names for key constraints
Posted by Henno Vermeulen <he...@huizemolenaar.nl>.
Also I am using inheritance with @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) and this strategy is overwritten by the base class strategy resulting in tables for the abstract superclass that otherwise wouldn't exist.
I found out the hard way that using BaseClassStrategy and SubclassStrategy are null by default. This is not clear from the documentation; it even states that flat is the default strategy which I verified to be false: an appropriate strategy is selected based on configuration on the entities. Using these settings will ignore the individual settings in the entities and use the strategies for all entities.
Lesson learned: don't mess with OpenJPA internals and be very careful with custom plugin strategies.
-----Oorspronkelijk bericht-----
Van: Henno Vermeulen [mailto:henno@huizemolenaar.nl]
Verzonden: woensdag 24 april 2013 11:24
Aan: 'users@openjpa.apache.org'
Onderwerp: RE: repeatable autogenerate names for key constraints
Note that there is a serious bug in the code I included in the answer of my own question. I am using the BaseClassStrategy also for subclasses which affects what tables are generated for inheritance! To solve this you should use
<property name="openjpa.jdbc.MappingDefaults"
value="nl.hm.olga.core.dao.openjpa.KeyConstraintNamesMappingDefaults(ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict,
BaseClassStrategy=org.apache.openjpa.jdbc.meta.strats.PrimaryKeyConstraintNameFullClassStrategy)" />
But this again results in non-predictable names for primary keys used in subclasses.
RE: repeatable autogenerate names for key constraints
Posted by Henno Vermeulen <he...@huizemolenaar.nl>.
Note that there is a serious bug in the code I included in the answer of my own question. I am using the BaseClassStrategy also for subclasses which affects what tables are generated for inheritance! To solve this you should use
<property name="openjpa.jdbc.MappingDefaults"
value="nl.hm.olga.core.dao.openjpa.KeyConstraintNamesMappingDefaults(ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict,
BaseClassStrategy=org.apache.openjpa.jdbc.meta.strats.PrimaryKeyConstraintNameFullClassStrategy)" />
But this again results in non-predictable names for primary keys used in subclasses.
RE: repeatable autogenerate names for key constraints
Posted by Henno Vermeulen <he...@huizemolenaar.nl>.
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 code.
By default the mapping defaults uses a setting of DefaultMissingInfo = true 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 = false I get an exception where it cannot handle a @MappedSuperclass that has a generated primary key column:
org.apache.openjpa.util.MetaDataException: For "nl.hm.olga.core.entity.AbstractEntity.id", expected 1 column(s), but found 0.
So I am using DefaultMissingInfo = true again and work around the issue by nothing that the Primary key is created inside ClassStrategy.map which is pluggable. So I created an extension of FullClassStrategy where the only way 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:
<property name="openjpa.jdbc.MappingDefaults"
value="nl.hm.olga.core.dao.openjpa.KeyConstraintNamesMappingDefaults(ForeignKeyDeleteAction=restrict, JoinForeignKeyDeleteAction=restrict,
BaseClassStrategy=org.apache.openjpa.jdbc.meta.strats.PrimaryKeyConstraintNameFullClassStrategy, SubclassStrategy=org.apache.openjpa.jdbc.meta.strats.PrimaryKeyConstraintNameFullClassStrategy)" />
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.PrimaryKeyConstraintNameFullClassStrategy;
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 constraint
* names to the database. The names generated on Microsoft SQL Server are not
* predictable because they contain a seemingly random number.
*
* <p>
* Note: {@link #getPrimaryKeyIdentifier(ClassMapping, Table)} is normally
* ignored when running with the default MappingTool option of
* <code>DefaultMissingInfo=true</code>. This can be solved by using the
* {@link PrimaryKeyConstraintNameFullClassStrategy}.
*
* @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}
*
* <p>
* 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
* <code>local</code> is the join table and <code>foreign</code> 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 =
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} is
* called to generate a name for the primary key.
*
* <p>
* Note: we had to copy/paste OpenJPA code so this is not guaranteed to work for
* later versions unless we do the copy/paste/adjust again. See
* {@link #checkCompatibility()} and <a href=
* "http://svn.apache.org/viewvc/openjpa/trunk/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/FullClassStrategy.java?view=log"
* >the source code</a> of {@link FullClassStrategy}.
*
* @author Henno Vermeulen
*/
public class PrimaryKeyConstraintNameFullClassStrategy extends
FullClassStrategy {
private static final long serialVersionUID = 1L;
private static final List<String> COMPATIBLE_OPENJPA_VERSION_NUMBERS =
asList("2.2.0", "2.2.1");
private static final Localizer _loc = 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 <code>adapt</code> in the following code with true
* so that the MappingDefaults is always checked to create the primary key
* name regardless of the value of <code>adapt</code>. (<code>adapt</code>
* is set to false by default and will be set to true by using the
* <code>DefaultMissing=true</code> option but this gives another
* exception.)
*
* <pre>
* DBIdentifier pkname = DBIdentifier.NULL;
* if (adapt)
* pkname =
* cls.getMappingRepository().getMappingDefaults()
* .getPrimaryKeyIdentifier(cls, table);
* </pre>
*/
@Override
public void map(boolean adapt) {
if (cls.getEmbeddingMetaData() != null)
throw new MetaDataException(_loc.get("not-full", cls));
ClassMapping sup = cls.getMappedPCSuperclassMapping();
ClassMappingInfo info = cls.getMappingInfo();
if (sup != 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 = info.getTable(cls, adapt);
// find primary key column
Column[] pkCols = null;
if (cls.getIdentityType() == cls.ID_DATASTORE) {
Column id = new Column();
DBDictionary dict = cls.getMappingRepository().getDBDictionary();
DBIdentifier idName =
DBIdentifier.newColumn("id",
dict != null ? dict.delimitAll() : false);
id.setIdentifier(idName);
id.setJavaType(JavaTypes.LONG);
id.setComment("datastore id");
if (cls.getIdentityStrategy() == ValueStrategies.AUTOASSIGN)
id.setAutoAssigned(true);
id.setNotNull(true);
pkCols =
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 = table.getPrimaryKey();
if (pk == null) {
DBIdentifier pkname = DBIdentifier.NULL;
if (true)
pkname =
cls.getMappingRepository().getMappingDefaults()
.getPrimaryKeyIdentifier(cls, table);
pk = table.addPrimaryKey(pkname);
pk.setLogical(!adapt);
if (pkCols != null)
pk.setColumns(pkCols);
}
// set joinable
if (cls.getIdentityType() == ClassMapping.ID_DATASTORE)
cls.setJoinable(cls.getPrimaryKeyColumns()[0],
new IdentityJoinable(cls));
}
}
-----Oorspronkelijk bericht-----
Van: Henno Vermeulen [mailto:henno@huizemolenaar.nl]
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 unique in the database!
-----Oorspronkelijk bericht-----
Van: Henno Vermeulen [mailto:henno@huizemolenaar.nl]
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 very 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 suggest?
All of my entities inherit an autogenerated primary key from a single mapped superclass.
PS. I know of the @ForeignKey annotation but it has a few drawbacks:
- adds unnecessary boilerplate code (name can be autogenerated without numbers) that takes time to maintain.
- Not sure if it works for mappings such as element collections and Maps
- Obviously won't work for primary keys, especially not if it is defined in one place in a mapped superclass
RE: repeatable autogenerate names for key constraints
Posted by Henno Vermeulen <he...@huizemolenaar.nl>.
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 unique in the database!
-----Oorspronkelijk bericht-----
Van: Henno Vermeulen [mailto:henno@huizemolenaar.nl]
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 very 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 suggest?
All of my entities inherit an autogenerated primary key from a single mapped superclass.
PS. I know of the @ForeignKey annotation but it has a few drawbacks:
- adds unnecessary boilerplate code (name can be autogenerated without numbers) that takes time to maintain.
- Not sure if it works for mappings such as element collections and Maps
- Obviously won't work for primary keys, especially not if it is defined in one place in a mapped superclass