diff --git a/openjpa-project/src/doc/manual/migration_considerations.xml b/openjpa-project/src/doc/manual/migration_considerations.xml index 83bc010aba..8f72fb40e0 100644 --- a/openjpa-project/src/doc/manual/migration_considerations.xml +++ b/openjpa-project/src/doc/manual/migration_considerations.xml @@ -649,5 +649,1081 @@ +
+ OpenJPA 4.2.0 +
+ Incompatibilities + + + OpenJPA 4.2.0 implements the Jakarta Persistence 3.2 specification and passes the + Jakarta Persistence 3.2 TCK. The following sections indicate changes that are incompatible + between OpenJPA 4.1.x releases and the 4.2.0 release. Most of them align OpenJPA with + the specification and cannot be switched off; where a configuration option restores + the previous behavior, it is mentioned in the respective section. Open follow-up items + from the review of this work are tracked under OPENJPA-2945. + +
+ Platform and Dependency Requirements + + OpenJPA 4.2.0 requires Java 17 or later at build and run time (4.1.x required + Java 11). All jars contain Java 17 class files. Java 21 and Java 25 runtimes are + supported; class files of newer JDKs are read through + xbean-asm9-shaded 4.30. + + + The jakarta.persistence:jakarta.persistence-api dependency was + raised from 3.1.0 to 3.2.0 (Jakarta EE 11 level). Applications must update explicit + dependencies on the API and recompile. Custom wrappers implementing + jakarta.persistence interfaces (EntityManager, + EntityManagerFactory, Query, + CriteriaBuilder, PersistenceUnitInfo, ...) + must implement the new 3.2 methods. The persistence_3_2.xsd and + orm_3_2.xsd schemas are bundled and selected for documents + declaring version="3.2"; version="3.1" + documents are still not accepted (use 3.0 or 3.2). + + + XML column mapping (XMLValueHandler) and the detection of + XML-mapped value classes now use jakarta.xml.bind (JAXB 4) + instead of javax.xml.bind (JAXB 2). The optional dependencies + are jakarta.xml.bind:jakarta.xml.bind-api 4.0.x and a JAXB 4 + runtime such as com.sun.xml.bind:jaxb-impl 4.0.x. Value classes + that are still annotated with javax.xml.bind.annotation + annotations are no longer recognized as XML column types and silently fall back to + the default (serialized) mapping. Migrate such classes to + jakarta.xml.bind.annotation. + + + The managed JDBC driver versions used for testing were raised: MySQL Connector/J 9.x + (com.mysql:mysql-connector-j, driver class + com.mysql.cj.jdbc.Driver), MariaDB Connector/J 3.5.x, Microsoft + mssql-jdbc 13.x and Derby 10.16.x (which itself requires Java 17). + MySQLDictionary now also recognises + com.mysql.cj.jdbc.exceptions.MySQLTimeoutException as a timeout + exception, so lock and query timeouts are classified correctly with Connector/J 8 + and later. Drivers are not shipped with OpenJPA; see + for the tested database and driver versions. + + + Build-only changes: the Maven profile test-h2-2 was removed + (use test-h2), and the legacy JPA 1.0 / 2.0 TCK profiles in + openjpa-integration/tck were replaced by the Jakarta Persistence + 3.2 TCK runner (-Ptck32-profile, run-tck32.sh). + +
+
+ SecurityManager Support Removed + + All AccessController.doPrivileged(...) calls were removed from + OpenJPA. Running OpenJPA under a Java SecurityManager with a + policy that grants permissions only to the OpenJPA jars is no longer supported (the + SecurityManager is deprecated for removal since Java 17 and + permanently disabled since Java 24, JEP 486). + + + As a consequence the public PrivilegedAction factory methods of + org.apache.openjpa.lib.util.J2DoPrivHelper (for example + getForNameAction, getClassLoaderAction, + newInstanceAction, getContextClassLoaderAction) + were removed; only getLineSeparator(), + getPathSeparator(), newInstance(Class) and + newDeamonThread(Runnable, String) remain. Third-party code + (custom product derivations, dictionaries, plugins, application server integrations) + using these helpers must call the JDK API directly. Plugin classes instantiated + through J2DoPrivHelper.newInstance may now have a non-public + no-argument constructor. + +
+
+ New JPQL Reserved Identifiers + + The JPQL grammar now knows the Jakarta Persistence 3.2 keywords + ID, VERSION, FIRST, + LAST, ON, NULLS, + CAST, STRING, INTEGER, + LONG, FLOAT, DOUBLE, + TREAT, UNION, INTERSECT, + EXCEPT, RIGHT and REPLACE + (case-insensitive), as well as the || concatenation operator. + In previous releases these words could be used as identification variables and + result aliases, for example SELECT e.id AS id FROM E e ORDER BY id + or SELECT first FROM Foo first. Such queries are now rejected + with a parse error, as required by section 4.4.1 of the specification. + + + With the exception of INTEGER, LONG, + FLOAT and DOUBLE the new keywords may still be + used as path components, so attributes named id, + version, first or replace + can still be navigated (o.version). Attributes named + integer, long, float or + double can no longer be referenced in a JPQL path expression; + rename them or access them through the Criteria API or native SQL. Rename + identification variables and aliases that collide with the new keywords (most + commonly id, version, first, + last and on). There is no soft-keyword mode + and no compatibility option. + +
+
+ JPQL Integer Literals are typed Integer + + Previous releases always created JPQL integer literals as + java.lang.Long, so SELECT 1 FROM ... or + e.intField + 1 produced Long results. + As required by section 4.8.5 of the specification, an integer literal without an + l/L suffix that fits into the + int range is now a java.lang.Integer; + arithmetic on Byte, Short and + Integer operands is promoted to Integer + (also in the Criteria API). + + + Application code that casts such results to Long must be + adapted to Integer or Number, or use an + explicit 1L literal. No compatibility option exists. + +
+
+ Query value conversion + + A String literal compared with a numeric path or parameter + (WHERE e.intField = '1', cb.equal(path, "12")) + is now parsed as a number of the path's type; previously a one-character literal was + compared as a Character and longer literals were rejected. The + character comparison is only used when the literal cannot be parsed as a number. + Selecting a collection- or map-valued attribute (SELECT e.addresses FROM + Employee e, query.select(root.get("addresses"))) is + treated as an implicit join and returns one row per element, typed as the element + type, instead of a collection-typed projection. + java.sql.Date values are no longer implicitly converted to + java.sql.Time or java.sql.Timestamp + when query values are compared (the same rule already applied to + java.util.Date). Enum names read from padded + CHAR columns are trimmed before Enum.valueOf(). + No compatibility option exists. + +
+
+ Query.getResultList() returns a materialized ArrayList + + Query.getResultList() previously returned a lazy + org.apache.openjpa.lib.rop.ResultList wrapper that streamed + rows on demand (see ) and became invalid + when the query or the EntityManager was closed. Jakarta + Persistence 3.2 requires a mutable List, so the result is now + copied into a java.util.ArrayList: all rows are fetched + immediately, the list stays usable after the EntityManager + is closed, and it is no longer an instance of ResultList. + Query.getResultStream() also materializes the complete result + first. Large result set collections on entity fields + () are not affected. + + + For very large query results use setFirstResult() / + setMaxResults() paging, or obtain the kernel query via + OpenJPAQuery.getDelegate() for lazy semantics. Code that casts + the result to ResultList or + DelegatingResultList must be changed. + +
+
+ Bulk DELETE no longer cascades + + In previous releases a JPQL or Criteria bulk DELETE against an + entity with cascade-delete or dependent relations (for example + @OneToMany(cascade=REMOVE) or an + @ElementCollection) was executed in memory: every instance was + loaded and removed through the persistence context, cascading to related entities + and cleaning up join table and element collection rows. As required by section + 4.10 of the specification, bulk operations do not cascade. A DELETE + query now issues a single SQL DELETE against the entity table(s); + related entities, join table rows and element collection rows are left untouched + and will cause foreign key violations unless the database defines + ON DELETE CASCADE. + + + Delete dependents explicitly (separate bulk deletes or em.remove()) + or rely on database-level cascades. Note that a bulk UPDATE or + DELETE that still has to be executed in memory now flushes all + pending changes of the persistence context after processing. No compatibility + option exists. See also . + +
+
+ FlushModeType.AUTO flushes whenever the context is dirty + + Previously a query only flushed pending changes when a dirty instance belonged to + a type in the query's access path, and never for SELECT queries + when was true. With + FlushModeType.AUTO (the default, + =true) and an active + transaction, a query now flushes whenever any instance in the persistence context + is new, dirty or deleted, regardless of the query's access path, and overrides + openjpa.IgnoreChanges. Applications with write-heavy transactions + may observe more flushes (and therefore earlier constraint or trigger evaluation). + + + To restore the previous behavior use FlushModeType.COMMIT on + the query or the EntityManager, or set + openjpa.FlushBeforeQueries to false or + with-connection. + +
+
+ Exceptions mark the transaction rollback-only; closed EntityManager checks + + As required by section 3.3.7.1 of the specification, every + RuntimeException raised by an EntityManager + or Query method now marks the active transaction for + rollback, except NoResultException, + NonUniqueResultException, + LockTimeoutException and + QueryTimeoutException. In previous releases the transaction + remained committable after, for example, an IllegalArgumentException + from find(), createNamedQuery() or an invalid + CriteriaQuery. Applications that caught such an exception and + committed the same transaction now receive a RollbackException + and must restart the transaction. + + + Operations on a closed EntityManager or its + Query objects now consistently throw + IllegalStateException. This includes + getEntityManagerFactory(), getCriteriaBuilder(), + getMetamodel(), getDelegate(), + setProperty(), isJoinedToTransaction(), + createEntityGraph(), createQuery(CriteriaQuery) + and Query.getHints(), getLockMode(), + closeAll() and the parameter accessors, which used to work on a + closed EntityManager. Further changes: + getLockMode()/setLockMode() on a bulk + UPDATE/DELETE query and + executeUpdate() on a SELECT query throw + IllegalStateException; + createQuery(CriteriaQuery) snapshots the criteria state, so + modifying the CriteriaQuery afterwards no longer affects the + created Query; isJoinedToTransaction() + returns true for an active resource-local transaction. + +
+
+ Argument validation in find(), getReference(), detach(), contains(), remove() + + find(cls, null) previously returned null, a + primary key of the wrong type or a non-entity class surfaced later as an OpenJPA + ArgumentException or a failed lookup, and removing a detached + unenhanced entity could be silently ignored. These methods now validate their + arguments as required by the specification and throw + IllegalArgumentException (marking the transaction for + rollback) for null keys, non-entity classes, primary keys of an + incompatible type (numeric widening such as Integer to + Long is accepted, narrowing is rejected), non-entities + passed to detach()/contains(), and detached + instances passed to remove(). getReference() + throws EntityNotFoundException also for unenhanced entities. + + + Guard against null keys, pass keys of the declared + @Id type and merge() detached instances before + removing them. No compatibility option exists. + +
+
+ EntityManager.close() with an active resource-local transaction + + Previously EntityManager.close() threw an + InvalidStateException while a resource-local transaction was + active; a deferred close existed only for managed (JTA) transactions with the + CloseOnManagedCommit compatibility flag. As required by sections + 3.3.2 and 7.7 of the specification, close() now always returns + and the persistence context is released when the transaction completes: + isOpen() reports false immediately while + em.getTransaction().commit()/rollback() + remain callable. + + + Code that relied on the exception to detect a leaked open transaction must check + em.getTransaction().isActive() itself and end the transaction + explicitly. There is no option to restore the exception. + +
+
+ EntityManagerFactory lifecycle and properties + + EntityManagerFactory.close() could previously be called more than + once, and methods of a closed factory either worked or failed deep inside the kernel. + Now getProperties(), createEntityManager(), + getCriteriaBuilder(), getMetamodel(), + getPersistenceUnitUtil() and + getSchemaManager() throw IllegalStateException + after close(), and a second close() throws as + well. createEntityManager(SynchronizationType.UNSYNCHRONIZED) + throws IllegalStateException instead of + UnsupportedOperationException (it is forbidden for + RESOURCE_LOCAL units and still unimplemented for JTA units). + + + EntityManagerFactory.getProperties() no longer creates a temporary + EntityManager to merge EntityManager-level + defaults (lock timeout, cache modes, fetch plan settings) into its result, and + null-valued entries are removed; read such defaults from + EntityManager.getProperties() instead. + addNamedQuery() now replaces an existing definition of the same + name and records flush mode, max results and lock mode; Criteria queries are stored + as JPQL text, which is not guaranteed to round-trip for complex criteria. + +
+
+ Query parameter API + + Reading an unbound parameter via getParameterValue() now throws + IllegalStateException (previously + IllegalArgumentException or null); + Parameter objects obtained from another query are rejected + with IllegalArgumentException; + getParameter(String, Class) now correctly accepts an exact or + wider type (the positional variant getParameter(int, Class) still + only accepts the exact type or a subtype). A parameter bound with + TemporalType.DATE is converted to java.sql.Date + (time of day dropped) instead of being passed through unchanged. + + + Catch IllegalStateException for unbound parameters and expect + java.sql.Date semantics for DATE-typed + parameters compared against TIMESTAMP columns. + +
+
+ StoredProcedureQuery semantics + + Positional stored procedure parameters are now resolved 1-based (previously the + 0-based column index was used as fallback, so position 1 could match the second + IN column), and only IN/INOUT/ + OUT columns are registered, so positional indexes shift for + PostgreSQL functions with a return value. When a procedure declares + OUT parameters the connection is kept open (with auto-commit + temporarily disabled) until the result list is closed, so that + REF_CURSOR results can be consumed; consume and close such + result lists promptly. + + + executeUpdate() now requires an active transaction + (TransactionRequiredException) and + getUpdateCount() returns -1 after it; + setLockMode()/getLockMode() throw + IllegalStateException; + getOutputParameterValue() throws + IllegalArgumentException for unknown names or positions; + NoResultException and + NonUniqueResultException propagate unwrapped; procedure + metadata lookup retries with the lower-cased name; an + orm.xml named-stored-procedure-query overrides + an annotation of the same name. + +
+
+ Criteria API and Metamodel + + Several OpenJPA extensions and lenient behaviors of the Criteria API were tightened + to the specification: CriteriaBuilder.array()/tuple() + reject nested compound selections and multiselect()/select() + reject duplicate aliases with IllegalArgumentException; + cb.literal(null) throws IllegalArgumentException + (use nullLiteral(Class)); Path.get() on a basic + path and From.getCorrelationParent() on a non-correlated + From throw IllegalStateException; + Metamodel.entity(Class)/embeddable(Class) throw + IllegalArgumentException for unknown types instead of + returning null; ParameterExpression.getPosition() + returns null (previously threw an internal exception). + + + Embeddable-typed attributes are no longer reported as associations + (isAssociation() is false, + getBindableType() is SINGULAR_ATTRIBUTE), + getBindableJavaType() returns the declared attribute type, and + getId(Class)/getDeclaredId(Class) require strict + type assignability. cb.treat(Root, Class) is now implemented; + treat() on joins and paths returns the argument unchanged without + narrowing, and TREAT only matches the exact treated class, not its + subclasses. Flatten nested selections, use unique aliases and adjust catch blocks + and null checks accordingly. + +
+
+ PersistenceUnitUtil.getIdentifier() returns the plain identifier + + PersistenceUnitUtil.getIdentifier() previously returned the + internal org.apache.openjpa.util.OpenJPAId wrapper + (LongId, StringId, ...) for managed + entities and null for new, detached or unenhanced entities and + for non-entities. It now returns the raw identifier value (the plain key or the + IdClass instance for compound identity), also for new, detached + and unenhanced entities, and throws IllegalArgumentException + for objects that are not entities. Code that cast the result to + OpenJPAId must use the plain value or call + OpenJPAEntityManager.getObjectId(). + +
+
+ Cache mode properties on EntityManager.setProperty() + + EntityManager.setProperty("jakarta.persistence.cache.retrieveMode", ...) + and "jakarta.persistence.cache.storeMode" previously accepted the + enum constants as well as their String names. The new + Jakarta Persistence 3.2 setters setCacheRetrieveMode() and + setCacheStoreMode() are now used to apply these properties, and + a String value is passed to the setter unconverted, which + fails with an IllegalArgumentException (argument type + mismatch); previously the call succeeded. Pass the + CacheRetrieveMode/CacheStoreMode enum + constant, or call the setters directly. String values given + as find(), refresh() or query hints continue + to work. + +
+
+ Default map key column renamed to <field>_KEY + + For Map-valued fields without @MapKeyColumn + the key column was previously named KEY (which most dictionaries + turned into KEY0 because KEY is a reserved + word). As required by section 11.1.35 of the specification, the default is now the + field name followed by _KEY (for example + PHONES_KEY). Schemas created by earlier releases no longer match: + schema validation fails, SynchronizeMappings=buildSchema adds a + new column and existing key data reads back as null. + + + Declare the existing column explicitly, for example + @MapKeyColumn(name="KEY0"), or rename the column in the + database. No compatibility option restores the old default. See also + . + +
+
+ Attribute overrides, @OrderBy and inverse map keys + + @AttributeOverride on an entity now also applies to the entity's + own declared fields (previously only mapped superclass fields were overridden), and + an override name without key./value. prefix on + an element collection Map refers to the map value instead of + failing. Attribute names in @OrderBy and overrides are resolved + case-insensitively as a fallback. @MapKeyColumn on the inverse + side of a @OneToMany(mappedBy) map is now written to the target + entity's table (additional UPDATE statements), and an + @ElementCollection whose table coincides with an entity's primary + table is no longer written separately. + + + Verify the column names produced by overrides that were previously ignored or + rejected, and @OrderBy values that differ from attribute names + only in case. + +
+
+ @SequenceGenerator without sequenceName + + A @SequenceGenerator annotation without + sequenceName previously fell back to the OpenJPA default + sequence OPENJPA_SEQUENCE, so all such generators shared one + database sequence. As defined by the specification, the generator name is now used + as the database sequence name, and DDL generation creates one sequence per + generator. orm.xml sequence-generator elements + are not affected. + + + For existing databases either add sequenceName="OPENJPA_SEQUENCE" + (or the previously used name) to each generator, or create the new per-generator + sequences. See . + +
+
+ AttributeConverter handling + + Support for jakarta.persistence.AttributeConverter was reworked. + @Converter(autoApply=true) classes found on the classpath are now + registered and applied to every basic attribute whose declared type matches + (excluding identifiers, version fields, relations, collections and maps) unless + the attribute declares its own @Convert; previously + autoApply was ignored. Class-level + @Convert(attributeName=...)/@Converts, + converters on embedded attributes, on mapped superclass attributes and on element + collection elements are honored (previously ignored or rejected). The database + column type is now derived from the converter's database type + Y instead of the attribute type X + (a Boolean to Integer converter now + yields an INTEGER column). + + + One converter instance per attribute is created lazily through the no-argument + constructor and shared between threads, so converters must be stateless. + RuntimeExceptions thrown by a converter surface as + jakarta.persistence.PersistenceException instead of + MetaDataException. @Convert(disableConversion=true) + only cancels an explicit converter on that attribute; it does not prevent an + autoApply converter from being applied. + + + Audit existing @Converter(autoApply=true) classes: attributes of + the matching type that were stored unconverted by 4.1.x are now written and read + through the converter and their generated column type changes. Remove + autoApply, declare an explicit converter, or pin the column type + with @Column(columnDefinition=...) where the old behavior is + required. + +
+
+ Access type determination and property accessors + + Implicit access type determination now follows the specification: only + access-defining annotations (@Id, @EmbeddedId, + @Version, @Basic, @Embedded, + the relationship annotations, @ElementCollection, + @Transient) decide the access type; supplementary annotations + such as @Column or @Temporal only count when + no access-defining annotation is present. A getter whose backing field is + @Transient is a per-attribute property override and no longer + makes the class "mixed"; an attribute annotated on both field and getter uses + property access; a subclass whose implicit access conflicts with its persistent + superclass inherits the superclass access type (previously an error); + @Basic may be combined with a more specific mapping annotation; + records always use field access; types declared as embeddable + only in orm.xml are mapped as embedded rather than serialized. + + + Boolean getters must have an upper-case character after is + (island() or isaBoolean() are no longer + persistent properties), and the setter for a property is derived from the actual + getter suffix (getdescription()/setdescription() + pairs are accepted). Entities that previously failed to load may now load with a + different access type than intended; add an explicit @Access + where fields and getters are both annotated, and review unusual boolean accessor + names. No compatibility option exists. + +
+
+ java.time.Instant and java.time.Year mapping + + java.time.Instant and java.time.Year + are now first-class persistent types: Instant maps to a + TIMESTAMP column and Year to an + INTEGER column. Previous releases had no type code for them and + stored such attributes through the generic object strategy as serialized binary + data. Existing columns created for such attributes are incompatible with the new + mapping: migrate the columns and data, or keep them serialized explicitly with + @Lob or an externalizer. java.util.Calendar + may now be used as a single-field identifier. + +
+
+ Generated DDL changes + + The DDL produced by the mapping tool, + and the new SchemaManager differs from previous releases: + + + jakarta.persistence.ForeignKey on + @JoinColumn, @JoinTable and + @SecondaryTable is now parsed. A named constraint + (ConstraintMode.CONSTRAINT) is emitted as a physical + foreign key even for relations that OpenJPA treats as logical; + NO_CONSTRAINT suppresses the key; + PROVIDER_DEFAULT keeps the 4.1.x behavior. Expect + additional ALTER TABLE ... ADD CONSTRAINT statements + and a stricter drop order. + + + Every declared @SecondaryTable is created, even if no + field is mapped to it. + + + The table-level comment is no longer emitted in + CREATE TABLE (column comments remain). + + + @Index(columnList="col DESC") is honored, + @JoinTable.indexes are created, + @Table(options) and @Column(options) + are appended verbatim, and @Column(secondPrecision) + takes precedence over scale and the dictionary's + DateFractionDigits for temporal columns. + + + + + Compare generated DDL against existing schemas before enabling schema + synchronization in production. + +
+
+ Jakarta Persistence schema generation and SQL scripts + + The jakarta.persistence.schema-generation.* properties are now + honored as described by the specification: scripts.action is + mapped to schema tool actions on its own (previously only + database.action was considered) and scripts are generated or + executed when the EntityManagerFactory is created rather + than lazily with the first EntityManager; explicit + create-source/drop-source values are + respected; java.io.Writer/Reader + targets and sources, file: URIs and absolute paths are accepted. + When Writer/Reader objects are + supplied, the corresponding keys are removed from the caller's property map, so the + map must be mutable. Persistence.generateSchema() defaults + database.action=create only if neither + database.action nor scripts.action is given. + A table dropped by an executed drop script is skipped by the next + buildSchema/add run in the same JVM (once by + default; until an EntityManagerFactory with schema-generation + properties starts when openjpa.SpecCompliantSchemaGeneration=true) + instead of being silently re-created. + + + SQL scripts (create, drop and load scripts) are now parsed as + ;-terminated statements that may span several lines, with + --, // and /* ... */ + comments stripped (string literals are not recognized). Previously every line was + one statement. Errors in scripts executed through schema generation are logged as + warnings on the channel instead of failing + start-up. Terminate every statement with ; and check the log + for script errors. Related SchemaTool changes: table + truncation continues after failing statements, dropping the last column of a table + drops the whole table, and the new DBDictionary.isDroppable(Sequence) + hook excludes system sequences. + + + Two options were added. openjpa.SpecCompliantSchemaGeneration=true + (also available as openjpa.Compatibility=SpecCompliantSchemaGeneration=true, + default false) enables strict Jakarta Persistence semantics: a + schema-generation configuration that resolves to no action (for example + database.action=none) also disables + openjpa.jdbc.SynchronizeMappings, @MapsId + foreign key columns are named <relation>_<targetPk>, + and on PostgreSQL identifiers are never quoted. Do not enable it on existing + databases created by earlier releases. The option + openjpa.jdbc.SyncMappingsExcludeTypes=a.B;c.D (or + SynchronizeMappings=buildSchema(ExcludeTypes=a.B;c.D)) excludes + entity classes from schema synchronization and drops their existing tables; only + list entities whose tables may be destroyed. See + and + . + +
+
+ Numeric versus character column type conflicts + + When two mappings, or the mapping and the reflected database column, disagreed on + an incompatible column type, previous releases failed with a + -bad-col MetaDataException (or logged a + warning with disableSchemaFactoryColumnTypeErrors). Conflicts + between numeric and character types are now silently resolved to + VARCHAR, and values are converted on read and write. As a side + effect, SynchronizeMappings=validate and + SchemaManager.validate() no longer report numeric versus + VARCHAR drift; verify such columns manually. All other + incompatible combinations still fail as before. + +
+
+ DELETE affecting zero rows tolerated for unversioned entities + + A DELETE statement reporting an update count of zero previously + always raised an OptimisticException, including rows already + removed by a database-level ON DELETE CASCADE. For entities + without a version strategy (no @Version and no state comparison + versioning) such a delete is now silently accepted. Entities with a version + strategy behave as before. Applications that relied on the exception to detect a + concurrently deleted unversioned row should add a @Version + attribute; subclasses of PreparedStatementManagerImpl may + override hasVersion(RowImpl). + +
+
+ Non-entity classes in persistence.xml + + Listing a class without persistence metadata in a persistence unit + (class element) previously failed at start-up with + "No registered metadata for type", in the runtime enhancer, in + getMetamodel() and during schema synchronization. Such classes + are now skipped with a warning on the openjpa.Enhance and + openjpa.jdbc.Schema logs. A forgotten @Entity + annotation is therefore no longer detected at start-up; watch the logs for the new + warnings. + +
+
+ Relaxed kernel checks + + Several early exceptions were relaxed to satisfy the specification: + a non-cascaded relation pointing at an object without a state manager is no longer + rejected at flush with "cant-cascade-persist"; the referenced row is looked up in + the database during flush (an extra SELECT for unenhanced or + subclass-enhanced entities) and truly transient references may now fail later with + a foreign key error. Modifying an embeddable obtained from a query projection no + longer throws; the modification is silently not persisted. Re-persisting an entity + after remove() and flush is tolerated. In addition, + orphanRemoval=true no longer downgrades + cascade=REMOVE/ALL, so removal is cascaded + immediately when both are combined. + +
+
+ Insert ordering across logical foreign keys + + Flush ordering previously delayed an insert only for physical (constraint-backed) + foreign keys. Rows related through logical foreign keys (no constraint declared in + the mapping, for example an externally created schema with real constraints) are + now also delayed until the referenced new row has been inserted. Statement order + at flush time may therefore change; tests asserting an exact SQL order may need to + be adjusted. There is no configuration switch. + +
+
+ Enhancer and runtime enhancement + + Classes enhanced by this release call new runtime methods (for example + ApplicationIds.getRelatedObjectId() for derived identities) and + fail on a 4.1.x runtime, while classes enhanced by 4.1.x still load but miss the + fixes of this release. Re-run the build-time enhancer + () with 4.2.0 when upgrading. + + + Runtime enhancement ( and the + Java agent) changed: class redefinition uses + Instrumentation.redefineClasses() and, if it fails, OpenJPA logs + "redefineClasses failed" at INFO and silently falls back to + subclass enhancement instead of throwing. getClass() calls in + user equals()/hashCode() implementations of + subclass-enhanced entities now see the entity class instead of the generated + subclass, and generated writeReplace() methods work with + non-public no-argument constructors. + +
+
+ Delayed collection proxies on Java 21 + + The delay-loading collection proxies (openjpa.ProxyManager=default(DelayCollectionLoading=true), + see ) now declare the Java 21 + SequencedCollection methods explicitly. + addFirst() and addLast() both delegate to + add(), so addFirst() appends instead of + prepending (previously DelayedLinkedListProxy prepended + after loading the collection), and reversed() returns a copy + rather than a write-through view for list and LinkedHashSet + proxies. Load the collection and reorder it explicitly where the position matters. + The ASM-generated non-delayed proxies are unchanged. + +
+
+ Static metamodel generator output + + Generated X_ classes now contain the Jakarta Persistence 3.2 + class_ field and QUERY_<NAME> / + MAPPING_<NAME> constants for named queries and result set + mappings declared on the type, and are annotated with + javax.annotation.processing.Generated instead of + jakarta.annotation.Generated when available. Regenerate the + metamodel classes and watch for name clashes with attributes named + class_. + +
+
+ Lifecycle callbacks and listeners + + Default entity listeners declared in several mapping files are now registered once + instead of once per file, and a callback declared both by annotation and in + orm.xml for the same method is registered once with the XML + declaration taking precedence. Callback parameter types are matched more leniently, + and listeners receive the managed entity instance (not the internal + ReflectingPersistenceCapable wrapper) on + AFTER_DELETE_PERFORMED for unenhanced entities. Applications that + depended on duplicate invocations must be adjusted. + +
+
+ persistence.xml resource handling + + An I/O error while reading a persistence.xml resource from the + classpath previously aborted createEntityManagerFactory(). Such + resources are now logged ("unreadable-persistence-xml") and skipped; schema + validation errors still abort. A unit that only exists in a skipped resource + surfaces later as a missing persistence unit. Resource streams are opened with + URL connection caching disabled. The 3.2 elements scope and + qualifier are exposed through + PersistenceUnitInfo. + +
+
+ Locale-independent case conversion + + Identifier normalization, JPQL parsing, in-memory LOWER()/ + UPPER() evaluation and SQL formatting used the JVM default + locale for case conversion. They now use Locale.ROOT + (Locale.ENGLISH for reserved word matching). This is only + observable under locales with special casing rules (Turkish, Azeri, Lithuanian), + where generated identifiers containing i/I + may now differ from those generated by earlier releases; use explicit + @Table/@Column names in that case. + +
+
+ Reserved word handling and MySQL delimiting + + Reserved word detection is now case-insensitive for all dictionaries, so generated + (defaulted) column, table and sequence names that equal a reserved word in a + different case may now receive a 0 suffix. + H2Dictionary additionally feeds its H2 2.x keyword list into + the naming rules. On MySQL (MySQLDictionary, not + MariaDBDictionary) reserved word identifiers are now + automatically delimited with back-ticks in all generated SQL, so previously + failing names such as KEY, TEXT or + LIBRARY work without manual delimiting. Tools comparing SQL text + must ignore the delimiters. Use explicit @Table/@Column + names if an old generated name must be kept. See + . + +
+
+ PostgreSQL + + Delimited identifiers: PostgresDictionary now strips the + double quotes from a quoted identifier whose inner text is a plain identifier + (letters, digits and underscores, not starting with a digit). PostgreSQL then folds + the name to lower case, so @Table(name="\"MyTable\"") now + addresses mytable and quoted reserved words such as + "Order" become bare keywords. Identifiers containing spaces or + other special characters keep their quotes. Do not rely on delimited identifiers to + preserve mixed case or to use reserved words as names on PostgreSQL; rename the + objects, or subclass PostgresDictionary and override + toDBName(). There is no configuration switch. + + + char/Character attributes: on PostgreSQL 9 + and later StoreCharsAsNumbers now defaults to + false, so such attributes map to CHAR(1) + columns instead of INTEGER columns holding code points, and the + Java default '\0' is stored as SQL NULL. + Existing INTEGER columns created by earlier releases fail + validation or return wrong values. Either migrate them to + CHAR(1) or restore the previous mapping with + openjpa.jdbc.DBDictionary=postgres(StoreCharsAsNumbers=true); + an explicitly configured value is respected (OPENJPA-2971). + + + Further fixes: java.util.UUID parameters are bound so that + both native uuid and varchar columns work (on + other databases a UUID is bound as VARCHAR unless the column is + a native UUID column); @Lob columns of type oid + are read and written through the large object API, which performs an implicit + COMMIT when reading in auto-commit mode; reflected + bool columns are reported as BOOLEAN, so + schema validation may now report boolean versus varchar drift that was previously + tolerated; DROP SEQUENCE IF EXISTS is emitted. + +
+
+ MySQL and MariaDB + + On MySQL 5.7+ and MariaDB 10.2+ temporal columns are now created as + DATETIME(6) and TIME(6) + (DateFractionDigits=6) instead of whole-second precision, so that + @Version attributes of type Instant or + LocalDateTime can distinguish updates within the same second. + The value is set at connection time and overrides a + DateFractionDigits value given in + . Existing columns keep working, but + schema validation or refresh may report or alter the precision. Use + @Column(secondPrecision=0) on individual columns, or subclass the + dictionary and reset dateFractionDigits after + connectedConfiguration(), to keep whole seconds. + + + MariaDBDictionary no longer replaces a configured positive + with Integer.MIN_VALUE + (the Connector/J 2.x streaming mode); the configured value is passed to the driver + unchanged. MySQLDictionary keeps the streaming behavior. + Subclass MariaDBDictionary and override + getBatchFetchSize(int) to restore streaming. + +
+
+ Microsoft SQL Server + + CURRENT_DATE and CURRENT_TIME are now + translated to CONVERT(DATE, GETDATE()) and + CONVERT(TIME, GETDATE()) instead of plain + GETDATE(), so the results are DATE/TIME + typed and comparisons against datetime columns may behave + differently. EXTRACT uses DATEPART and JPQL + time literals are rendered as CAST('hh:mm:ss' AS TIME). To + restore the previous SQL set + openjpa.jdbc.DBDictionary=sqlserver(CurrentDateFunction=GETDATE(),CurrentTimeFunction=GETDATE()). + +
+
+ Oracle + + Identity column sequences (ISEQ$$_*) are treated as system + sequences and excluded from drop actions, AUDSYS is treated as a + system schema, an @Index duplicating the primary key is skipped, + CEILING() is translated to CEIL(), and the new + Jakarta Persistence 3.2 functions are mapped to Oracle syntax + (EXCEPT as MINUS before Oracle 21, + LEFT/RIGHT via SUBSTR). + These are fixes; workarounds for the old behavior can be removed. + +
+
+ HSQLDB + + HSQLDictionary no longer disables + SupportsSelectForUpdate, so pessimistic locks now emit + SELECT ... FOR UPDATE; query timeouts are disabled + (SupportsQueryTimeout=false); OffsetTime + attributes are created as TIME instead of + TIME WITH TIME ZONE; numeric casts are sized + NUMERIC(128,32) so fractional digits are no longer truncated; + INFORMATION_SCHEMA and SYSTEM_LOBS are treated + as system schemas. Use openjpa.jdbc.DBDictionary=hsql(SupportsSelectForUpdate=false,SupportsQueryTimeout=true) + and @Column(columnDefinition="TIME WITH TIME ZONE") to restore + the previous behavior. On H2 2.x, table truncation now skips the + INFORMATION_SCHEMA meta tables. + +
+
+ SPI changes for custom store, dictionary and expression implementations + + Implementors of OpenJPA SPI interfaces must recompile and implement new methods: + BrokerFactory (createPersistenceStructure, + dropPersistenceStructure, validatePersistenceStructure, + truncateData; AbstractBrokerFactory + throws UnsupportedOperationException by default), + ExpressionFactory (newTypecastAsString, + newTypecastAsNumber, left, right, + replace, getNativeObjectId, version), + Result (getInstant, getYear), + Select (appendNullsPrecedence, + addSetOperatorSQL, getSetOperatorBuffer), + OpenJPAConfiguration (schema generation script accessors, + isSchemaGenerationExplicit, isSpecCompliantSchemaGeneration) + and JDBCConfiguration (get/setSyncMappingsExcludeTypes). + + + DBDictionary.SerializedData is now a record + (bytes() instead of the bytes field); + IdentifierRule.setReservedWords takes a + Collection and matches case-insensitively, so subclasses + overriding the Set variant no longer override; + JavaTypes.INSTANT (39) and JavaTypes.YEAR (40) + were added and must be handled by custom value handlers and strategies; + QueryExpressions gained nullPrecedence, + setOperationType and setOperands, which only + the JDBC store consumes, so a custom StoreQuery silently ignores + NULLS FIRST/LAST and set operations unless it is extended. + DBDictionary gained a number of public configuration fields + (ReplaceFunctionName, LeftFunctionName, + RightFunctionName, NaturalLogarithmFunction, + CeilingFunction, ExceptFunction, + TypecastToStringTypeName, IntegerCastTypeName, + SupportsUnsizedCharOnCast) and hooks + (isDroppable(Sequence), toJDBCEscapedDateTimeLiteral, + appendNullsPrecedence, getExtractField, + get/setMajorVersion, get/setMinorVersion). + +
+
+ Notable new features + + The following Jakarta Persistence 3.2 features are new in this release. They are + opt-in and do not change existing behavior unless noted: + + + JPQL: ID() and VERSION() functions, + CAST, LEFT, RIGHT, + REPLACE, the || operator, + UNION/INTERSECT/EXCEPT [ALL], + NULLS FIRST/LAST, TREAT in joins and + paths, JOIN ... ON, EXTRACT, + LOCAL DATE/TIME/DATETIME, additional math functions, + an optional SELECT clause and the implicit + this identification variable (bound automatically when a + FROM item declares no identification variable). See . + In-memory query execution (and custom StoreQuery + implementations) silently ignore + UNION/INTERSECT/EXCEPT + and NULLS FIRST/LAST; + LEFT/RIGHT, CAST and + EXTRACT are not available on Derby. + + + EntityManager: find(), + lock() and refresh() with + FindOption/LockOption/RefreshOption, + getReference(entity), cache mode and timeout accessors, + runWithConnection()/callWithConnection(), + createQuery(CriteriaSelect); + Query: getSingleResultOrNull(), + cache mode and timeout setters. + + + EntityManagerFactory: + runInTransaction()/callInTransaction() + (exceptions are rethrown wrapped in + org.apache.openjpa.persistence.PersistenceException), + getSchemaManager(), getName(), + getTransactionType(), getNamedEntityGraphs(); + PersistenceUnitUtil: getVersion(), + isInstance(), getClass(), + load(); programmatic bootstrap via + Persistence.createEntityManagerFactory(PersistenceConfiguration) + and the jakarta.persistence.dataSource property. + EntityManager.find(EntityGraph, ...), + createQuery(TypedQueryReference), + getNamedQueries() and + SynchronizationType.UNSYNCHRONIZED are not yet implemented. + + + Entity graphs (@NamedEntityGraph, + createEntityGraph(), getEntityGraph()), + CriteriaUpdate, CriteriaDelete, + CriteriaSelect set operations, + Join.on(), CriteriaBuilder.cast()/ + left()/right()/replace()/ + extract() and treat(Root), all of which + previously threw UnsupportedOperationException. + + + Mapping: Java records as @Embeddable (records are + always treated as managed types, regardless of + openjpa.RuntimeUnenhancedClasses), + @EnumeratedValue (an enum declaring such a field changes + its stored representation), @Version on + java.time.Instant and + java.time.LocalDateTime (give such columns at least + microsecond precision), @Column(secondPrecision, options), + @Table(options), repeatable + @SequenceGenerator/@TableGenerator, + ConstructorResult in result set mappings, inline result + mappings on @NamedNativeQuery, orm.xml + version 3.2, id classes without a public no-argument constructor, and + @MapsId with non-embeddable id classes. + + + jakarta.persistence.ForeignKey, + @Index sort order, @JoinTable.indexes + and @Converter(autoApply=true) are honored (see the + respective sections above for the effect on existing schemas). + + + The bundled Jakarta Persistence schemas are now included under the Eclipse + Foundation Specification License 1.1 instead of the CDDL. + + + +
+
+