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
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
|
@ -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)
|
||||||
|
|
|
@ -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 "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.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"),
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue