OPENJPA-697: Add new capabilities to support version columns to spread across primary and secondary tables

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@690346 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2008-08-29 17:46:15 +00:00
parent 991f5a7dce
commit aa1516f04b
10 changed files with 368 additions and 37 deletions

View File

@ -96,6 +96,21 @@ public abstract class MappingInfo
return (_cols == null) ? Collections.EMPTY_LIST : _cols; return (_cols == null) ? Collections.EMPTY_LIST : _cols;
} }
/**
* Gets the columns whose table name matches the given table name.
*/
public List getColumns(String tableName) {
if (_cols == null)
return Collections.EMPTY_LIST;
List result = new ArrayList();
for (Object col : _cols) {
if (StringUtils.equals(((Column)col).getTableName(),
tableName))
result.add(col);
}
return result;
}
/** /**
* Raw column data. * Raw column data.
*/ */
@ -531,10 +546,19 @@ public abstract class MappingInfo
boolean fill = ((MappingRepository) context.getRepository()). boolean fill = ((MappingRepository) context.getRepository()).
getMappingDefaults().defaultMissingInfo(); getMappingDefaults().defaultMissingInfo();
if ((!given.isEmpty() || (!adapt && !fill)) if ((!given.isEmpty() || (!adapt && !fill))
&& given.size() != tmplates.length) && given.size() != tmplates.length) {
throw new MetaDataException(_loc.get(prefix + "-num-cols", // also consider when this info has columns from multiple tables
context, String.valueOf(tmplates.length), given = getColumns(table.getName());
String.valueOf(given.size()))); if ((!adapt && !fill) && given.size() != tmplates.length) {
// try default table
given = getColumns("");
if ((!adapt && !fill) && given.size() != tmplates.length) {
throw new MetaDataException(_loc.get(prefix + "-num-cols",
context, String.valueOf(tmplates.length),
String.valueOf(given.size())));
}
}
}
Column[] cols = new Column[tmplates.length]; Column[] cols = new Column[tmplates.length];
_io = null; _io = null;
@ -548,6 +572,11 @@ public abstract class MappingInfo
return cols; return cols;
} }
boolean canMerge(List given, Column[] templates, boolean adapt, boolean fill) {
return !((!given.isEmpty() || (!adapt && !fill))
&& given.size() != templates.length);
}
/** /**
* Set the proper internal column I/O metadata for the given column's flags. * Set the proper internal column I/O metadata for the given column's flags.
*/ */

View File

