mirror of https://github.com/apache/openjpa.git
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:
parent
991f5a7dce
commit
aa1516f04b
|
@ -95,6 +95,21 @@ public abstract class MappingInfo
|
|||
public List getColumns() {
|
||||
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.
|
||||
|
@ -531,10 +546,19 @@ public abstract class MappingInfo
|
|||
boolean fill = ((MappingRepository) context.getRepository()).
|
||||
getMappingDefaults().defaultMissingInfo();
|
||||
if ((!given.isEmpty() || (!adapt && !fill))
|
||||
&& given.size() != tmplates.length)
|
||||
throw new MetaDataException(_loc.get(prefix + "-num-cols",
|
||||
context, String.valueOf(tmplates.length),
|
||||
String.valueOf(given.size())));
|
||||
&& given.size() != tmplates.length) {
|
||||
// also consider when this info has columns from multiple tables
|
||||
given = getColumns(table.getName());
|
||||
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];
|
||||
_io = null;
|
||||
|
@ -547,6 +571,11 @@ public abstract class MappingInfo
|
|||
}
|
||||
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.
|
||||
|
|
|
@ -18,12 +18,23 @@
|
|||
*/
|
||||
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.SuperclassVersionStrategy;
|
||||
import org.apache.openjpa.jdbc.schema.Column;
|
||||
import org.apache.openjpa.jdbc.schema.Index;
|
||||
import org.apache.openjpa.jdbc.schema.SchemaGroup;
|
||||
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
|
||||
|
@ -36,17 +47,59 @@ import org.apache.openjpa.jdbc.schema.Table;
|
|||
public class VersionMappingInfo
|
||||
extends MappingInfo {
|
||||
|
||||
private static final Localizer _loc = Localizer.forPackage
|
||||
(VersionMappingInfo.class);
|
||||
/**
|
||||
* 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) {
|
||||
Table table = version.getClassMapping().getTable();
|
||||
if (spansMultipleTables(templates))
|
||||
return getMultiTableColumns(version, templates, adapt);
|
||||
Table table = getSingleTable(version, templates);
|
||||
version.getMappingRepository().getMappingDefaults().populateColumns
|
||||
(version, table, tmplates);
|
||||
return createColumns(version, null, tmplates, table, adapt);
|
||||
(version, table, templates);
|
||||
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()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index to set on the version columns, or null if none.
|
||||
*/
|
||||
|
@ -86,4 +139,30 @@ public class VersionMappingInfo
|
|||
&& cls.getJoinablePCSuperclassMapping() == null))
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.openjpa.jdbc.meta.VersionMappingInfo;
|
|||
import org.apache.openjpa.jdbc.schema.Column;
|
||||
import org.apache.openjpa.jdbc.schema.ColumnIO;
|
||||
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.Row;
|
||||
import org.apache.openjpa.jdbc.sql.RowManager;
|
||||
|
@ -48,6 +49,7 @@ import serp.util.Numbers;
|
|||
* Uses a single column and corresponding version object.
|
||||
*
|
||||
* @author Marc Prud'hommeaux
|
||||
* @author Pinaki Poddar
|
||||
*/
|
||||
public abstract class ColumnVersionStrategy
|
||||
extends AbstractVersionStrategy {
|
||||
|
@ -110,19 +112,23 @@ public abstract class ColumnVersionStrategy
|
|||
/**
|
||||
* Compare each element of the given arrays that must be of equal size.
|
||||
*
|
||||
* @return If each element comparison results into same sign then returns
|
||||
* that sign. If some elements compare equal and all the rest has the same
|
||||
* sign then return that sign. Otherwise, return 1.
|
||||
* @return If any element of a1 is later than corresponding element of
|
||||
* a2 then return 1 i.e. a1 as a whole is later than a2.
|
||||
* 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) {
|
||||
if (a1.length != a2.length)
|
||||
throw new InternalException();
|
||||
Set<Integer> comps = new HashSet<Integer>();
|
||||
for (int i = 0; i < a1.length; i++)
|
||||
comps.add(sign(compare(a1[i], a2[i])));
|
||||
if (comps.size() == 1 || (comps.size() == 2 && comps.remove(0)))
|
||||
return comps.iterator().next();
|
||||
return 1;
|
||||
int total = 0;
|
||||
for (int i = 0; i < a1.length; i++) {
|
||||
int c = compare(a1[i], a2[i]);
|
||||
if (c > 0)
|
||||
return 1;
|
||||
total += c;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
int sign(int i) {
|
||||
|
@ -144,11 +150,12 @@ public abstract class ColumnVersionStrategy
|
|||
for (int i = 0; i < info.getColumns().size(); i++) {
|
||||
templates[i] = new Column();
|
||||
Column infoColumn = (Column)info.getColumns().get(i);
|
||||
templates[i].setTableName(infoColumn.getTableName());
|
||||
templates[i].setType(infoColumn.getType());
|
||||
templates[i].setSize(infoColumn.getSize());
|
||||
templates[i].setDecimalDigits(infoColumn.getDecimalDigits());
|
||||
templates[i].setJavaType(getJavaType(i));
|
||||
templates[i].setName("versn" +i);
|
||||
templates[i].setName(infoColumn.getName());
|
||||
}
|
||||
Column[] cols = info.getColumns(vers, templates, adapt);
|
||||
for (int i = 0; i < cols.length; i++)
|
||||
|
@ -175,12 +182,11 @@ public abstract class ColumnVersionStrategy
|
|||
Column[] cols = vers.getColumns();
|
||||
ColumnIO io = vers.getColumnIO();
|
||||
Object initial = nextVersion(null);
|
||||
Row row = rm.getRow(vers.getClassMapping().getTable(),
|
||||
Row.ACTION_INSERT, sm, true);
|
||||
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_INSERT, sm, true);
|
||||
if (io.isInsertable(i, initial == null))
|
||||
row.setObject(cols[i], getColumnValue(initial, i));
|
||||
|
||||
}
|
||||
// set initial version into state manager
|
||||
Object nextVersion;
|
||||
nextVersion = initial;
|
||||
|
@ -197,12 +203,11 @@ public abstract class ColumnVersionStrategy
|
|||
Object curVersion = sm.getVersion();
|
||||
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
|
||||
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())
|
||||
row.whereObject(cols[i], getColumnValue(curVersion, i));
|
||||
if (vers.getColumnIO().isUpdatable(i, nextVersion == null))
|
||||
|
@ -215,14 +220,14 @@ public abstract class ColumnVersionStrategy
|
|||
|
||||
public void delete(OpenJPAStateManager sm, JDBCStore store, RowManager rm)
|
||||
throws SQLException {
|
||||
Row row = rm.getRow(vers.getClassMapping().getTable(),
|
||||
Row.ACTION_DELETE, sm, true);
|
||||
row.setFailedObject(sm.getManagedInstance());
|
||||
Column[] cols = vers.getColumns();
|
||||
|
||||
Object curVersion = sm.getVersion();
|
||||
Object cur;
|
||||
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);
|
||||
// set where and update conditions on row
|
||||
if (cur != null)
|
||||
|
|
|
@ -414,4 +414,6 @@ unique-missing-column: The column "{1}" in a unique constraint in "{0}" on \
|
|||
table "{2}" can not be found in the list of available columns "{3}".
|
||||
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 \
|
||||
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.
|
|
@ -807,6 +807,7 @@ public class AnnotationPersistenceMappingParser
|
|||
*/
|
||||
private static Column newColumn(VersionColumn anno) {
|
||||
Column col = new Column();
|
||||
col.setTableName(anno.table());
|
||||
if (!StringUtils.isEmpty(anno.name()))
|
||||
col.setName(anno.name());
|
||||
if (anno.precision() != 0)
|
||||
|
|
|
@ -49,4 +49,6 @@ public @interface VersionColumn {
|
|||
int precision() default 0; // decimal precision
|
||||
|
||||
int scale() default 0; // decimal scale
|
||||
|
||||
String table() default "";
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ package org.apache.openjpa.persistence.jdbc.annotations;
|
|||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.apache.openjpa.persistence.jdbc.VersionColumn;
|
||||
import org.apache.openjpa.persistence.jdbc.VersionColumns;
|
||||
|
@ -36,6 +37,7 @@ import org.apache.openjpa.persistence.jdbc.VersionStrategy;
|
|||
*
|
||||
*/
|
||||
@Entity
|
||||
@Table(name="MCV")
|
||||
@VersionStrategy("version-numbers")
|
||||
@VersionColumns({
|
||||
@VersionColumn(name="v1"),
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
package org.apache.openjpa.persistence.jdbc.annotations;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.apache.openjpa.jdbc.meta.ClassMapping;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests numeric version spanning multiple columns.
|
||||
* Tests numeric version spanning multiple columns and those columns spanning
|
||||
* multiple tables.
|
||||
*
|
||||
* @author Pinaki Poddar
|
||||
*/
|
||||
public class TestMultiColumnVersion extends SingleEMFTestCase {
|
||||
public void setUp() {
|
||||
setUp(MultiColumnVersionPC.class, CLEAR_TABLES);
|
||||
setUp(MultiColumnVersionPC.class, MultiColumnSecondaryVersionPC.class,
|
||||
CLEAR_TABLES);
|
||||
}
|
||||
|
||||
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());
|
||||
assertTrue(mapping.getVersion().getStrategy()
|
||||
instanceof MultiColumnVersionStrategy);
|
||||
}
|
||||
|
||||
public void testVersionOnPersistAndUpdate() {
|
||||
public void testVersionOnPersistAndUpdateForSingleTable() {
|
||||
OpenJPAEntityManager em = emf.createEntityManager();
|
||||
em.getTransaction().begin();
|
||||
MultiColumnVersionPC pc = new MultiColumnVersionPC();
|
||||
|
@ -59,7 +65,7 @@ public class TestMultiColumnVersion extends SingleEMFTestCase {
|
|||
assertVersionEquals(new Number[]{2,2, 2.0f}, em.getVersion(pc));
|
||||
}
|
||||
|
||||
public void testConcurrentOptimisticUpdateFails() {
|
||||
public void testConcurrentOptimisticUpdateFailsForSingleTable() {
|
||||
OpenJPAEntityManager em1 = emf.createEntityManager();
|
||||
em1.getTransaction().begin();
|
||||
OpenJPAEntityManager em2 = emf.createEntityManager();
|
||||
|
@ -88,7 +94,7 @@ public class TestMultiColumnVersion extends SingleEMFTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testConcurrentOptimisticReadSucceeds() {
|
||||
public void testConcurrentOptimisticReadSucceedsForSingleTable() {
|
||||
OpenJPAEntityManager em1 = emf.createEntityManager();
|
||||
em1.getTransaction().begin();
|
||||
OpenJPAEntityManager em2 = emf.createEntityManager();
|
||||
|
@ -108,6 +114,71 @@ public class TestMultiColumnVersion extends SingleEMFTestCase {
|
|||
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) {
|
||||
assertTrue(expected.getClass().isArray());
|
||||
assertTrue(actual.getClass().isArray());
|
||||
|
|
|
@ -1629,6 +1629,11 @@ values. Each <classname>VersionColumn</classname> has the following properties:
|
|||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>String table</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<literal>int length</literal>
|
||||
</para>
|
||||
</listitem>
|
||||
|
@ -1674,6 +1679,46 @@ strategy. You can choose a different strategy with the <classname>
|
|||
VersionStrategy</classname> annotation described in
|
||||
<xref linkend="version-strategy"/>.
|
||||
</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 id="ref_guide_mapping_jpa_columns">
|
||||
<title>
|
||||
|
|
Loading…
Reference in New Issue