Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions api/src/main/java/jakarta/persistence/EntityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,40 @@ void refresh(Object entity,
@Deprecated(since = "4.0", forRemoval = true)
EntityGraph<?> createEntityGraph(String graphName);

/**
* Enable tracking of modifications to the given managed
* instance of a {@linkplain ReadOnly read-only} entity
* class. This operation disables the effect of the
* {@link ReadOnly} annotation for the given instance
* only. If the given instance has been modified, the
* modifications are synchronized with the database the
* next time the persistence context is flushed. If this
* operation was already applied to the given instance,
* the invocation has no effect.
* @param entity a managed instance of a read-only entity
* class
* @throws IllegalArgumentException if the instance is
* not associated with this persistence context
* or is not a non-null instance of a read-only
* entity class
* @since 4.0
* @see ReadOnly
* @see #flush
*/
void enableFlush(Object entity);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU, in Hibernate ORM it's impossible to make @Immutable entities non-read-only, so I would rather not do this. Also see org.hibernate.engine.internal.AbstractEntityEntry#setReadOnly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exists in Hibernate today grew organically and it shows. I don't think it's an especially perfect model.


/**
* Obtain an {@link EntityAgent} which shares the transaction
* associated with this {@code EntityManager}. If this as a
* {@linkplain PersistenceUnitTransactionType#RESOURCE_LOCAL
* resource-local entity manager}, the {@link #getTransaction()}
* method of the returned agent always returns exactly the same
* object as the {@code getTransaction()} method of this entity
* manager.
* @since 4.0
*/
EntityAgent getAgent();

/**
* Return the underlying provider object for the
* {@link EntityManager}, if available. The result of this
Expand Down
45 changes: 29 additions & 16 deletions api/src/main/java/jakarta/persistence/FlushModeType.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@
* of the query. The persistence provider implementation might
* guarantee this by flushing pending updates to modified
* entities to the database before executing the query.
* <li>Otherwise, if {@link #COMMIT} is set, the effect on the
* results of the query of pending modifications to entities
* in the persistence context is unspecified.
* <li>Otherwise, if {@link #COMMIT} or {@link #EXPLICIT} is set,
* the effect on the results of the query of pending
* modifications to entities in the persistence context is
* unspecified.
* </ul>
*
* <p>When there is no transaction active, or if the persistence
Expand All @@ -56,31 +57,43 @@
* @since 1.0
*/
public enum FlushModeType {
/**
* Every flush is an explicit operation requested by the
* application program. The session is never automatically
* flushed. Pending modifications to entities held in the
* persistence context might not be visible to the processing
* of queries and might never be made persistent.
*
* @since 4.0
*/
EXPLICIT,

/**
* Pending modifications to entities associated with a
* persistence context joined to the current transaction
* are flushed to the database when the transaction commits.
* The provider is permitted to flush at other times, but is
* not required to.
* persistence context joined to the current transaction are
* automatically flushed to the database when the transaction
* commits. The provider is permitted to flush at other times,
* but is not required to. Pending modifications to entities
* associated with the persistence context might not be visible
* to the processing of queries.
*/
COMMIT,
COMMIT,

/**
* Pending modifications to entities associated with a
* persistence context joined to the current transaction
* are flushed to the database when the transaction commits.
* In addition, the persistence provider must ensure that
* every modification to the state of every entity associated
* with the persistence context which could possibly affect
* the result of a query is visible to the processing of the
* query. The persistence provider implementation might
* persistence context joined to the current transaction are
* automatically flushed to the database when the transaction
* commits. In addition, the persistence provider must ensure
* that every modification to the state of every entity
* associated with the persistence context which could possibly
* affect the result of a query is visible to the processing
* of the query. The persistence provider implementation might
* guarantee this by flushing pending modifications to the
* database before executing the query. The provider is
* permitted to flush at other times, but is not required to.
* <p>
* This is the default flush mode for a newly created
* {@link EntityManager}.
*/
AUTO
AUTO
}
65 changes: 65 additions & 0 deletions api/src/main/java/jakarta/persistence/ReadOnly.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/

// Contributors:
// Gavin King - 4.0

package jakarta.persistence;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Specifies that the annotated entity class is read-only,
* meaning that mutations to its fields and properties are
* not normally made persistent.
* <p>
* If an instance of a read-only entity is modified while
* the instance is in the managed state and associated with
* a persistence context, the behavior is undefined. Such
* modifications might be lost; they might be made persistent;
* or the provider might throw an exception. Portable
* applications should not depend on such provider-specific
* behavior.
* <p>
* Many write operations are permitted for read-only entities.
* A new instance of a read-only entity may be made persistent
* by calling {@link EntityManager#persist}. A managed instance
* may be removed by calling {@link EntityManager#remove}. And
* a new or detached instance may be passed to any appropriate
* operation of {@link EntityAgent}, including {@code insert()},
* {@code update()}, {@code upsert()}, and {@code delete()}.
* <p>
* The effect of this annotation may be selectively disabled
* for a given managed instance of a read-only entity class by
* passing the instance to {@link EntityManager#enableFlush}.
* The {@link ReadOnly} annotation has no effect at all on an
* {@code EntityAgent}.
* <p>
* The provider is not required to detect modifications to
* read-only entities and is encouraged to avoid tracking
* the state of read-only entities whenever it might result
* in a significant cost to performance.
*
* @see EntityManager#enableFlush(Object)
*
* @since 4.0
*/
@Documented
@Target(TYPE)
@Retention(RUNTIME)
public @interface ReadOnly {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this definition, I think the name @ReadOnly is odd since you can insert and remove entries. IMO @Immutable like we have in Hibernate ORM is a better name for this. It might be worth adding a section about inheritance i.e. polymorphic subclasses inherit the behavior.
Additionally, it might be interesting to allow the annotation on FIELD/METHOD to control which persistent attributes contribute to dirty checking, similar to what we define in Hibernate ORM already, which would allow us to get rid of the Hibernate ORM specific annotation.

Copy link
Copy Markdown
Member Author

@gavinking gavinking Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name @readonly is odd since you can insert and remove entries.

I .... guess I don't find that very odd?

It's perhaps slightly odd to say that you can delete a read-only thing, but I don't see how it's strange to say that you can create one.

IMO @Immutable like we have in Hibernate ORM is a better name for this.

"Immutable" to me implies a much higher level of un-modifiability than "read only". When I say something is "immutable", I usually mean in some sort of Platonic sense. Not just in the sense of "I'm not interested in modifying this thing".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, I consdered calling it @Unflushable, and I think maybe that's better.

}
8 changes: 5 additions & 3 deletions spec/src/main/asciidoc/ch03-entity-operations.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ the persistence context is joined to the transaction.
* If `FlushModeType.COMMIT` is specified, flushing will occur at
transaction commit; the persistence provider is permitted, but not
required, to flush at other times.
* If `FlushMode.EXPLICIT` is specified, the persistence provider
must never automatically flush modifications to the database.

If there is no transaction active or if the persistence context has not
been joined to the current transaction, the persistence provider must not
Expand Down Expand Up @@ -2695,8 +2697,8 @@ or in native SQL is executed via the `Query` or `TypedQuery` interface.
- Similarly, any native SQL statement that returns a row count is executed
by calling `executeUpdate()`.

Every query executed using `getResultList()`, `getResultStream()`,
`getSingleResult()`, or `getSingleResultOrNull()` has a well-defined
Every query executed using `getResultList()`, `getResultStream()`,
`getSingleResult()`, or `getSingleResultOrNull()` has a well-defined
result type.

- For an instance of `TypedQuery` that represents a criteria query,
Expand Down Expand Up @@ -2862,7 +2864,7 @@ The possible flush modes are enumerated by `FlushModeType`.

[source,java]
----
include::../../../../api/src/main/java/jakarta/persistence/FlushModeType.java[lines=18..-1]
include::../../../../api/src/main/java/jakarta/persistence/FlushModeType.java[lines=49..-1]
----

When a Jakarta Persistence or native SQL query is executed within
Expand Down
Loading