@ -18,12 +18,23 @@
*/ */
package org.apache.openjpa.jdbc.meta; package org.apache.openjpa.jdbc.meta;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.meta.strats.NoneVersionStrategy; import org.apache.openjpa.jdbc.meta.strats.NoneVersionStrategy;
import org.apache.openjpa.jdbc.meta.strats.SuperclassVersionStrategy; import org.apache.openjpa.jdbc.meta.strats.SuperclassVersionStrategy;
import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Index; import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.SchemaGroup; import org.apache.openjpa.jdbc.schema.SchemaGroup;
import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.UserException;
/** /**
* Information about the mapping from a version indicator to the schema, in * Information about the mapping from a version indicator to the schema, in
@ -36,15 +47,57 @@ import org.apache.openjpa.jdbc.schema.Table;
public class VersionMappingInfo public class VersionMappingInfo
extends MappingInfo { extends MappingInfo {
private static final Localizer _loc = Localizer.forPackage
(VersionMappingInfo.class);
/** /**
* Return the columns set for this version, based on the given templates. * Return the columns set for this version, based on the given templates.
*/ */
public Column[] getColumns(Version version, Column[] tmplates, public Column[] getColumns(Version version, Column[] templates,
boolean adapt) { boolean adapt) {
Table table = version.getClassMapping().getTable(); if (spansMultipleTables(templates))
return getMultiTableColumns(version, templates, adapt);
Table table = getSingleTable(version, templates);
version.getMappingRepository().getMappingDefaults().populateColumns version.getMappingRepository().getMappingDefaults().populateColumns
(version, table, tmplates); (version, table, templates);
return createColumns(version, null, tmplates, table, adapt); return createColumns(version, null, templates, table, adapt);
}
/**
* Return the columns set for this version when the columns are spread
* across multiple tables.
*/
public Column[] getMultiTableColumns(Version vers, Column[] templates,
boolean adapt) {
Table primaryTable = vers.getClassMapping().getTable();
List<String> secondaryTableNames = Arrays.asList(vers
.getClassMapping().getMappingInfo().getSecondaryTableNames());
Map<Table, List<Column>> assign = new HashMap<Table, List<Column>>();
for (Column col : templates) {
String tableName = col.getTableName();
Table table;
if (StringUtils.isEmpty(tableName)
|| tableName.equals(primaryTable.getName())) {
table = primaryTable;
} else if (secondaryTableNames.contains(tableName)) {
table = primaryTable.getSchema().getTable(tableName);
} else {
throw new UserException(_loc.get("bad-version-column-table",
col.getName(), tableName));
}
if (!assign.containsKey(table))
assign.put(table, new ArrayList<Column>());
assign.get(table).add(col);
}
MappingDefaults def = vers.getMappingRepository().getMappingDefaults();
List<Column> result = new ArrayList<Column>();
for (Table table : assign.keySet()) {
List<Column> cols = assign.get(table);
Column[] partTemplates = cols.toArray(new Column[cols.size()]);
def.populateColumns(vers, table, partTemplates);
result.addAll(Arrays.asList(createColumns(vers, null, partTemplates,
table, adapt)));
}
return result.toArray(new Column[result.size()]);
} }
/** /**
@ -86,4 +139,30 @@ public class VersionMappingInfo
&& cls.getJoinablePCSuperclassMapping() == null)) && cls.getJoinablePCSuperclassMapping() == null))
setStrategy(strat); setStrategy(strat);
} }
/**
* Affirms if the given columns belong to more than one tables.
*/
boolean spansMultipleTables(Column[] cols) {
if (cols == null || cols.length <= 1)
return false;
Set<String> tables = new HashSet<String>();
for (Column col : cols)
if (tables.add(col.getTableName()) && tables.size() > 1)
return true;
return false;
}
/**
* Gets the table where this version columns are mapped.
*/
private Table getSingleTable(Version version, Column[] cols) {
if (cols == null || cols.length == 0
|| StringUtils.isEmpty(cols[0].getTableName()))
return version.getClassMapping().getTable();
return version.getClassMapping().getTable().getSchema()
.getTable(cols[0].getTableName());
}
} }

View File

