jakartaee / jakartaee/persistence
TCK Department4 uses mixed access strategy without @Access, leading to undefined behavior
- Dominant language
- Java
- Stars
- 267
- Forks
- 78
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 13
Description
## Summary
`https://github.com/jakartaee/persistence/blob/main/tck/spec-tests/src/main/java/ee/jakarta/tck/persistence/core/annotations/mapkeyenumerated/Client.java`
`https://github.com/jakartaee/persistence/blob/main/tck/spec-tests/src/main/java/ee/jakarta/tck/persistence/core/annotations/mapkeyenumerated/Department4.java`
`Department4` in the `mapkeyenumerated` test uses mixed access in a way that is not compliant with Jakarta Persistence access rules, and that is the root cause of the apparent standalone success.
Related to: https://github.com/jakartaee/platform-tck/issues/2678
both tested with hibernate 7.2.6
## Why it can appear to succeed
`Department4` declares `@Id` on the field, so the entity uses implicit **field access**.
At the same time:
- `lastNameEmployees` is marked `@Transient` on the field
- the actual mapping annotations for `lastNameEmployees` are placed on the getter:
- `@ElementCollection`
- `@CollectionTable`
- `@AttributeOverrides`
- `@MapKeyEnumerated`
Under field access, those getter annotations are ignored. That means `lastNameEmployees` is **not a persistent attribute**.
However, the test setup assigns the map in Java before persistence:
```java
deptRef4[0].setLastNameEmployees(link4);
```
So in standalone execution, the test can appear to pass if it reuses the same managed `Department4` instance or stale in-memory state created during setup. In that case, the plain Java field may still contain the map even though the provider never mapped or loaded it from the database.
Once the persistence context is cleared or a fresh entity instance is loaded, the field is `null` and the test fails.
In other words, the outcome can depend on persistence-context reuse rather than actual mapping correctness.
## How to check
Add a metamodel probe in `elementCollectionTest`:
```java
var entityType = getEntityManager().getMetamodel().entity(Department4.class);
logger.log(Logger.Level.INFO, "Department4 attributes:");
for (var a : entityType.getAttributes()) {
logger.log(Logger.Level.INFO, " " + a.getName() + " / " + a.getPersistentAttributeType());
}
logger.log(
Logger.Level.INFO,
"Has lastNameEmployees = " +
entityType.getAttributes().stream()
.anyMatch(a -> a.getName().equals("lastNameEmployees"))
);
```
### Observed result
```text
Department4 attributes:
id / BASIC
name / BASIC
Has lastNameEmployees = false
```
This shows that `lastNameEmployees` is not part of the persistent metamodel.
## How to reproduce the failure reliably
To eliminate false success caused by reused in-memory state, clear the persistence context before loading the entity:
```java
getEntityManager().clear(); // notice i have cleared context , this shouldn't matter because it would be fine to get emp from database if mapping worked
getEntityTransaction().begin();
Employee4 emp = getEntityManager().find(Employee4.class, 8);
logger.log(Logger.Level.TRACE, "Name:" + emp.getLastName());
Department4 dept = emp.getDepartment();
logger.log(Logger.Level.INFO, "dept managed = " + getEntityManager().contains(dept));
logger.log(Logger.Level.INFO, "map null = " + (dept.getLastNameEmployees() == null));
Map emps = dept.getLastNameEmployees();
if (emps == null) {
throw new IllegalStateException("Department4.lastNameEmployees is null after reload");
}
```
### Observed result
```text
dept managed = true
map null = true
```
This proves the collection is not being loaded from the database, because it is not mapped.
## Root cause
`Department4` mixes access styles:
- field access is selected by `@Id` on the field
- `lastNameEmployees` is excluded with field-level `@Transient`
- mapping annotations for `lastNameEmployees` are incorrectly placed on the getter
As a result, `lastNameEmployees` is ignored as a persistent attribute.
## Suggested fix
Make `Department4` use a single consistent access strategy.
Contributor guide
Research direction
Start with tck/spec-tests/src/main/java/ee/jakarta/tck/persistence/core/annotations/mapkeyenumerated/Department4.java and its use in Client.java, then inspect the elementCollectionTest path. Check the Department4 metamodel and reload behavior after clearing the persistence context; done means lastNameEmployees is persistent and is populated from the database rather than reused in-memory state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- database, testing
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 54/100