This is an automated email from the ASF dual-hosted git repository. solomax pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openjpa.git
The following commit(s) were added to refs/heads/master by this push: new 6f4cf5b11 OPENJPA-2930 Implements UUID and GenerationType.UUID strategy (#124) 6f4cf5b11 is described below commit 6f4cf5b11b5bc5ffa0da1925260cdb7fbf7b8fe5 Author: Paulo Cristovão de Araújo Silva Filho <pcris...@gmail.com> AuthorDate: Wed Feb 19 11:12:45 2025 -0300 OPENJPA-2930 Implements UUID and GenerationType.UUID strategy (#124) * [OPENJPA-2930] Implemented JPA 3.1 UUID support * added basic support for UUID as string or uuid field, per db support * added GenerationStrategy.UUID support using UUID-4 random * [OPENJPA-2930] Implements UUID support * Allows usage of UUID as a basic type * Allows usage of UUID as identity type * Allows GenerationType.AUTO and GenerationType.UUID usage on String and UUID fields * Generates type 4 UUID for @GeneratedValue String and UUID fields * Storage UUID fields as binary data on supporting pair db/jdbc driver * Storage UUID fields as canonical string representation when database or jdbc driver does not support UUID binary usage. * Updates manual * [OPENJPA-2930] Fixing issues found on review --- .../openjpa/jdbc/kernel/JDBCStoreManager.java | 1 + .../openjpa/jdbc/meta/MappingRepository.java | 1 + .../jdbc/meta/strats/ImmutableValueHandler.java | 14 + .../org/apache/openjpa/jdbc/sql/DBDictionary.java | 19 ++ .../org/apache/openjpa/jdbc/sql/H2Dictionary.java | 2 + .../apache/openjpa/jdbc/sql/HSQLDictionary.java | 3 + .../openjpa/jdbc/sql/PostgresDictionary.java | 4 +- .../org/apache/openjpa/enhance/PCEnhancer.java | 1 + .../apache/openjpa/kernel/StateManagerImpl.java | 8 + .../org/apache/openjpa/meta/ClassMetaData.java | 4 + .../java/org/apache/openjpa/meta/JavaTypes.java | 9 + .../org/apache/openjpa/meta/ValueStrategies.java | 9 + .../org/apache/openjpa/util/ApplicationIds.java | 7 + .../java/org/apache/openjpa/util/ImplHelper.java | 5 + .../main/java/org/apache/openjpa/util/UuidId.java | 77 +++++ .../generationtype/TestUuidGeneratedEntity.java | 322 +++++++++++++++++++++ .../generationtype/UuidGeneratedEntity.java | 96 ++++++ .../AnnotationPersistenceMetaDataParser.java | 17 +- .../src/doc/manual/jpa_overview_meta.xml | 13 +- openjpa-project/src/doc/manual/jpa_overview_pc.xml | 15 +- .../src/doc/manual/supported_databases.xml | 22 +- 21 files changed, 641 insertions(+), 8 deletions(-) diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java index 801bd528e..af0a689f9 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/JDBCStoreManager.java @@ -82,6 +82,7 @@ import org.apache.openjpa.util.InvalidStateException; import org.apache.openjpa.util.OpenJPAId; import org.apache.openjpa.util.StoreException; import org.apache.openjpa.util.UserException; +import org.apache.openjpa.util.UuidId; /** * StoreManager plugin that uses JDBC to store persistent data in a diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java index c8db77abe..a007759de 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingRepository.java @@ -1360,6 +1360,7 @@ public class MappingRepository extends MetaDataRepository { case JavaTypes.LOCAL_DATETIME: case JavaTypes.OFFSET_TIME: case JavaTypes.OFFSET_DATETIME: + case JavaTypes.UUID_OBJ: return ImmutableValueHandler.getInstance(); case JavaTypes.STRING: if (isClob(val, true)) diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ImmutableValueHandler.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ImmutableValueHandler.java index 362d18de6..f28f703af 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ImmutableValueHandler.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/strats/ImmutableValueHandler.java @@ -65,6 +65,8 @@ public class ImmutableValueHandler extends AbstractValueHandler { col.setIdentifier(name); if (vm.getTypeCode() == JavaTypes.DATE) col.setJavaType(JavaSQLTypes.getDateTypeCode(vm.getType())); + else if (vm.getTypeCode() == JavaTypes.UUID_OBJ) + updateUUIDColumn(vm, col); else col.setJavaType(vm.getTypeCode()); return new Column[]{ col }; @@ -94,6 +96,7 @@ public class ImmutableValueHandler extends AbstractValueHandler { case JavaTypes.OFFSET_DATETIME: case JavaTypes.BIGINTEGER: case JavaTypes.LOCALE: + case JavaTypes.UUID_OBJ: return true; default: return false; @@ -117,4 +120,15 @@ public class ImmutableValueHandler extends AbstractValueHandler { // honor the user's null-value=default return JavaSQLTypes.getEmptyValue(vm.getTypeCode()); } + + private void updateUUIDColumn(ValueMapping vm, Column col) { + DBDictionary dict = vm.getMappingRepository().getDBDictionary(); + if (dict.supportsUuidType) { + col.setJavaType(vm.getTypeCode()); + col.setSize(-1); + } else { + col.setJavaType(JavaTypes.STRING); + col.setSize(36); + } + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index 537c6a286..fdfd15afa 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -67,6 +67,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.UUID; import javax.sql.DataSource; @@ -397,6 +398,7 @@ public class DBDictionary public String varcharTypeName = "VARCHAR"; public String xmlTypeName = "XML"; public String xmlTypeEncoding = "UTF-8"; + public String uuidTypeName = "UUID"; public String getStringVal = ""; // schema metadata @@ -493,6 +495,8 @@ public class DBDictionary protected String defaultSchemaName = null; private String conversionKey = null; + public boolean supportsUuidType = false; + // Naming utility and naming rules private DBIdentifierUtil namingUtil = null; private Map<String, IdentifierRule> namingRules = new HashMap<>(); @@ -1628,6 +1632,9 @@ public class DBDictionary } else setTimestamp(stmnt, idx, (Timestamp) val, null, col); break; + case JavaTypes.UUID_OBJ: + setObject(stmnt, idx, val, Types.OTHER, col); + break; default: if (col != null && (col.getType() == Types.BLOB || col.getType() == Types.VARBINARY)) @@ -1733,6 +1740,10 @@ public class DBDictionary else if (val instanceof Reader) setCharacterStream(stmnt, idx, (Reader) val, (sized == null) ? 0 : sized.size, col); + else if (val instanceof UUID && supportsUuidType) + setObject(stmnt, idx, (UUID) val, Types.OTHER, col); + else if (val instanceof UUID && !supportsUuidType) + setString(stmnt, idx, ((UUID) val).toString(), col); else throw new UserException(_loc.get("bad-param", val.getClass())); } @@ -1956,6 +1967,14 @@ public class DBDictionary if (col.isAutoAssigned() && autoAssignTypeName != null) return appendSize(col, autoAssignTypeName); + if (col.getJavaType() == JavaTypes.UUID_OBJ) { + if (supportsUuidType) + return appendSize(col, uuidTypeName); + else { + return appendSize(col, getTypeName(Types.VARCHAR)); + } + } + return appendSize(col, getTypeName(col.getType())); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/H2Dictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/H2Dictionary.java index 8e10535c0..a541b12b6 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/H2Dictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/H2Dictionary.java @@ -144,6 +144,7 @@ public class H2Dictionary extends DBDictionary { "UNKNOWN", "USER", "USING", + "UUID", "VALUE", "VALUES", "WHEN", @@ -238,6 +239,7 @@ public class H2Dictionary extends DBDictionary { "IS", "JOIN", "LIKE", "LIMIT", "MINUS", "NATURAL", "NOT", "NULL", "OFFSET", "ON", "ORDER", "PRIMARY", "ROWNUM", "SELECT", "SYSDATE", "TRUE", "UNION", "UNIQUE", "WHERE", "WITH", })); + supportsUuidType = true; } @Override diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/HSQLDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/HSQLDictionary.java index 34bbb7418..766a8ac77 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/HSQLDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/HSQLDictionary.java @@ -145,6 +145,9 @@ public class HSQLDictionary extends DBDictionary { packageName = "org.hsqldb.Trace"; fieldName = "VIOLATION_OF_UNIQUE_INDEX"; } + if (dbMajorVersion > 2 || (dbMajorVersion == 2 && dbMinorVersion >= 4)) { + supportsUuidType = true; + } try { Class<?> cls = Class.forName(packageName); Field fld = cls.getField(fieldName); diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/PostgresDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/PostgresDictionary.java index 9fbdbad3f..f03036523 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/PostgresDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/PostgresDictionary.java @@ -216,6 +216,8 @@ public class PostgresDictionary extends DBDictionary { // PostgreSQL requires to escape search strings requiresSearchStringEscapeForLike = true; + supportsUuidType = true; + uuidTypeName = "UUID"; } @@ -305,7 +307,7 @@ public class PostgresDictionary extends DBDictionary { public void setNull(PreparedStatement stmnt, int idx, int colType, Column col) throws SQLException { - if (col != null && col.isXML()) { + if (col != null && (col.isXML() || col.getJavaType() == JavaTypes.UUID_OBJ)) { stmnt.setNull(idx, Types.OTHER); return; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java index 6fd062dfb..f413b63b2 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java @@ -4927,6 +4927,7 @@ public class PCEnhancer { case JavaTypes.MAP: case JavaTypes.OBJECT: case JavaTypes.CALENDAR: + case JavaTypes.UUID_OBJ: // if (sm != null) // sm.proxyDetachedDeserialized (<index>); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java index a9e619b68..e83b8d259 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/StateManagerImpl.java @@ -36,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Objects; import java.util.TimeZone; +import java.util.UUID; import java.util.concurrent.locks.ReentrantLock; import org.apache.openjpa.conf.OpenJPAConfiguration; @@ -2905,6 +2906,13 @@ public class StateManagerImpl implements OpenJPAStateManager, Serializable { case JavaTypes.STRING: fm.storeStringField(field, (String) val); break; + case JavaTypes.UUID_OBJ: + if (val instanceof String) { + fm.storeObjectField(field, (UUID) UUID.fromString((String) val)); + } else if (val instanceof UUID) { + fm.storeObjectField(field, (UUID) val); + } + break; default: fm.storeObjectField(field, val); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java index 655a3cf39..52e598bc0 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ClassMetaData.java @@ -71,6 +71,7 @@ import org.apache.openjpa.util.OpenJPAId; import org.apache.openjpa.util.ShortId; import org.apache.openjpa.util.StringId; import org.apache.openjpa.util.UnsupportedException; +import org.apache.openjpa.util.UuidId; /** @@ -559,6 +560,9 @@ public class ClassMetaData case JavaTypes.BOOLEAN_OBJ: _objectId = BooleanId.class; break; + case JavaTypes.UUID_OBJ: + _objectId = UuidId.class; + break; } return _objectId; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/JavaTypes.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/JavaTypes.java index c0b9b8a3f..4c2907267 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/JavaTypes.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/JavaTypes.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.UUID; import org.apache.openjpa.enhance.PersistenceCapable; import org.apache.openjpa.lib.meta.CFMetaDataParser; @@ -96,6 +97,7 @@ public class JavaTypes { public static final int OFFSET_TIME = 36; public static final int OFFSET_DATETIME = 37; + public static final int UUID_OBJ = 38; private static final Localizer _loc = Localizer.forPackage(JavaTypes.class); @@ -131,6 +133,8 @@ public class JavaTypes { _typeCodes.put(LocalDateTime.class, LOCAL_DATETIME); _typeCodes.put(OffsetTime.class, OFFSET_TIME); _typeCodes.put(OffsetDateTime.class, OFFSET_DATETIME); + + _typeCodes.put(UUID.class, UUID_OBJ); } /** @@ -417,6 +421,11 @@ public class JavaTypes { if (val instanceof String) return Short.valueOf(val.toString()); return val; + case UUID_OBJ: + if (val instanceof String) { + return UUID.fromString((String) val); + } + return (UUID) val; case STRING: return val.toString(); default: diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueStrategies.java b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueStrategies.java index 3aad2e89f..6f818c5b5 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueStrategies.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/meta/ValueStrategies.java @@ -77,6 +77,13 @@ public class ValueStrategies { */ public static final int UUID_TYPE4_HEX = 8; + /** + * JPA 3.1 native UUID strategy + */ + public static final int UUID_JPA = 9; + + public static final int UUID_TYPE4_CANON = 10; + private static final Localizer _loc = Localizer.forPackage (ValueStrategies.class); @@ -93,6 +100,8 @@ public class ValueStrategies { _map.put("uuid-hex", UUID_HEX); _map.put("uuid-type4-string", UUID_TYPE4_STRING); _map.put("uuid-type4-hex", UUID_TYPE4_HEX); + _map.put("uuid-jpa", UUID_JPA); + _map.put("uuid-type4-canon", UUID_TYPE4_CANON); } /** diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ApplicationIds.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ApplicationIds.java index 7346d080f..78820d0e4 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ApplicationIds.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ApplicationIds.java @@ -25,6 +25,7 @@ import java.math.BigInteger; import java.security.AccessController; import java.security.PrivilegedActionException; import java.util.Date; +import java.util.UUID; import org.apache.openjpa.enhance.FieldManager; import org.apache.openjpa.enhance.PCRegistry; @@ -208,6 +209,12 @@ public class ApplicationIds { throw new ClassCastException("!(x instanceof Boolean)"); return new BooleanId(meta.getDescribedType(), val == null ? false : (Boolean)val); + case JavaTypes.UUID_OBJ: + if (convert && (val instanceof String)) + return new UuidId(meta.getDescribedType(), UUID.fromString((String) val)); + else if (val instanceof UUID) + return new UuidId(meta.getDescribedType(), (UUID) val); + throw new ClassCastException(String.format("Could not convert [%s] to UUID", val.getClass().getCanonicalName())); default: throw new InternalException(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java index 9d30db4ff..1c106ca4c 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ImplHelper.java @@ -23,6 +23,7 @@ import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.UUID; import org.apache.openjpa.conf.OpenJPAConfiguration; import org.apache.openjpa.enhance.ManagedInstanceProvider; @@ -169,6 +170,10 @@ public class ImplHelper { return UUIDGenerator.nextString(UUIDGenerator.TYPE4); case ValueStrategies.UUID_TYPE4_HEX: return UUIDGenerator.nextHex(UUIDGenerator.TYPE4); + case ValueStrategies.UUID_TYPE4_CANON: + return UUID.randomUUID().toString(); + case ValueStrategies.UUID_JPA: + return UUID.randomUUID(); default: return null; } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/UuidId.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/UuidId.java new file mode 100644 index 000000000..fca7af49e --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/UuidId.java @@ -0,0 +1,77 @@ +/* + * 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.util; + +import java.util.UUID; + +/** + * Identity type appropriate for UUID primary key fields and shared + * id classes. + * + * @author Abe White + * @author Paulo Cristovão Filho + * @author Max Solodovnik + */ +public final class UuidId + extends OpenJPAId { + + + private static final long serialVersionUID = 1L; + private UUID _key; + + public UuidId(Class<?> cls, UUID key) { + super(cls); + _key = key; + } + + public UuidId(Class<?> cls, UUID key, boolean subs) { + super(cls, subs); + _key = key; + } + + public UUID getId() { + return _key; + } + + /** + * Allow utilities in this package to mutate id. + */ + void setId(UUID id) { + _key = id; + } + + @Override + public Object getIdObject() { + return _key; + } + + @Override + protected int idHash() { + return (_key == null) ? 0 : _key.hashCode(); + } + + @Override + protected boolean idEquals(OpenJPAId o) { + if (!(o instanceof UuidId)) { + return false; + } + Object key = ((UuidId) o)._key; + return (_key == null) ? key == null : _key.equals(key); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestUuidGeneratedEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestUuidGeneratedEntity.java new file mode 100644 index 000000000..9796bea0d --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/TestUuidGeneratedEntity.java @@ -0,0 +1,322 @@ +/* + * 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.generationtype; + +import jakarta.persistence.EntityManager; + +import java.util.List; +import java.util.UUID; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.jdbc.meta.ClassMapping; +import org.apache.openjpa.jdbc.schema.Column; +import org.apache.openjpa.jdbc.sql.DBDictionary; +import org.apache.openjpa.meta.JavaTypes; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +public class TestUuidGeneratedEntity extends SingleEMFTestCase { + + DBDictionary _dict; + + @Override + public void setUp() { + setUp(UuidGeneratedEntity.class, CLEAR_TABLES); + _dict = ((JDBCConfiguration)emf.getConfiguration()).getDBDictionaryInstance(); + } + + public void testMapping() { + ClassMapping cm = getMapping(UuidGeneratedEntity.class); + Column[] cols = cm.getPrimaryKeyColumns(); + assertEquals(1, cols.length); + + Column col = cols[0]; + assertEquals(_dict.supportsUuidType ? JavaTypes.UUID_OBJ : JavaTypes.STRING, col.getJavaType()); + } + + public void testDefaultValues() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity gv1 = new UuidGeneratedEntity(); + UuidGeneratedEntity gv2 = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(gv1); + em.persist(gv2); + em.getTransaction().commit(); + + em.refresh(gv1); + em.refresh(gv2); + + assertNotNull(gv1.getId()); + assertNotNull(gv2.getId()); + assertFalse(gv1.getId().compareTo(gv2.getId()) == 0); + assertNotNull(gv1.getNativeUuid()); + assertNotNull(gv2.getNativeUuid()); + assertFalse(gv1.getNativeUuid().compareTo(gv2.getNativeUuid()) == 0); + assertTrue(isCanonicalHexUUID(gv1.getStringUUID(), 4)); + assertTrue(isCanonicalHexUUID(gv2.getStringUUID(), 4)); + closeEM(em); + } + + public void testFindByUUIDProperty() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity gv = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(gv); + em.getTransaction().commit(); + + UUID nid = gv.getNativeUuid(); + + String query = "SELECT u FROM UuidGeneratedEntity AS u WHERE u.nativeUuid = :nid"; + + List<UuidGeneratedEntity> list = em + .createQuery(query, UuidGeneratedEntity.class) + .setParameter("nid", nid) + .getResultList(); + + assertEquals(1, list.size()); + assertEquals(nid, list.get(0).getNativeUuid()); + + closeEM(em); + } + + public void testUpdateUUIDProperty() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity gv = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(gv); + em.getTransaction().commit(); + + UUID nid = gv.getNativeUuid(); + + String query = "SELECT u FROM UuidGeneratedEntity AS u WHERE u.nativeUuid = :nid"; + + List<UuidGeneratedEntity> list = em + .createQuery(query, UuidGeneratedEntity.class) + .setParameter("nid", nid) + .getResultList(); + assertEquals(1, list.size()); + UUID changed = UUID.randomUUID(); + + em.getTransaction().begin(); + list.get(0).setNativeUuid(changed); + em.merge(list.get(0)); + em.getTransaction().commit(); + + list = em.createQuery(query, UuidGeneratedEntity.class) + .setParameter("nid", nid) + .getResultList(); + assertEquals(0, list.size()); + list = em.createQuery(query, UuidGeneratedEntity.class) + .setParameter("nid", changed) + .getResultList(); + assertEquals(1, list.size()); + + closeEM(em); + + } + + public void testFindByStringUUIDProperty() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity gv = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(gv); + em.getTransaction().commit(); + + String sid = gv.getStringUUID(); + + String query = "SELECT u FROM UuidGeneratedEntity AS u WHERE u.stringUUID = :sid"; + + List<UuidGeneratedEntity> list = em + .createQuery(query, UuidGeneratedEntity.class) + .setParameter("sid", sid) + .getResultList(); + + assertEquals(1, list.size()); + assertEquals(sid, list.get(0).getStringUUID()); + + closeEM(em); + + } + + public void testFindByUUID() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity gv = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(gv); + em.getTransaction().commit(); + + UUID id = gv.getId(); + + UuidGeneratedEntity fv = em.find(UuidGeneratedEntity.class, id); + + assertNotNull(fv); + assertEquals(gv.getId(), fv.getId()); + assertEquals(gv.getStringUUID(), fv.getStringUUID()); + assertEquals(gv.getNativeUuid(), fv.getNativeUuid()); + + closeEM(em); + } + + public void testRemoveById() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity gv = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(gv); + em.getTransaction().commit(); + + UUID id = gv.getId(); + + UuidGeneratedEntity fv = em.find(UuidGeneratedEntity.class, id); + + em.getTransaction().begin(); + em.remove(fv); + em.getTransaction().commit(); + + fv = em.find(UuidGeneratedEntity.class, id); + assertNull(fv); + + closeEM(em); + } + + public void testParentRelationshipById() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity parent = new UuidGeneratedEntity(); + UuidGeneratedEntity child = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(parent); + child.setParent(parent); + em.persist(child); + em.getTransaction().commit(); + + assertEquals(parent, child.getParent()); + assertEquals(parent.getId(), child.getParent().getId()); + + UUID parentId = parent.getId(); + UUID childId = child.getId(); + + String query = "SELECT u FROM UuidGeneratedEntity AS u WHERE u.parent.id = :pid"; + + List<UuidGeneratedEntity> list = em + .createQuery(query, UuidGeneratedEntity.class) + .setParameter("pid", parentId) + .getResultList(); + assertEquals(1, list.size()); + assertEquals(childId, list.get(0).getId()); + + closeEM(em); + } + + public void testParentRelationshipByEntity() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity parent = new UuidGeneratedEntity(); + UuidGeneratedEntity child = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(parent); + child.setParent(parent); + em.persist(child); + em.getTransaction().commit(); + + UUID childId = child.getId(); + + String query = "SELECT u FROM UuidGeneratedEntity AS u WHERE u.parent = :parent"; + + List<UuidGeneratedEntity> list = em + .createQuery(query, UuidGeneratedEntity.class) + .setParameter("parent", parent) + .getResultList(); + assertEquals(1, list.size()); + assertEquals(childId, list.get(0).getId()); + + closeEM(em); + } + + public void testSetPreviouslyNullProperty() { + EntityManager em = emf.createEntityManager(); + + UuidGeneratedEntity parent = new UuidGeneratedEntity(); + UuidGeneratedEntity child = new UuidGeneratedEntity(); + + em.getTransaction().begin(); + em.persist(parent); + child.setParent(parent); + em.persist(child); + em.getTransaction().commit(); + + em.getTransaction().begin(); + child.setBasicUuid(new UUID(0, 0)); + child = em.merge(child); + em.getTransaction().commit(); + + String query = "SELECT u FROM UuidGeneratedEntity AS u WHERE u.parent = :parent"; + + List<UuidGeneratedEntity> list = em + .createQuery(query, UuidGeneratedEntity.class) + .setParameter("parent", parent) + .getResultList(); + assertNotNull(child.getBasicUuid()); + + + closeEM(em); + } + + /* + * Verify a uuid hex string value is 32 characters long, consists entirely + * of hex digits and is the correct version. + */ + private boolean isCanonicalHexUUID(String value, int type) { + if (value.length() != 36) + return false; + char[] chArr = value.toCharArray(); + for (int i = 0; i < 36; i++) + { + char ch = chArr[i]; + if ((i == 8 || i == 13 || i == 18|| i == 23) && ch == '-') + continue; + if (!(Character.isDigit(ch) || + (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F'))) + return false; + if (i == 14) { + if (type == 1 && ch != '1') + return false; + if (type == 4 && ch != '4') + return false; + if (type == 7 && ch != '7') + return false; + } + } + return true; + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/UuidGeneratedEntity.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/UuidGeneratedEntity.java new file mode 100644 index 000000000..6c1909932 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/generationtype/UuidGeneratedEntity.java @@ -0,0 +1,96 @@ +/* + * 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.generationtype; + +import static jakarta.persistence.GenerationType.UUID; + +import java.util.UUID; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +@Entity +public class UuidGeneratedEntity { + + @Id + @GeneratedValue + @Column(name = "id_") + private UUID id; + + @GeneratedValue(strategy = UUID) + @Column(name = "nativeuuid_") + private UUID nativeUuid; + + @GeneratedValue(strategy = UUID) + @Column(name = "stringuuid_") + private String stringUUID; + + private UUID basicUuid; + + @ManyToOne + private UuidGeneratedEntity parent; + + public UuidGeneratedEntity() { + super(); + } + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public UUID getNativeUuid() { + return nativeUuid; + } + + public void setNativeUuid(UUID nativeUuid) { + this.nativeUuid = nativeUuid; + } + + public String getStringUUID() { + return stringUUID; + } + + public void setStringUUID(String stringUUID) { + this.stringUUID = stringUUID; + } + + public UUID getBasicUuid() { + return basicUuid; + } + + public void setBasicUuid(UUID basicUuid) { + this.basicUuid = basicUuid; + } + + public UuidGeneratedEntity getParent() { + return parent; + } + + public void setParent(UuidGeneratedEntity parent) { + this.parent = parent; + } + +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java index 6d100993a..8da37ed5e 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/AnnotationPersistenceMetaDataParser.java @@ -90,6 +90,7 @@ import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.TreeSet; +import java.util.UUID; import jakarta.persistence.Access; import jakarta.persistence.AccessType; @@ -1410,12 +1411,22 @@ public class AnnotationPersistenceMetaDataParser else fmd.setValueSequenceName(generator); break; - case AUTO: - fmd.setValueSequenceName(SequenceMetaData.NAME_SYSTEM); - break; case IDENTITY: fmd.setValueStrategy(ValueStrategies.AUTOASSIGN); break; + case AUTO: + if (fmd.getType() != UUID.class) { + fmd.setValueSequenceName(SequenceMetaData.NAME_SYSTEM); + return; + } + case UUID: + if (fmd.getType() == UUID.class) { + fmd.setValueStrategy(ValueStrategies.UUID_JPA); + return; + } else if (fmd.getType() == String.class) { + fmd.setValueStrategy(ValueStrategies.UUID_TYPE4_CANON); + return; + } default: throw new UnsupportedException(strategy.toString()); } diff --git a/openjpa-project/src/doc/manual/jpa_overview_meta.xml b/openjpa-project/src/doc/manual/jpa_overview_meta.xml index 3054e9f8e..f6d6d0d9d 100644 --- a/openjpa-project/src/doc/manual/jpa_overview_meta.xml +++ b/openjpa-project/src/doc/manual/jpa_overview_meta.xml @@ -886,7 +886,8 @@ has the following values: <listitem> <para> <literal>GeneratorType.AUTO</literal>: The default. Assign the field a -generated value, leaving the details to the JPA vendor. +generated value, leaving the details to the JPA vendor. If the field type +is UUID, it will be given an generated UUID. </para> </listitem> <listitem> @@ -907,6 +908,13 @@ generate a field value. field value. </para> </listitem> + <listitem> + <para> +<literal>GenerationType.UUID</literal>: Assign the field a type 4 UUID +generated value. In this case, the field must be either <classname>String</classname> + or <classname>UUID<classname>. + </para> + </listitem> </itemizedlist> </listitem> <listitem> @@ -1191,6 +1199,9 @@ Since JPA 2.2 the following <classname>java.time</classname> Types are also supp and <classname>java.time.OffsetDateTime</classname>. </para> <para> +Since JPA 3.1, <classname>java.util.UUID</classname> is also supported. + </para> + <para> <classname>Basic</classname> declares these properties: </para> <itemizedlist> diff --git a/openjpa-project/src/doc/manual/jpa_overview_pc.xml b/openjpa-project/src/doc/manual/jpa_overview_pc.xml index ba0b3de8b..cd3d7f52a 100644 --- a/openjpa-project/src/doc/manual/jpa_overview_pc.xml +++ b/openjpa-project/src/doc/manual/jpa_overview_pc.xml @@ -460,6 +460,11 @@ java.lang.Byte</classname>, etc) <classname>java.time.OffsetTime</classname> </para> </listitem> + <listitem> + <para> +<classname>java.util.UUID</classname> + </para> + </listitem> </itemizedlist> <para> JPA also supports <classname>byte[]</classname>, <classname>Byte[]</classname>, @@ -739,7 +744,7 @@ other entities of the same type. <para> Identity fields must be primitives, primitive wrappers, <classname> String</classname>s, <classname>Date</classname>s, <classname> -Timestamp</classname>s, or embeddable types. +Timestamp</classname>s, <classname>UUID</classname>s or embeddable types. </para> <note> <para> @@ -751,6 +756,14 @@ identity field, you must create an identity class. Identity classes are covered below. </para> </note> + <note> + <para> +OpenJPA supports <classname>UUID</classname>s identity fields using the +binary representation of UUID on the database if the database and its +JDBC driver supports it. When this conditions are not met, it uses the +canonical string representation of the UUID. + </para> + </note> <warning> <para> Changing the fields of an embeddable instance while it is assigned to an diff --git a/openjpa-project/src/doc/manual/supported_databases.xml b/openjpa-project/src/doc/manual/supported_databases.xml index b6332e596..bc279eaf8 100644 --- a/openjpa-project/src/doc/manual/supported_databases.xml +++ b/openjpa-project/src/doc/manual/supported_databases.xml @@ -827,9 +827,13 @@ openjpa.ConnectionURL: jdbc:h2:DB_NAME <itemizedlist> <listitem> <para> -None +<classname>UUID</classname> support allows usage of binary data on entities. +If the user wants to exchange <classname>String</classname> for +<classname>UUID</classname>, it is necessary to previously convert the +<classname>String</classname> to UUID type in database. </para> </listitem> + </itemizedlist> </section> </section> @@ -1258,7 +1262,13 @@ The number of fractions can be explicitly set via scale: A value of <code>@Column(scale=-1)</code> will explicitly turn off all fractions. </para> </listitem> - + <listitem> + <para> + As of MariaDB 10.7 the <code>UUID</code> data type is supported, + but the JDBC driver does not writes the object directly, so + UUID field support is made through UUID to String conversion. + </para> + </listitem> </itemizedlist> </section> </section> @@ -1467,6 +1477,14 @@ supported when using <link linkend="ref_guide_streamsupport">LOB streaming</link>. </para> </listitem> + <listitem> + <para> +<classname>UUID</classname> support allows usage of binary data on entities. +If the user wants to exchange <classname>String</classname> for +<classname>UUID</classname>, it is necessary to previously convert the +<classname>String</classname> to UUID type in database. + </para> + </listitem> </itemizedlist> </section> </section>