You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by ht...@apache.org on 2014/05/01 04:34:15 UTC
svn commit: r1591536 - in /openjpa/branches/2.1.x:
openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/
openjpa-kernel/src/main/java/org/apache/openjpa/kernel/
openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/
openjpa-...
Author: hthomann
Date: Thu May 1 02:34:15 2014
New Revision: 1591536
URL: http://svn.apache.org/r1591536
Log:
OPENJPA-2476: Fixed OptimisticLockEx due to rounding of a Timestamp
Added:
openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/
openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/
openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/TestTimestampOptLockEx.java
openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/VersionTSEntity.java
Modified:
openjpa/branches/2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
openjpa/branches/2.1.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java
openjpa/branches/2.1.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
Modified: openjpa/branches/2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java?rev=1591536&r1=1591535&r2=1591536&view=diff
==============================================================================
--- openjpa/branches/2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java (original)
+++ openjpa/branches/2.1.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java Thu May 1 02:34:15 2014
@@ -28,7 +28,6 @@ import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
-import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Array;
@@ -96,6 +95,7 @@ import org.apache.openjpa.jdbc.schema.Fo
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.Seq;
+import org.apache.openjpa.kernel.StateManagerImpl;
import org.apache.openjpa.kernel.exps.Path;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
@@ -1248,24 +1248,13 @@ public class DBDictionary
public void setTimestamp(PreparedStatement stmnt, int idx,
Timestamp val, Calendar cal, Column col)
throws SQLException {
- // ensure that we do not insert dates at a greater precision than
- // that at which they will be returned by a SELECT
- int rounded = (int) Math.round(val.getNanos() /
- (double) datePrecision);
- int nanos = rounded * datePrecision;
- if (nanos > 999999999) {
- // rollover to next second
- val.setTime(val.getTime() + 1000);
- nanos = 0;
- }
- Timestamp valForStmnt = new Timestamp(val.getTime());
- valForStmnt.setNanos(nanos);
-
+ val = StateManagerImpl.roundTimestamp(val, datePrecision);
+
if (cal == null)
- stmnt.setTimestamp(idx, valForStmnt);
+ stmnt.setTimestamp(idx, val);
else
- stmnt.setTimestamp(idx, valForStmnt, cal);
+ stmnt.setTimestamp(idx, val, cal);
}
/**
Modified: openjpa/branches/2.1.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.1.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java?rev=1591536&r1=1591535&r2=1591536&view=diff
==============================================================================
--- openjpa/branches/2.1.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java (original)
+++ openjpa/branches/2.1.x/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java Thu May 1 02:34:15 2014
@@ -25,6 +25,7 @@ import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Modifier;
+import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
@@ -34,7 +35,6 @@ import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.locks.ReentrantLock;
@@ -157,6 +157,8 @@ public class StateManagerImpl
private transient ReentrantLock _instanceLock = null;
+ private int _datePrecision = -1;
+
/**
* Constructor; supply id, type metadata, and owning persistence manager.
*/
@@ -729,13 +731,48 @@ public class StateManagerImpl
public void setNextVersion(Object version) {
assignVersionField(version);
}
+
+ public static Timestamp roundTimestamp(Timestamp val, int datePrecision) {
+ // ensure that we do not insert dates at a greater precision than
+ // that at which they will be returned by a SELECT
+ int rounded = (int) Math.round(val.getNanos() / (double) datePrecision);
+ long time = val.getTime();
+ int nanos = rounded * datePrecision;
+ if (nanos > 999999999) {
+ // rollover to next second
+ time = time + 1000;
+ nanos = 0;
+ }
+ val = new Timestamp(time);
+ val.setNanos(nanos);
+ return val;
+ }
+
private void assignVersionField(Object version) {
+
+ if (version instanceof Timestamp) {
+ if (_datePrecision == -1) {
+ try {
+ OpenJPAConfiguration conf = _broker.getConfiguration();
+ Class confCls = Class.forName("org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl");
+ if (confCls.isAssignableFrom(conf.getClass())) {
+ Object o = conf.getClass().getMethod("getDBDictionaryInstance").invoke(conf, (Object[]) null);
+ _datePrecision = o.getClass().getField("datePrecision").getInt(o);
+ } else {
+ _datePrecision = 1000;
+ }
+ } catch (Throwable e) {
+ _datePrecision = 1000;
+ }
+ }
+
+ version = roundTimestamp((Timestamp) version, _datePrecision);
+ }
_version = version;
FieldMetaData vfield = _meta.getVersionField();
if (vfield != null)
- store(vfield.getIndex(), JavaTypes.convert(version,
- vfield.getTypeCode()));
+ store(vfield.getIndex(), JavaTypes.convert(version, vfield.getTypeCode()));
}
public PCState getPCState() {
Added: openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/TestTimestampOptLockEx.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/TestTimestampOptLockEx.java?rev=1591536&view=auto
==============================================================================
--- openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/TestTimestampOptLockEx.java (added)
+++ openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/TestTimestampOptLockEx.java Thu May 1 02:34:15 2014
@@ -0,0 +1,129 @@
+/*
+ * 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.optlockex.timestamp;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityTransaction;
+
+import org.apache.openjpa.persistence.test.SingleEMFTestCase;
+
+/*
+ * Test create for JIRA OPENJPA-2476, see it for a very detailed
+ * description of the issue.
+ */
+public class TestTimestampOptLockEx extends SingleEMFTestCase {
+
+ @Override
+ public void setUp() {
+ // By default we'd round a Timestamp to the nearest millisecond on Oracle (see DBDictionary.datePrecision
+ // and DBDictionary.setTimestamp) and nearest microsecond on DB2 (see DB2Dictionary.datePrecision and
+ // DBDictionary.setTimestamp) when sending the value to the db...if we change datePrecision to 1, we round to
+ // the nearest nanosecond. On DB2 and Oracle, it appears the default precision is microseconds but it seems
+ // DB2 truncates (no rounding) to microsecond for anything it is given with greater precision, whereas Oracle
+ // rounds. So in the case of DB2, this test will pass if datePrecision=1, but still fails on Oracle.
+ // On the other hand, if we set the datePrecision to 1000000 and run against DB2, the test will fail.
+
+ // This test requires datePrecision to be set to the same precision as the Timestamp column.
+ // I've only been testing on Oracle and DB2 and not sure how other DBs treat a Timestamps precision
+ // by default. In VersionTSEntity I use a Timestamp(3) but this is not supported on, at least, Derby
+ // and older versions of DB2...at this time I'll enable only on Oracle.
+ setSupportedDatabases(org.apache.openjpa.jdbc.sql.OracleDictionary.class);
+ if (isTestsDisabled()) {
+ return;
+ }
+
+ // Set datePrecision=1000000 for Oracle since we are using Timestamp(3)....on Oracle
+ // the default is 1000000 so we shouldn't need to set it, but lets set it to future
+ // proof the test.
+ super.setUp(DROP_TABLES, "openjpa.jdbc.DBDictionary", "datePrecision=1000000", VersionTSEntity.class);
+ }
+
+ public void testUpdate() {
+ poplulate();
+ //This loop is necessary since we need a timestamp which has been rounded up
+ //by the database, or by OpenJPA such that the in-memory version of the Timestamp
+ //varies from that which is in the database.
+ for (int i = 0; i < 50000; i++) {
+ EntityManager em = emf.createEntityManager();
+ EntityTransaction tx = em.getTransaction();
+
+ // Find an existing VersionTSEntity:
+ // stored with microsecond precision, e.g. 2014-01-21 13:16:46.595428
+ VersionTSEntity t = em.find(VersionTSEntity.class, 1);
+
+ tx.begin();
+ t.setSomeInt(t.getSomeInt() + 1);
+ t = em.merge(t);
+
+ tx.commit();
+ // If this clear is removed the test works fine.
+ em.clear();
+
+ // Lets say at this point the 'in-memory' timestamp is: 2014-01-22 07:22:11.548778567. What we
+ // actually sent to the DB (via the previous merge) is by default rounded (see DBDictionary.setTimestamp)
+ // to the nearest millisecond on Oracle (see DBDictionary.datePrecision) and nearest microsecond on
+ // DB2 (see DB2Dictionary.datePrecision) when sending the value to the db.
+ // Therefore, what we actually send to the db is: 2014-01-22 07:22:11.548779 (for DB2) or
+ // 2014-01-22 07:22:11.549 (for Oracle). Notice in either case we rounded up.
+
+ // now, do a merge with the unchanged entity
+ tx = em.getTransaction();
+ tx.begin();
+
+ t = em.merge(t); // this results in a select of VersionTSEntity
+
+ //This 'fixes' the issue (but customer doesn't really want to add this):
+ //em.refresh(t);
+
+ // Here is where things get interesting.....an error will happen here when the timestamp
+ // has been rounded up, as I'll explain:
+ // As part of this merge/commit, we select the timestamp from the db to get its value
+ // (see method ColumnVersionStrategy.checkVersion below), i.e:
+ // 'SELECT t0.updateTimestamp FROM VersionTSEntity t0 WHERE t0.id = ?'.
+ // We then compare the 'in-memory' timestamp to that which we got back from the DB, i.e. on
+ // DB2 we compare:
+ // in-mem: 2014-01-22 07:22:11.548778567
+ // from db: 2014-01-22 07:22:11.548779
+ // Because these do not 'compare' properly (the db version is greater), we throw the OptimisticLockEx!!
+ // For completeness, lets look at an example where the timestamp is as follows after the above
+ // update: 2014-01-22 07:22:11.548771234. We would send to DB2
+ // the following value: 2014-01-22 07:22:11.548771. Then, as part of the very last merge/commit, we'd
+ // compare:
+ // in-mem: 2014-01-22 07:22:11.548771234
+ // from db: 2014-01-22 07:22:11.548771
+ // These two would 'compare' properly (the db version is lesser), as such we would not throw an
+ // OptLockEx and the test works fine.
+ tx.commit();
+ em.close();
+ }
+ }
+
+ public void poplulate(){
+ EntityManager em = emf.createEntityManager();
+ EntityTransaction tx = em.getTransaction();
+ tx.begin();
+ VersionTSEntity r = new VersionTSEntity();
+
+ r.setId(1L);
+ r.setSomeInt(0);
+ em.persist(r);
+ tx.commit();
+ em.close();
+ }
+}
Added: openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/VersionTSEntity.java
URL: http://svn.apache.org/viewvc/openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/VersionTSEntity.java?rev=1591536&view=auto
==============================================================================
--- openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/VersionTSEntity.java (added)
+++ openjpa/branches/2.1.x/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/optlockex/timestamp/VersionTSEntity.java Thu May 1 02:34:15 2014
@@ -0,0 +1,63 @@
+/*
+ * 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.optlockex.timestamp;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+
+@Entity
+public class VersionTSEntity implements Serializable {
+
+ private static final long serialVersionUID = 2948711625184868242L;
+
+ @Id
+ private Long id;
+
+ @Version
+ @Column(columnDefinition="TIMESTAMP(3)")
+ private Timestamp updateTimestamp;
+
+ private Integer someInt;
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Timestamp getUpdateTimestamp() {
+ return this.updateTimestamp;
+ }
+
+ public void setSomeInt(Integer someInt) {
+ this.someInt = someInt;
+
+ }
+
+ public Integer getSomeInt() {
+ return someInt;
+ }
+}
Modified: openjpa/branches/2.1.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
URL: http://svn.apache.org/viewvc/openjpa/branches/2.1.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml?rev=1591536&r1=1591535&r2=1591536&view=diff
==============================================================================
--- openjpa/branches/2.1.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml (original)
+++ openjpa/branches/2.1.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml Thu May 1 02:34:15 2014
@@ -1377,9 +1377,36 @@ This value is usually one million, meani
to store time values with a precision of one millisecond. Particular
databases may have more or less precision.
OpenJPA will round all time values to this degree of precision
-before storing them in the database.
-Defaults to 1000000.
- </para>
+before storing them in the database. This property can be set to one
+of the following precisions:
+ </para>
+ <itemizedlist>
+ <listitem>
+ <para>
+<literal>DECI</literal>: 100000000
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+<literal>CENIT</literal>: 10000000
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+<literal>MILLI (default precision)</literal>: 1000000
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+<literal>MICRO</literal>: 1000
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+<literal>NANO (max precision)</literal>: 1
+ </para>
+ </listitem>
+ </itemizedlist>
</listitem>
<listitem id="DBDictionary.DateMillisecondBehavior">
<para>