@ -31,6 +31,7 @@ import org.apache.openjpa.jdbc.meta.VersionMappingInfo;
import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ColumnIO; import org.apache.openjpa.jdbc.schema.ColumnIO;
import org.apache.openjpa.jdbc.schema.Index; import org.apache.openjpa.jdbc.schema.Index;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.sql.Result; import org.apache.openjpa.jdbc.sql.Result;
import org.apache.openjpa.jdbc.sql.Row; import org.apache.openjpa.jdbc.sql.Row;
import org.apache.openjpa.jdbc.sql.RowManager; import org.apache.openjpa.jdbc.sql.RowManager;
@ -48,6 +49,7 @@ import serp.util.Numbers;
* Uses a single column and corresponding version object. * Uses a single column and corresponding version object.
* *
* @author Marc Prud'hommeaux * @author Marc Prud'hommeaux
* @author Pinaki Poddar
*/ */
public abstract class ColumnVersionStrategy public abstract class ColumnVersionStrategy
extends AbstractVersionStrategy { extends AbstractVersionStrategy {
@ -110,19 +112,23 @@ public abstract class ColumnVersionStrategy
/** /**
* Compare each element of the given arrays that must be of equal size. * Compare each element of the given arrays that must be of equal size.
* *
* @return If each element comparison results into same sign then returns * @return If any element of a1 is later than corresponding element of
* that sign. If some elements compare equal and all the rest has the same * a2 then return 1 i.e. a1 as a whole is later than a2.
* sign then return that sign. Otherwise, return 1. * If each element of a1 is to equal corresponding element of a2 then return
* 0 i.e. a1 is as a whole equals to a2.
* else return a negative number i.e. a1 is earlier than a2.
*/ */
protected int compare(Object[] a1, Object[] a2) { protected int compare(Object[] a1, Object[] a2) {
if (a1.length != a2.length) if (a1.length != a2.length)
throw new InternalException(); throw new InternalException();
Set<Integer> comps = new HashSet<Integer>(); int total = 0;
for (int i = 0; i < a1.length; i++) for (int i = 0; i < a1.length; i++) {
comps.add(sign(compare(a1[i], a2[i]))); int c = compare(a1[i], a2[i]);
if (comps.size() == 1 || (comps.size() == 2 && comps.remove(0))) if (c > 0)
return comps.iterator().next(); return 1;
return 1; total += c;
}
return total;
} }
int sign(int i) { int sign(int i) {
@ -144,11 +150,12 @@ public abstract class ColumnVersionStrategy
for (int i = 0; i < info.getColumns().size(); i++) { for (int i = 0; i < info.getColumns().size(); i++) {
templates[i] = new Column(); templates[i] = new Column();
Column infoColumn = (Column)info.getColumns().get(i); Column infoColumn = (Column)info.getColumns().get(i);
templates[i].setTableName(infoColumn.getTableName());
templates[i].setType(infoColumn.getType()); templates[i].setType(infoColumn.getType());
templates[i].setSize(infoColumn.getSize()); templates[i].setSize(infoColumn.getSize());
templates[i].setDecimalDigits(infoColumn.getDecimalDigits()); templates[i].setDecimalDigits(infoColumn.getDecimalDigits());
templates[i].setJavaType(getJavaType(i)); templates[i].setJavaType(getJavaType(i));
templates[i].setName("versn" +i); templates[i].setName(infoColumn.getName());
} }
Column[] cols = info.getColumns(vers, templates, adapt); Column[] cols = info.getColumns(vers, templates, adapt);
for (int i = 0; i < cols.length; i++) for (int i = 0; i < cols.length; i++)
@ -175,12 +182,11 @@ public abstract class ColumnVersionStrategy
Column[] cols = vers.getColumns(); Column[] cols = vers.getColumns();
ColumnIO io = vers.getColumnIO(); ColumnIO io = vers.getColumnIO();
Object initial = nextVersion(null); Object initial = nextVersion(null);
Row row = rm.getRow(vers.getClassMapping().getTable(), for (int i = 0; i < cols.length; i++) {
Row.ACTION_INSERT, sm, true); Row row = rm.getRow(cols[i].getTable(), Row.ACTION_INSERT, sm, true);
for (int i = 0; i < cols.length; i++)
if (io.isInsertable(i, initial == null)) if (io.isInsertable(i, initial == null))
row.setObject(cols[i], getColumnValue(initial, i)); row.setObject(cols[i], getColumnValue(initial, i));
}
// set initial version into state manager // set initial version into state manager
Object nextVersion; Object nextVersion;
nextVersion = initial; nextVersion = initial;
@ -197,12 +203,11 @@ public abstract class ColumnVersionStrategy
Object curVersion = sm.getVersion(); Object curVersion = sm.getVersion();
Object nextVersion = nextVersion(curVersion); Object nextVersion = nextVersion(curVersion);
Row row = rm.getRow(vers.getClassMapping().getTable(),
Row.ACTION_UPDATE, sm, true);
row.setFailedObject(sm.getManagedInstance());
// set where and update conditions on row // set where and update conditions on row
for (int i = 0; i < cols.length; i++) { for (int i = 0; i < cols.length; i++) {
Row row = rm.getRow(cols[i].getTable(), Row.ACTION_UPDATE, sm, true);
row.setFailedObject(sm.getManagedInstance());
if (curVersion != null && sm.isVersionCheckRequired()) if (curVersion != null && sm.isVersionCheckRequired())
row.whereObject(cols[i], getColumnValue(curVersion, i)); row.whereObject(cols[i], getColumnValue(curVersion, i));
if (vers.getColumnIO().isUpdatable(i, nextVersion == null)) if (vers.getColumnIO().isUpdatable(i, nextVersion == null))
@ -215,14 +220,14 @@ public abstract class ColumnVersionStrategy
public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm) public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
throws SQLException { throws SQLException {
Row row = rm.getRow(vers.getClassMapping().getTable(),
Row.ACTION_DELETE, sm, true);
row.setFailedObject(sm.getManagedInstance());
Column[] cols = vers.getColumns(); Column[] cols = vers.getColumns();
Object curVersion = sm.getVersion(); Object curVersion = sm.getVersion();
Object cur; Object cur;
for (int i = 0; i < cols.length; i++) { for (int i = 0; i < cols.length; i++) {
Row row = rm.getRow(cols[i].getTable(),
Row.ACTION_DELETE, sm, true);
row.setFailedObject(sm.getManagedInstance());
cur = getColumnValue(curVersion, i); cur = getColumnValue(curVersion, i);
// set where and update conditions on row // set where and update conditions on row
if (cur != null) if (cur != null)

View File

@ -415,3 +415,5 @@ unique-missing-column: The column "{1}" in a unique constraint in "{0}" on \
unique-no-table: A unique constraint on table "{0}" can not be added to \ unique-no-table: A unique constraint on table "{0}" can not be added to \
mapping of class "{1}" because the table does neither match its primary \ mapping of class "{1}" because the table does neither match its primary \
table "{2}" nor any of its secondary table(s) "{3}". table "{2}" nor any of its secondary table(s) "{3}".
bad-version-column-table: One of the version column "{0}" has been associated \
with table "{1}", but no primary or secondary table of such name exists.

View File

@ -807,6 +807,7 @@ public class AnnotationPersistenceMappingParser
*/ */
private static Column newColumn(VersionColumn anno) { private static Column newColumn(VersionColumn anno) {
Column col = new Column(); Column col = new Column();
col.setTableName(anno.table());
if (!StringUtils.isEmpty(anno.name())) if (!StringUtils.isEmpty(anno.name()))
col.setName(anno.name()); col.setName(anno.name());
if (anno.precision() != 0) if (anno.precision() != 0)

View File

@ -49,4 +49,6 @@ public @interface VersionColumn {
int precision() default 0; // decimal precision int precision() default 0; // decimal precision
int scale() default 0; // decimal scale int scale() default 0; // decimal scale
String table() default "";
} }

View File

@ -0,0 +1,95 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.persistence.jdbc.annotations;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.Table;
import org.apache.openjpa.persistence.jdbc.VersionColumn;
import org.apache.openjpa.persistence.jdbc.VersionColumns;
import org.apache.openjpa.persistence.jdbc.VersionStrategy;
/**
* Persistent entity for testing multiple column numeric version strategy as set
* by <code>@VersionColumns</code> annotations and where the version columns are
* spread over primary and secondary table(s).
*
* @author Pinaki Poddar
*
*/
@Entity
@Table(name="MCSV")
@SecondaryTables({
@SecondaryTable(name = "MCSV1", pkJoinColumns=@PrimaryKeyJoinColumn(name="ID")),
@SecondaryTable(name = "MCSV2", pkJoinColumns=@PrimaryKeyJoinColumn(name="ID"))
})
@VersionStrategy("version-numbers")
@VersionColumns({
@VersionColumn(name = "v11", table="MCSV1"),
@VersionColumn(name = "v12", table="MCSV1"),
@VersionColumn(name = "v21", table="MCSV2"),
@VersionColumn(name = "v01") // default is the primary table
})
public class MultiColumnSecondaryVersionPC {
@Id
@GeneratedValue
private long id;
private String name;
@Column(table="MCSV1")
private String s1;
@Column(table="MCSV2")
private String s2;
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getS1() {
return s1;
}
public void setS1(String s1) {
this.s1 = s1;
}
public String getS2() {
return s2;
}
public void setS2(String s2) {
this.s2 = s2;
}
}

View File

@ -21,6 +21,7 @@ package org.apache.openjpa.persistence.jdbc.annotations;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table;
import org.apache.openjpa.persistence.jdbc.VersionColumn; import org.apache.openjpa.persistence.jdbc.VersionColumn;
import org.apache.openjpa.persistence.jdbc.VersionColumns; import org.apache.openjpa.persistence.jdbc.VersionColumns;
@ -36,6 +37,7 @@ import org.apache.openjpa.persistence.jdbc.VersionStrategy;
* *
*/ */
@Entity @Entity
@Table(name="MCV")
@VersionStrategy("version-numbers") @VersionStrategy("version-numbers")
@VersionColumns({ @VersionColumns({
@VersionColumn(name="v1"), @VersionColumn(name="v1"),

View File

@ -19,7 +19,6 @@
package org.apache.openjpa.persistence.jdbc.annotations; package org.apache.openjpa.persistence.jdbc.annotations;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.Arrays;
import org.apache.openjpa.jdbc.meta.ClassMapping; import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.meta.strats.MultiColumnVersionStrategy; import org.apache.openjpa.jdbc.meta.strats.MultiColumnVersionStrategy;
@ -27,23 +26,30 @@ import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.test.SingleEMFTestCase; import org.apache.openjpa.persistence.test.SingleEMFTestCase;
/** /**
* Tests numeric version spanning multiple columns. * Tests numeric version spanning multiple columns and those columns spanning
* multiple tables.
* *
* @author Pinaki Poddar * @author Pinaki Poddar
*/ */
public class TestMultiColumnVersion extends SingleEMFTestCase { public class TestMultiColumnVersion extends SingleEMFTestCase {
public void setUp() { public void setUp() {
setUp(MultiColumnVersionPC.class, CLEAR_TABLES); setUp(MultiColumnVersionPC.class, MultiColumnSecondaryVersionPC.class,
CLEAR_TABLES);
} }
public void testVersionStrategyIsSet() { public void testVersionStrategyIsSet() {
ClassMapping mapping = getMapping(MultiColumnVersionPC.class); assertStrategy(MultiColumnVersionPC.class);
assertStrategy(MultiColumnSecondaryVersionPC.class);
}
public void assertStrategy(Class cls) {
ClassMapping mapping = getMapping(cls);
assertNotNull(mapping.getVersion()); assertNotNull(mapping.getVersion());
assertTrue(mapping.getVersion().getStrategy() assertTrue(mapping.getVersion().getStrategy()
instanceof MultiColumnVersionStrategy); instanceof MultiColumnVersionStrategy);
} }
public void testVersionOnPersistAndUpdate() { public void testVersionOnPersistAndUpdateForSingleTable() {
OpenJPAEntityManager em = emf.createEntityManager(); OpenJPAEntityManager em = emf.createEntityManager();
em.getTransaction().begin(); em.getTransaction().begin();
MultiColumnVersionPC pc = new MultiColumnVersionPC(); MultiColumnVersionPC pc = new MultiColumnVersionPC();
@ -59,7 +65,7 @@ public class TestMultiColumnVersion extends SingleEMFTestCase {
assertVersionEquals(new Number[]{2,2, 2.0f}, em.getVersion(pc)); assertVersionEquals(new Number[]{2,2, 2.0f}, em.getVersion(pc));
} }
public void testConcurrentOptimisticUpdateFails() { public void testConcurrentOptimisticUpdateFailsForSingleTable() {
OpenJPAEntityManager em1 = emf.createEntityManager(); OpenJPAEntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin(); em1.getTransaction().begin();
OpenJPAEntityManager em2 = emf.createEntityManager(); OpenJPAEntityManager em2 = emf.createEntityManager();
@ -88,7 +94,7 @@ public class TestMultiColumnVersion extends SingleEMFTestCase {
} }
} }
public void testConcurrentOptimisticReadSucceeds() { public void testConcurrentOptimisticReadSucceedsForSingleTable() {
OpenJPAEntityManager em1 = emf.createEntityManager(); OpenJPAEntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin(); em1.getTransaction().begin();
OpenJPAEntityManager em2 = emf.createEntityManager(); OpenJPAEntityManager em2 = emf.createEntityManager();
@ -108,6 +114,71 @@ public class TestMultiColumnVersion extends SingleEMFTestCase {
em2.getTransaction().commit(); em2.getTransaction().commit();
} }
public void testVersionOnPersistAndUpdateForMultiTable() {
OpenJPAEntityManager em = emf.createEntityManager();
em.getTransaction().begin();
MultiColumnSecondaryVersionPC pc = new MultiColumnSecondaryVersionPC();
assertEquals(null, em.getVersion(pc));
em.persist(pc);
em.getTransaction().commit();
assertVersionEquals(new Number[]{1,1,1,1}, em.getVersion(pc));
em.getTransaction().begin();
pc.setName("updated");
em.merge(pc);
em.getTransaction().commit();
assertVersionEquals(new Number[]{2,2,2,2}, em.getVersion(pc));
}
public void testConcurrentOptimisticUpdateFailsForMultiTable() {
OpenJPAEntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();
OpenJPAEntityManager em2 = emf.createEntityManager();
em2.getTransaction().begin();
MultiColumnSecondaryVersionPC pc1 = new MultiColumnSecondaryVersionPC();
em1.persist(pc1);
em1.getTransaction().commit();
em1.getTransaction().begin();
Object oid = em1.getObjectId(pc1);
MultiColumnSecondaryVersionPC pc2 = em2.find(MultiColumnSecondaryVersionPC.class, oid);
assertVersionEquals(em1.getVersion(pc1), em2.getVersion(pc2));
pc1.setName("Updated in em1");
pc2.setName("Updated in em2");
em1.getTransaction().commit();
try {
em2.getTransaction().commit();
fail("Optimistic fail");
} catch (Exception e) {
} finally {
em2.close();
}
}
public void testConcurrentOptimisticReadSucceedsForMultiTable() {
OpenJPAEntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();
OpenJPAEntityManager em2 = emf.createEntityManager();
em2.getTransaction().begin();
MultiColumnSecondaryVersionPC pc1 = new MultiColumnSecondaryVersionPC();
em1.persist(pc1);
em1.getTransaction().commit();
em1.getTransaction().begin();
Object oid = em1.getObjectId(pc1);
MultiColumnSecondaryVersionPC pc2 = em2.find(MultiColumnSecondaryVersionPC.class, oid);
assertVersionEquals(em1.getVersion(pc1), em2.getVersion(pc2));
em1.getTransaction().commit();
em2.getTransaction().commit();
}
static void assertVersionEquals(Object expected, Object actual) { static void assertVersionEquals(Object expected, Object actual) {
assertTrue(expected.getClass().isArray()); assertTrue(expected.getClass().isArray());
assertTrue(actual.getClass().isArray()); assertTrue(actual.getClass().isArray());

View File

@ -1629,6 +1629,11 @@ values. Each <classname>VersionColumn</classname> has the following properties:
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<literal>String table</literal>
</para>
</listitem>
<listitem>
<para>
<literal>int length</literal> <literal>int length</literal>
</para> </para>
</listitem> </listitem>
@ -1674,6 +1679,46 @@ strategy. You can choose a different strategy with the <classname>
VersionStrategy</classname> annotation described in VersionStrategy</classname> annotation described in
<xref linkend="version-strategy"/>. <xref linkend="version-strategy"/>.
</para> </para>
<para>
If multiple columns are used for surrogate versioning, then each column,
by default, uses a version number. But column definition for each version
column can be set independently to other numeric types. The version values are
compared to detect optimistic concurrent modification. Such comparison must
determine whether a version value <literal>v1</literal> represents an earlier,
later or same with respect to another version value <literal>v2</literal>. While
result of such comparison is obvious for a single numeric column that
monotonically increases on each update, the same is not true when version value
is an array of numbers. By default, OpenJPA compares a version
<literal>v1</literal> as later than another version <literal>v2</literal>,
if any array element of <literal>v1</literal> is
later than the corresponding element of <literal>v2</literal>.
<literal>v1</literal> is equal to <literal>v2</literal> if every array element
is equal and <literal>v1</literal> is earlier to <literal>v1</literal> if some
elements of <literal>v1</literal> are earlier and rest are equal to corresponding
element of <literal>v2</literal>.
</para>
<para>
Multiple surrogate version columns can be spread across primary and secondary
tables. For example, following example shows 3 version columns
<literal>v01, v11, v12, v21</literal> defined across the primary and secondary tables of
a persistent entity
</para>
<programlisting>
@Entity
@Table(name="PRIMARY")
@SecondaryTables({
@SecondaryTable(name = "SECONDARY_1"),
@SecondaryTable(name = "SECONDARY_2")
})
@VersionStrategy("version-numbers")
@VersionColumns({
@VersionColumn(name = "v01") // default is the PRIMARY table
@VersionColumn(name = "v11", table="SECONDARY_1", columnDefinition="FLOAT", scale=3, precision=10),
@VersionColumn(name = "v12", table="SECONDARY_1"),
@VersionColumn(name = "v21", table="SECONDARY_2"),
})
</programlisting>
</section> </section>
<section id="ref_guide_mapping_jpa_columns"> <section id="ref_guide_mapping_jpa_columns">
<title> <title>