From 60187a311f9668547dc1ccf20ba622ca20fa1ae1 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Fri, 15 May 2026 10:09:00 +0200 Subject: [PATCH 1/2] [Bug #437] Add test reproducing the issue. --- .../UpdateOperationsMultiContextRunner.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java index 1fcf0580f..9744e5cfb 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java @@ -238,8 +238,7 @@ void testUpdateRemoveFromReferencedListInContext() { em.getTransaction().commit(); final OWLClassC resC = findRequired(OWLClassC.class, entityC.getUri(), cDescriptor); - assertEquals(entityC.getReferencedList().size() - removed.size(), resC.getReferencedList() - .size()); + assertEquals(entityC.getReferencedList().size() - removed.size(), resC.getReferencedList().size()); for (OWLClassA a : removed) { final OWLClassA resA = em.find(OWLClassA.class, a.getUri(), lstDescriptor); assertNotNull(resA); @@ -315,8 +314,8 @@ void testUpdateObjectPropertyInContextWithAssertionInContext() { }); assertTrue(em.createNativeQuery("ASK { GRAPH ?g { ?s ?p ?o. }}", Boolean.class).setParameter("g", CONTEXT_TWO) - .setParameter("s", entityD.getUri()).setParameter("p", URI.create(Vocabulary.P_HAS_OWL_CLASS_A)) - .setParameter("o", newA.getUri()).getSingleResult()); + .setParameter("s", entityD.getUri()).setParameter("p", URI.create(Vocabulary.P_HAS_OWL_CLASS_A)) + .setParameter("o", newA.getUri()).getSingleResult()); final OWLClassD result = findRequired(OWLClassD.class, entityD.getUri(), dDescriptor); assertNotNull(result.getOwlClassA()); assertEquals(newA.getUri(), result.getOwlClassA().getUri()); @@ -336,7 +335,7 @@ void updateSupportsObjectPropertyWithMultipleContexts() { entityF.setSimpleSet(new HashSet<>(Arrays.asList(entityA, entityA2))); final Descriptor descriptor = new EntityDescriptor(); descriptor.addAttributeContext(fieldSpecification(OWLClassF.class, "simpleSet"), CONTEXT_ONE) - .addAttributeContext(fieldSpecification(OWLClassF.class, "simpleSet"), CONTEXT_TWO); + .addAttributeContext(fieldSpecification(OWLClassF.class, "simpleSet"), CONTEXT_TWO); transactional(() -> em.merge(entityF, descriptor)); final OWLClassF result = findRequired(OWLClassF.class, entityF.getUri(), descriptor); @@ -354,7 +353,7 @@ void updateWithinTransactionSupportsObjectPropertyWithMultipleContexts() { final Descriptor descriptor = new EntityDescriptor(); descriptor.addAttributeContext(fieldSpecification(OWLClassF.class, "simpleSet"), CONTEXT_ONE) - .addAttributeContext(fieldSpecification(OWLClassF.class, "simpleSet"), CONTEXT_TWO); + .addAttributeContext(fieldSpecification(OWLClassF.class, "simpleSet"), CONTEXT_TWO); transactional(() -> { final OWLClassF toUpdate = findRequired(OWLClassF.class, entityF.getUri(), descriptor); toUpdate.setSimpleSet(new HashSet<>(Arrays.asList(entityA, entityA2))); @@ -367,4 +366,30 @@ void updateWithinTransactionSupportsObjectPropertyWithMultipleContexts() { assertTrue(result.getSimpleSet().stream().anyMatch(a -> a.getUri().equals(entityA.getUri()))); assertTrue(result.getSimpleSet().stream().anyMatch(a -> a.getUri().equals(entityA2.getUri()))); } + + /** + * Bug #437 + */ + @Test + void updateOnEntityInAnnotationBasedContextLoadedWithQuerySavesValueInCorrectContext() { + this.em = getEntityManager("updateOnEntityInAnnotationBasedContextLoadedWithQuerySavesValueInCorrectContext", false); + final ClassInContext entity = new ClassInContext(); + entity.setLabel("Initial label"); + transactional(() -> em.persist(entity)); + + transactional(() -> { + final List loaded = em.createQuery("SELECT c FROM ClassInContext c", ClassInContext.class) + .getResultList(); + assertEquals(1, loaded.size()); + final ClassInContext loadedEntity = loaded.get(0); + loadedEntity.setLabel("Updated label"); + }); + + final ClassInContext updatedEntity = em.find(ClassInContext.class, entity.getId()); + assertEquals("Updated label", updatedEntity.getLabel()); + assertTrue(em.createNativeQuery("ASK WHERE { GRAPH ?g { ?entity rdfs:label ?label . }", Boolean.class) + .setParameter("entity", entity) + .setParameter("g", URI.create("https://example.com/context")) + .getSingleResult()); + } } From 75401a0dae308c29e236de6578dd6d31fe22ecaa Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Fri, 15 May 2026 10:51:36 +0200 Subject: [PATCH 2/2] [Bug #437] Pass statically declared descriptor to TypedQuery when available. --- .../kbss/jopa/model/EntityManagerImpl.java | 37 +++++++++++-------- .../jopa/model/EntityManagerImplTest.java | 26 ++++++++++++- .../UpdateOperationsMultiContextRunner.java | 7 +++- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java index 1c3783542..c91f636ea 100644 --- a/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java +++ b/jopa-impl/src/main/java/cz/cvut/kbss/jopa/model/EntityManagerImpl.java @@ -469,9 +469,13 @@ public boolean isLoaded(Object object) { public QueryImpl createQuery(String qlString) { ensureOpen(); final QueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory().createQuery(qlString); + initQueryCallbacks(q); + return q; + } + + private void initQueryCallbacks(AbstractQuery q) { q.setRollbackOnlyMarker(this::markTransactionForRollback); q.setEnsureOpenProcedure(this::ensureOpen); - return q; } @Override @@ -483,19 +487,25 @@ public TypedQuery createQuery(CriteriaQuery criteriaQuery) { LOG.debug("CriteriaQuery translate to SOQL query: {}", soqlQuery); final TypedQueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory() .createQuery(soqlQuery, query.getResultType()); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); + initTypedQueryDescriptor(query.getResultType(), q); parameterFiller.setValuesToRegisteredParameters(q); return q; } + private void initTypedQueryDescriptor(Class resultClass, TypedQuery q) { + if (getCurrentPersistenceContext().isEntityType(resultClass)) { + q.setDescriptor(descriptorFactory.createDescriptor(resultClass)); + } + } + @Override public TypedQueryImpl createQuery(String query, Class resultClass) { ensureOpen(); final TypedQueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory().createQuery(query, resultClass); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); + initTypedQueryDescriptor(resultClass, q); return q; } @@ -503,8 +513,7 @@ public TypedQueryImpl createQuery(String query, Class resultClass) { public QueryImpl createNativeQuery(String sparqlString) { ensureOpen(); final QueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory().createNativeQuery(sparqlString); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); return q; } @@ -513,8 +522,8 @@ public TypedQueryImpl createNativeQuery(String sparqlString, Class res ensureOpen(); final TypedQueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory() .createNativeQuery(sparqlString, resultClass); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); + initTypedQueryDescriptor(resultClass, q); return q; } @@ -523,8 +532,7 @@ public QueryImpl createNativeQuery(String sparqlString, String resultSetMapping) ensureOpen(); final QueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory() .createNativeQuery(sparqlString, resultSetMapping); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); return q; } @@ -532,8 +540,7 @@ public QueryImpl createNativeQuery(String sparqlString, String resultSetMapping) public QueryImpl createNamedQuery(String name) { ensureOpen(); final QueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory().createNamedQuery(name); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); return q; } @@ -542,8 +549,8 @@ public TypedQueryImpl createNamedQuery(String name, Class resultClass) ensureOpen(); final TypedQueryImpl q = getCurrentPersistenceContext().sparqlQueryFactory() .createNamedQuery(name, resultClass); - q.setRollbackOnlyMarker(this::markTransactionForRollback); - q.setEnsureOpenProcedure(this::ensureOpen); + initQueryCallbacks(q); + initTypedQueryDescriptor(resultClass, q); return q; } diff --git a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java index 91a44a079..6c658cfcb 100644 --- a/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java +++ b/jopa-impl/src/test/java/cz/cvut/kbss/jopa/model/EntityManagerImplTest.java @@ -119,7 +119,7 @@ void setUp() throws Exception { when(emfMock.getMetamodel()).thenReturn(metamodelMock); when(emfMock.getServerSession()).thenReturn(serverSessionMock); final NamespaceResolver nsResolver = new NamespaceResolver(); - this.descriptorFactory = new DefaultEntityDescriptorFactory(metamodelMock, nsResolver); + this.descriptorFactory = spy(new DefaultEntityDescriptorFactory(metamodelMock, nsResolver)); when(metamodelMock.getNamespaceResolver()).thenReturn(nsResolver); this.mocks = new MetamodelMocks(); mocks.setMocks(metamodelMock); @@ -739,4 +739,28 @@ void cascadeRemoveTriggersLazyLoadingAndCascadesRemoval() { verify(connectorMock).remove(eq(toRemove.getUri()), eq(OWLClassJ.class), any(Descriptor.class)); verify(connectorMock).remove(eq(a.getUri()), eq(OWLClassA.class), any(Descriptor.class)); } + + @Test + void createTypedNativeQuerySetsDescriptorWhenResultTypeIsEntity() { + final EntityDescriptor d = new EntityDescriptor(Generators.createIndividualIdentifier()); + when(descriptorFactory.createDescriptor(OWLClassA.class)).thenReturn(d); + final TypedQueryImpl query = em.createNativeQuery("SELECT * WHERE { ?x a ?type . }", OWLClassA.class); + verify(descriptorFactory).createDescriptor(OWLClassA.class); + assertEquals(d, query.getDescriptor()); + } + + @Test + void createTypedQuerySetsDescriptorWhenResultTypeIsEntity() { + final EntityDescriptor d = new EntityDescriptor(Generators.createIndividualIdentifier()); + when(descriptorFactory.createDescriptor(OWLClassA.class)).thenReturn(d); + final TypedQueryImpl query = em.createQuery("SELECT a FROM OWLClassA a", OWLClassA.class); + verify(descriptorFactory).createDescriptor(OWLClassA.class); + assertEquals(d, query.getDescriptor()); + } + + @Test + void createTypedNativeQueryDoesNotSetDescriptorWhenResultTypeIsNotEntity() { + em.createNativeQuery("ASK WHERE { ?x a ?type } ", Boolean.class); + verify(descriptorFactory, never()).createDescriptor(Integer.class); + } } diff --git a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java index 9744e5cfb..8560f4399 100644 --- a/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java +++ b/jopa-integration-tests/src/main/java/cz/cvut/kbss/jopa/test/runner/UpdateOperationsMultiContextRunner.java @@ -25,6 +25,7 @@ import cz.cvut.kbss.jopa.test.environment.Generators; import cz.cvut.kbss.jopa.test.environment.PersistenceFactory; import cz.cvut.kbss.jopa.test.environment.TestEnvironmentUtils; +import cz.cvut.kbss.jopa.vocabulary.RDFS; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -378,7 +379,8 @@ void updateOnEntityInAnnotationBasedContextLoadedWithQuerySavesValueInCorrectCon transactional(() -> em.persist(entity)); transactional(() -> { - final List loaded = em.createQuery("SELECT c FROM ClassInContext c", ClassInContext.class) + final List loaded = em.createNativeQuery("SELECT ?x WHERE { GRAPH ?g {?x a ?type } }", ClassInContext.class) + .setParameter("type", URI.create(Vocabulary.CLASS_IRI_BASE + "classInContext")) .getResultList(); assertEquals(1, loaded.size()); final ClassInContext loadedEntity = loaded.get(0); @@ -387,9 +389,10 @@ void updateOnEntityInAnnotationBasedContextLoadedWithQuerySavesValueInCorrectCon final ClassInContext updatedEntity = em.find(ClassInContext.class, entity.getId()); assertEquals("Updated label", updatedEntity.getLabel()); - assertTrue(em.createNativeQuery("ASK WHERE { GRAPH ?g { ?entity rdfs:label ?label . }", Boolean.class) + assertTrue(em.createNativeQuery("ASK WHERE { GRAPH ?g { ?entity ?hasLabel ?label . } }", Boolean.class) .setParameter("entity", entity) .setParameter("g", URI.create("https://example.com/context")) + .setParameter("hasLabel", URI.create(RDFS.LABEL)) .getSingleResult()); } }