diff --git a/integrations/cdi/jpa-cdi/src/main/resources/META-INF/helidon/native-image/reflection-config.json b/integrations/cdi/jpa-cdi/src/main/resources/META-INF/helidon/native-image/reflection-config.json index 7b1d6221b..ee754e2d8 100644 --- a/integrations/cdi/jpa-cdi/src/main/resources/META-INF/helidon/native-image/reflection-config.json +++ b/integrations/cdi/jpa-cdi/src/main/resources/META-INF/helidon/native-image/reflection-config.json @@ -1,6 +1,7 @@ { "annotated":[ - "javax.persistence.Entity" + "javax.persistence.Entity", + "javax.persistence.MappedSuperclass" ], "class-hierarchy": [ "javax.xml.stream.XMLEventFactory", diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index 4c5f8cbc3..00ec373c5 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -75,6 +75,7 @@ public class HelidonReflectionFeature implements Feature { private static final boolean TRACE = NativeConfig.option("reflection.trace", false); private static final String AT_ENTITY = "javax.persistence.Entity"; + private static final String AT_MAPPED_SUPERCLASS = "javax.persistence.MappedSuperclass"; private static final String AT_REGISTER_REST_CLIENT = "org.eclipse.microprofile.rest.client.inject.RegisterRestClient"; private static final Map, Class> PRIMITIVES_TO_OBJECT = new HashMap<>(); @@ -379,23 +380,35 @@ public class HelidonReflectionFeature implements Feature { @SuppressWarnings("unchecked") private void processEntity(BeforeAnalysisContext context) { - final Class annotation = (Class) context.access() + final Class entityAnnotation = (Class) context.access() .findClassByName(AT_ENTITY); - if (annotation == null) { + final Class superclassAnnotation = (Class) context.access() + .findClassByName(AT_MAPPED_SUPERCLASS); + Set> annotatedSet = null; + traceParsing(() -> "Looking up annotated by " + AT_ENTITY); + if (entityAnnotation != null) { + annotatedSet = new HashSet<>(findAnnotated(context, AT_ENTITY)); + } + traceParsing(() -> "Looking up annotated by " + AT_MAPPED_SUPERCLASS); + if (superclassAnnotation != null) { + if (annotatedSet == null) { + annotatedSet = new HashSet<>(findAnnotated(context, AT_MAPPED_SUPERCLASS)); + } else { + annotatedSet.addAll(findAnnotated(context, AT_MAPPED_SUPERCLASS)); + } + } + if (annotatedSet == null || annotatedSet.isEmpty()) { return; } - traceParsing(() -> "Looking up annotated by " + AT_ENTITY); - final List> annotatedList = findAnnotated(context, AT_ENTITY); - annotatedList.forEach(aClass -> { + annotatedSet.forEach(aClass -> { + traceParsing(() -> "Processing annotated class " + aClass.getName()); String resourceName = aClass.getName().replace('.', '/') + ".class"; InputStream resourceStream = aClass.getClassLoader().getResourceAsStream(resourceName); Resources.registerResource(resourceName, resourceStream); for (Field declaredField : aClass.getDeclaredFields()) { if (!Modifier.isPublic(declaredField.getModifiers()) && declaredField.getAnnotations().length == 0) { RuntimeReflection.register(declaredField); - if (TRACE) { - System.out.println(" non annotated field " + declaredField); - } + traceParsing(() -> " added non annotated field " + declaredField); } } }); diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java index 26f47dbf3..f21cfb020 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java +++ b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DbUtils.java @@ -15,8 +15,12 @@ */ package io.helidon.tests.integration.jpa.appl; +import java.util.List; + import javax.persistence.EntityManager; +import io.helidon.tests.integration.jpa.model.Pokemon; + /** * Database utilities. */ @@ -37,4 +41,22 @@ public class DbUtils { em.getEntityManagerFactory().getCache().evictAll(); } + /** + * Find pokemon by name from pokemon List. + * + * @param pokemons List to search + * @param name name of pokemon + * @return found pokemon or null when no such pokemon exists + */ + public static Pokemon findPokemonByName(List pokemons, String name) { + if (pokemons != null && !pokemons.isEmpty()) { + for (Pokemon pokemon : pokemons) { + if (pokemon.getName().equals(name)) { + return pokemon; + } + } + } + return null; + } + } diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java index c6719c345..68a682dbe 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java +++ b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/DeleteIT.java @@ -27,7 +27,10 @@ import javax.persistence.criteria.Root; import io.helidon.tests.integration.jpa.dao.Create; import io.helidon.tests.integration.jpa.dao.Delete; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; +import io.helidon.tests.integration.jpa.model.Trainer; /** * Verify delete operations of ORM (server side). @@ -47,6 +50,7 @@ public class DeleteIT { @MPTest public TestResult setup(TestResult result) { Create.dbInsertMisty(em); + Create.dbInsertViridian(em); return result; } @@ -59,6 +63,7 @@ public class DeleteIT { @MPTest public TestResult destroy(TestResult result) { Delete.dbDeleteMisty(em); + Delete.dbDeleteViridian(em); return result; } @@ -132,4 +137,31 @@ public class DeleteIT { return result; } + /** + * Delete Viridian City. + * + * @param result test execution result + * @return test execution result + */ + @MPTest + public TestResult testDeleteViridianCity(TestResult result) { + City city = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", "Viridian City") + .getSingleResult(); + Stadium stadium = city.getStadium(); + Trainer trainer = stadium.getTrainer(); + List pokemons = trainer.getPokemons(); + em.remove(city); + em.remove(trainer); + pokemons.forEach(poklemon -> em.remove(poklemon)); + DbUtils.cleanEm(em); + List cities = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", "Viridian City") + .getResultList(); + result.assertTrue(cities.isEmpty()); + return result; + } + } diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java index b1a92cfa8..aa0f68952 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java +++ b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/InsertIT.java @@ -17,13 +17,16 @@ package io.helidon.tests.integration.jpa.appl; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.enterprise.context.ApplicationScoped; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; import io.helidon.tests.integration.jpa.model.Trainer; import io.helidon.tests.integration.jpa.model.Type; @@ -35,6 +38,8 @@ public class InsertIT { private static final Set DELETE_POKEMONS = new HashSet<>(); private static final Set DELETE_TRAINERS = new HashSet<>(); + private static final Set DELETE_STADIUMS = new HashSet<>(); + private static final Set DELETE_TOWNS = new HashSet<>(); @PersistenceContext(unitName = "test") private EntityManager em; @@ -51,18 +56,30 @@ public class InsertIT { em.createQuery("DELETE FROM Type t WHERE t.id = :id") .setParameter("id", 20) .executeUpdate(); + // Towns cleanup + DELETE_TOWNS.forEach((id) -> { + em.createQuery("DELETE FROM City c WHERE c.id = :id") + .setParameter("id", id) + .executeUpdate(); + }); + // Stadiums cleanup + DELETE_STADIUMS.forEach((id) -> { + em.createQuery("DELETE FROM Stadium s WHERE s.id = :id") + .setParameter("id", id) + .executeUpdate(); + }); // Pokemons cleanup - for (int id : DELETE_POKEMONS) { + DELETE_POKEMONS.forEach((id) -> { em.createQuery("DELETE FROM Pokemon p WHERE p.id = :id") .setParameter("id", id) .executeUpdate(); - } + }); // Trainers cleanup - for (int id : DELETE_TRAINERS) { + DELETE_TRAINERS.forEach((id) -> { em.createQuery("DELETE FROM Trainer t WHERE t.id = :id") .setParameter("id", id) .executeUpdate(); - } + }); return result; } @@ -131,4 +148,60 @@ public class InsertIT { return result; } + /** + * Verify complex create operation (persist) on a full ORM model (Lt. Surge in Vermilion City). + * + * @param result test execution result + * @return test execution result + */ + @MPTest + public TestResult testInsertTownWithStadium(TestResult result) { + final Trainer[] trainers = new Trainer[1]; + final Pokemon[] pokemons = new Pokemon[6]; + final Stadium[] stadiums = new Stadium[1]; + final City[] cities = new City[1]; + Type steel = em.find(Type.class, 9); + Type electric = em.find(Type.class, 13); + trainers[0] = new Trainer("Lt. Surge", 28); + pokemons[0] = new Pokemon(trainers[0], "Raichu", 1521, Arrays.asList(electric)); + pokemons[1] = new Pokemon(trainers[0], "Manectric", 1589, Arrays.asList(electric)); + pokemons[2] = new Pokemon(trainers[0], "Magnezone", 1853, Arrays.asList(electric)); + pokemons[3] = new Pokemon(trainers[0], "Electrode", 1237, Arrays.asList(electric)); + pokemons[4] = new Pokemon(trainers[0], "Pachirisu", 942, Arrays.asList(electric)); + pokemons[5] = new Pokemon(trainers[0], "Electivire", 1931, Arrays.asList(electric)); + stadiums[0] = new Stadium("Vermilion Gym", trainers[0]); + cities[0] = new City("Vermilion City", "Mina", stadiums[0]); + em.persist(trainers[0]); + em.persist(pokemons[0]); + em.persist(pokemons[1]); + em.persist(pokemons[2]); + em.persist(pokemons[3]); + em.persist(pokemons[4]); + em.persist(pokemons[5]); + //em.persist(stadiums[0]); + em.persist(cities[0]); + em.flush(); + DbUtils.cleanEm(em); + City dbCity = em.find(City.class, cities[0].getId()); + Stadium dbStadium = dbCity.getStadium(); + Trainer dbTrainer = dbStadium.getTrainer(); + List dbPokemons = dbTrainer.getPokemons(); + Set pokemonSet = new HashSet<>(pokemons.length); + pokemonSet.addAll(Arrays.asList(pokemons)); + dbPokemons.forEach((dbPokemon) -> { + result.assertTrue(pokemonSet.remove(dbPokemon)); + }); + result.assertTrue(pokemonSet.isEmpty()); + result.assertEquals(trainers[0], dbTrainer); + result.assertEquals(stadiums[0], dbStadium); + result.assertEquals(cities[0], dbCity); + for (Pokemon pokemon : pokemons) { + DELETE_POKEMONS.add(pokemon.getId()); + } + DELETE_TRAINERS.add(dbTrainer.getId()); + DELETE_STADIUMS.add(dbStadium.getId()); + DELETE_TOWNS.add(dbCity.getId()); + return result; + } + } diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java index 729924577..81e9240c7 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java +++ b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/QueryIT.java @@ -26,6 +26,7 @@ import javax.persistence.criteria.Root; import io.helidon.tests.integration.jpa.dao.Create; import io.helidon.tests.integration.jpa.dao.Delete; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; import io.helidon.tests.integration.jpa.model.Trainer; @@ -49,6 +50,7 @@ public class QueryIT { @MPTest public TestResult setup(TestResult result) { ASH_ID = Create.dbInsertAsh(em); + Create.dbInsertCeladon(em); return result; } @@ -61,6 +63,7 @@ public class QueryIT { @MPTest public TestResult destroy(TestResult result) { Delete.dbDeleteAsh(em); + Delete.dbDeleteCeladon(em); return result; } @@ -117,4 +120,48 @@ public class QueryIT { return result; } + /** + * Query Celadon city using JPQL. + * + * @param result test execution result + * @return test execution result + */ + @MPTest + public TestResult testQueryCeladonJPQL(TestResult result) { + City city = em.createQuery( + "SELECT c FROM City c " + + "JOIN FETCH c.stadium s " + + "JOIN FETCH s.trainer t " + + "WHERE c.name = :name", City.class) + .setParameter("name", "Celadon City") + .getSingleResult(); + result.assertEquals(city.getName(), "Celadon City"); + result.assertEquals(city.getStadium().getName(), "Celadon Gym"); + result.assertEquals(city.getStadium().getTrainer().getName(), "Erika"); + return result; + } + + /** + * Query Celadon city using CriteriaQuery. + * + * @param result test execution result + * @return test execution result + */ + @MPTest + public TestResult testQueryCeladonCriteria(TestResult result) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(City.class); + Root cityRoot = cq.from(City.class); + cityRoot + .fetch("stadium") + .fetch("trainer"); + cq.select(cityRoot) + .where(cb.equal(cityRoot.get("name"), "Celadon City")); + City city = em.createQuery(cq).getSingleResult(); + result.assertEquals(city.getName(), "Celadon City"); + result.assertEquals(city.getStadium().getName(), "Celadon Gym"); + result.assertEquals(city.getStadium().getTrainer().getName(), "Erika"); + return result; + } + } diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java index ade4b51a9..d49b6e02e 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java +++ b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/TestResult.java @@ -176,6 +176,27 @@ public class TestResult { } } + /** + * Test result check: boolean true + * + * @param value actual value to be checked + */ + public void assertTrue(Boolean value, String header) { + StringBuilder sb = new StringBuilder(); + sb.append(header); + sb.append(" Expected: true"); + sb.append(" Actual: "); + sb.append(Boolean.toString(value)); + sb.append(" :: "); + sb.append(value ? "EQUAL" : "NOT EQUAL"); + String message = sb.toString(); + msg.add(message); + if (!value) { + ob.add("error", message); + failed = true; + } + } + /** * Test result check: boolean false * diff --git a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java index e8d04cb12..9edecb3f8 100644 --- a/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java +++ b/tests/integration/jpa/appl/src/main/java/io/helidon/tests/integration/jpa/appl/UpdateIT.java @@ -15,6 +15,11 @@ */ package io.helidon.tests.integration.jpa.appl; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import javax.enterprise.context.ApplicationScoped; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @@ -25,7 +30,10 @@ import javax.persistence.criteria.Root; import io.helidon.tests.integration.jpa.dao.Create; import io.helidon.tests.integration.jpa.dao.Delete; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; +import io.helidon.tests.integration.jpa.model.Trainer; /** * Verify update operations of ORM (server side). @@ -46,6 +54,7 @@ public class UpdateIT { @MPTest public TestResult setup(TestResult result) { Create.dbInsertBrock(em); + Create.dbInsertSaffron(em); return result; } @@ -58,6 +67,7 @@ public class UpdateIT { @MPTest public TestResult destroy(TestResult result) { Delete.dbDeleteBrock(em); + Delete.dbDeleteSaffron(em); return result; } @@ -138,5 +148,58 @@ public class UpdateIT { return result; } + /** + * Update Saffron City data structure. + * Replace stadium trainer with new guy who will get all pokemons from previous trainer. + * Also Alakazam evolves to Mega Alakazam at the same time. + * + * @param result test execution result + * @return test execution result + */ + @MPTest + public TestResult testUpdateSaffron(TestResult result) { + City[] cities = new City[1]; + Set pokemonNames = new HashSet<>(6); + cities[0] = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", "Saffron City") + .getSingleResult(); + Stadium stadium = cities[0].getStadium(); + Trainer sabrina = stadium.getTrainer(); + Trainer janine = new Trainer("Janine", 24); + stadium.setTrainer(janine); + List pokemons = sabrina.getPokemons(); + janine.setPokemons(pokemons); + sabrina.setPokemons(Collections.EMPTY_LIST); + em.remove(sabrina); + em.persist(janine); + for (Pokemon pokemon : pokemons) { + pokemon.setTrainer(janine); + pokemonNames.add(pokemon.getName()); + em.persist(pokemon); + } + em.persist(stadium); + Pokemon alkazam = DbUtils.findPokemonByName(pokemons, "Alakazam"); + // Update pokemon by query + em.createQuery( + "UPDATE Pokemon p SET p.name = :newName, p.cp = :newCp WHERE p.id = :id") + .setParameter("newName", "Mega Alakazam") + .setParameter("newCp", 4348) + .setParameter("id", alkazam.getId()) + .executeUpdate(); + pokemonNames.remove("Alakazam"); + pokemonNames.add("Mega Alakazam"); + DbUtils.cleanEm(em); + City city = em.find(City.class, cities[0].getId()); + stadium = city.getStadium(); + Trainer trainer = stadium.getTrainer(); + em.refresh(trainer); + pokemons = trainer.getPokemons(); + result.assertEquals(trainer.getName(), "Janine"); + for (Pokemon pokemon : pokemons) { + result.assertTrue(pokemonNames.remove(pokemon.getName()), "Pokemon " + pokemon.getName() + " is missing"); + } + return result; + } } diff --git a/tests/integration/jpa/appl/src/main/resources/META-INF/persistence.xml b/tests/integration/jpa/appl/src/main/resources/META-INF/persistence.xml index 46fde90b0..752f45f09 100644 --- a/tests/integration/jpa/appl/src/main/resources/META-INF/persistence.xml +++ b/tests/integration/jpa/appl/src/main/resources/META-INF/persistence.xml @@ -22,6 +22,8 @@ xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"> + io.helidon.tests.integration.jpa.model.City + io.helidon.tests.integration.jpa.model.Stadium io.helidon.tests.integration.jpa.model.Type io.helidon.tests.integration.jpa.model.Trainer io.helidon.tests.integration.jpa.model.Pokemon diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java index 49ef0446f..9102e7d93 100644 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java +++ b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/DeleteIT.java @@ -62,4 +62,12 @@ public class DeleteIT { ClientUtils.callTest("/test/DeleteIT.testDeleteCriteria"); } + /** + * Delete Viridian City. + */ + @Test + public void testDeleteViridianCity() { + ClientUtils.callTest("/test/DeleteIT.testDeleteViridianCity"); + } + } diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java index d202820b0..cf90f2169 100644 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java +++ b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/InsertIT.java @@ -32,7 +32,7 @@ public class InsertIT { * Verify simple create operation (persist) on a single database row. */ @Test - public void testUpdateEntity() { + public void testInsertType() { ClientUtils.callTest("/test/InsertIT.testInsertType"); } @@ -41,8 +41,16 @@ public class InsertIT { * Relations are not marked for cascade persist operation so every entity instance has to be persisted separately. */ @Test - public void testUpdateJPQL() { + public void testInsertTrainerWithPokemons() { ClientUtils.callTest("/test/InsertIT.testInsertTrainerWithPokemons"); } + /** + * Verify complex create operation (persist) on a full ORM model (Lt. Surge in Vermilion City). + */ + @Test + public void testInsertTownWithStadium() { + ClientUtils.callTest("/test/InsertIT.testInsertTownWithStadium"); + } + } diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java index e7fa3f65a..401e39352 100644 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java +++ b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/QueryIT.java @@ -59,4 +59,20 @@ public class QueryIT { ClientUtils.callTest("/test/QueryIT.testQueryCriteria"); } + /** + * Query Celadon city using JPQL. + */ + @Test + public void testQueryCeladonJPQL() { + ClientUtils.callTest("/test/QueryIT.testQueryCeladonJPQL"); + } + + /** + * Query Celadon city using CriteriaQuery. + */ + @Test + public void testQueryCeladonCriteria() { + ClientUtils.callTest("/test/QueryIT.testQueryCeladonCriteria"); + } + } diff --git a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java index dbee00386..9d82c0fd4 100644 --- a/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java +++ b/tests/integration/jpa/appl/src/test/java/io/helidon/tests/integration/jpa/appl/test/UpdateIT.java @@ -61,4 +61,14 @@ public class UpdateIT { ClientUtils.callTest("/test/UpdateIT.testUpdateCriteria"); } + /** + * Update Saffron City data structure. + * Replace stadium trainer with new guy who will get all pokemons from previous trainer. + * Also Alakazam evolves to Mega Alakazam at the same time. + */ + @Test + public void testUpdateSaffron() { + ClientUtils.callTest("/test/UpdateIT.testUpdateSaffron"); + } + } diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java index 4931d0276..0b55eed5b 100644 --- a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java +++ b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Create.java @@ -17,9 +17,12 @@ package io.helidon.tests.integration.jpa.dao; import java.util.Arrays; import java.util.List; + import javax.persistence.EntityManager; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Trainer; import io.helidon.tests.integration.jpa.model.Type; @@ -115,7 +118,7 @@ public class Create { /** * Create Misty and her pokemons. - * Misty and her pokemons are used for update tests only. + * Misty and her pokemons are used for delete tests only. * * @param em Entity manager instance */ @@ -137,4 +140,70 @@ public class Create { em.getEntityManagerFactory().getCache().evictAll(); } + /** + * Create Celadon City with stadium and trainer. + * Used for query tests only. + */ + public static void dbInsertCeladon(final EntityManager em) { + Type poison = em.find(Type.class, 4); + Type grass = em.find(Type.class, 12); + Type psychic = em.find(Type.class, 14); + Trainer trainer = new Trainer("Erika", 16); + Stadium stadium = new Stadium("Celadon Gym", trainer); + City city = new City("Celadon City", "Madam Celadon", stadium); + em.persist(trainer); + em.persist(new Pokemon(trainer, "Gloom", 651, Arrays.asList(grass, poison))); + em.persist(new Pokemon(trainer, "Victreebel", 751, Arrays.asList(grass, poison))); + em.persist(new Pokemon(trainer, "Tangela", 234, Arrays.asList(grass))); + em.persist(new Pokemon(trainer, "Vileplume", 1571, Arrays.asList(grass, poison))); + em.persist(new Pokemon(trainer, "Weepinbell", 1923, Arrays.asList(grass, poison))); + em.persist(new Pokemon(trainer, "Exeggcute", 317, Arrays.asList(grass, psychic))); + //em.persist(stadium); + em.persist(city); + } + + /** + * Create Saffron City with stadium and trainer. + * Used for update tests only. + */ + public static void dbInsertSaffron(final EntityManager em) { + Type fighting = em.find(Type.class, 2); + Type psychic = em.find(Type.class, 14); + Type ice = em.find(Type.class, 15); + Trainer trainer = new Trainer("Sabrina", 23); + Stadium stadium = new Stadium("Saffron Gym", trainer); + City city = new City("Saffron City", "Koichi", stadium); + em.persist(trainer); + em.persist(new Pokemon(trainer, "Alakazam", 2178, Arrays.asList(psychic))); + em.persist(new Pokemon(trainer, "Espeon", 2745, Arrays.asList(psychic))); + em.persist(new Pokemon(trainer, "Mr. Mime", 1478, Arrays.asList(psychic))); + em.persist(new Pokemon(trainer, "Jynx", 2471, Arrays.asList(psychic, ice))); + em.persist(new Pokemon(trainer, "Wobbuffet", 1478, Arrays.asList(psychic))); + em.persist(new Pokemon(trainer, "Gallade", 2147, Arrays.asList(psychic, fighting))); + //em.persist(stadium); + em.persist(city); + } + + /** + * Create Viridian City with stadium and trainer. + * Used for delete tests only. + */ + public static void dbInsertViridian(final EntityManager em) { + Type poison = em.find(Type.class, 4); + Type ground = em.find(Type.class, 5); + Type rock = em.find(Type.class, 6); + Trainer trainer = new Trainer("Giovanni", 37); + Stadium stadium = new Stadium("Viridian Gym", trainer); + City city = new City("Viridian City", "Koichi", stadium); + em.persist(trainer); + em.persist(new Pokemon(trainer, "Rhyperior", 3841, Arrays.asList(ground, rock))); + em.persist(new Pokemon(trainer, "Golem", 3651, Arrays.asList(ground, rock))); + em.persist(new Pokemon(trainer, "Nidoking", 2451, Arrays.asList(ground, poison))); + em.persist(new Pokemon(trainer, "Marowak", 2249, Arrays.asList(ground))); + em.persist(new Pokemon(trainer, "Sandslash", 1953, Arrays.asList(ground))); + em.persist(new Pokemon(trainer, "Nidoqueen", 3147, Arrays.asList(ground))); + //em.persist(stadium); + em.persist(city); + } + } diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java index 9df7d57bc..97457435f 100644 --- a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java +++ b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/dao/Delete.java @@ -19,7 +19,9 @@ import java.util.List; import javax.persistence.EntityManager; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; import io.helidon.tests.integration.jpa.model.Trainer; /** @@ -61,6 +63,60 @@ public class Delete { dbDeleteTrainerAndHisPokemons(em, "Ash Ketchum"); } + /** + * Delete Celadon City. + * Celadon City is used for query tests only. + * + * @param em Entity manager instance + */ + public static void dbDeleteCeladon(final EntityManager em) { + dbDeleteCity(em, "Celadon City"); + } + + /** + * Delete Saffron City. + * Saffron City is used for update tests only. + * + * @param em Entity manager instance + */ + public static void dbDeleteSaffron(final EntityManager em) { + dbDeleteCity(em, "Saffron City"); + } + + /** + * Delete Viridian City. + * Viridian City is used for update tests only. + * + * @param em Entity manager instance + */ + public static void dbDeleteViridian(final EntityManager em) { + dbDeleteCity(em, "Viridian City"); + } + + /** + * Delete city. + * + * @param em Entity manager instance + * @param name name of city to delete + */ + public static void dbDeleteCity(final EntityManager em, final String name) { + List cities = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", name) + .getResultList(); + if (!cities.isEmpty()) { + cities.forEach((city) -> { + Stadium stadium = city.getStadium(); + Trainer trainer = stadium.getTrainer(); + List pokemons = trainer.getPokemons(); + em.remove(city); + //em.remove(stadium); + pokemons.forEach((pokemon) -> em.remove(pokemon)); + em.remove(trainer); + }); + } + } + /** * Delete trainer and his pokemons. * Trainer is identified by his name. @@ -69,16 +125,20 @@ public class Delete { * @param name name of trainer to delete */ public static void dbDeleteTrainerAndHisPokemons(final EntityManager em, final String name) { - Trainer trainer = em.createQuery( + List trainers = em.createQuery( "SELECT t FROM Trainer t WHERE t.name = :name", Trainer.class) .setParameter("name", name) - .getSingleResult(); - List pokemons = em.createQuery( - "SELECT p FROM Pokemon p INNER JOIN p.trainer t WHERE t.name = :name", Pokemon.class) - .setParameter("name", name) .getResultList(); - pokemons.forEach((pokemon) -> em.remove(pokemon)); - em.remove(trainer); + if (!trainers.isEmpty()) { + trainers.forEach((trainer) -> { + List pokemons = em.createQuery( + "SELECT p FROM Pokemon p INNER JOIN p.trainer t WHERE t.name = :name", Pokemon.class) + .setParameter("name", name) + .getResultList(); + pokemons.forEach((pokemon) -> em.remove(pokemon)); + em.remove(trainer); + }); + } } /** @@ -108,12 +168,32 @@ public class Delete { em.createQuery("DELETE FROM Type").executeUpdate(); } + /** + * Delete all cities. + * + * @param em Entity manager instance + */ + public static void deleteCities(final EntityManager em) { + em.createQuery("DELETE FROM City").executeUpdate(); + } + + /** + * Delete all stadiums. + * + * @param em Entity manager instance + */ + public static void deleteStadiums(final EntityManager em) { + em.createQuery("DELETE FROM Stadium").executeUpdate(); + } + /** * Delete all database records. * * @param em Entity manager instance */ public static void dbCleanup(final EntityManager em) { + deleteCities(em); + deleteStadiums(em); deletePokemons(em); deleteTrainers(em); deleteTypes(em); diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/City.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/City.java new file mode 100644 index 000000000..a3bb0e114 --- /dev/null +++ b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/City.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.tests.integration.jpa.model; + +import java.util.Objects; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; + +/** + * City in pokemon world + */ +@Entity +public class City extends Settlement { + + private String mayor; + + @OneToOne(cascade = CascadeType.ALL) + @MapsId + private Stadium stadium; + + public City() { + } + + public City(String name, String mayor, Stadium stadium) { + super(name); + this.mayor = mayor; + this.stadium = stadium; + } + + public String getMayor() { + return mayor; + } + + public void setMayor(String mayor) { + this.mayor = mayor; + } + + public Stadium getStadium() { + return stadium; + } + + public void setStadium(Stadium stadium) { + this.stadium = stadium; + } + + @Override + public boolean equals(Object oth) { + if (this == oth) { + return true; + } + if (oth == null) { + return false; + } + if (super.equals(oth) && (oth instanceof City)) { + return Objects.equals(mayor, ((City) oth).mayor) + && Objects.equals(stadium, ((City) oth).stadium); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = super.hashCode(); + if (mayor != null) { + hashCode = hashCode * 31 + mayor.hashCode(); + } + if (stadium != null) { + hashCode = hashCode * 31 + stadium.hashCode(); + } + return hashCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Town: {id="); + sb.append(getId()); + sb.append(", name="); + if (getName() != null) { + sb.append('"').append(getName()).append('"'); + } else { + sb.append(""); + } + sb.append(", mayor="); + if (mayor != null) { + sb.append('"').append(mayor).append('"'); + } else { + sb.append(""); + } + sb.append(", stadium="); + if (stadium != null) { + sb.append('"').append(stadium.toString()).append('"'); + } else { + sb.append(""); + } + sb.append('}'); + return sb.toString(); + } + +} diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Settlement.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Settlement.java new file mode 100644 index 000000000..914551c50 --- /dev/null +++ b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Settlement.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.tests.integration.jpa.model; + +import java.util.Objects; + +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * Settlement in pokemon world. + */ +@MappedSuperclass +public class Settlement { + + @Id + @GeneratedValue + private int id; + + private String name; + + public Settlement() { + } + + public Settlement(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object oth) { + if (this == oth) { + return true; + } + if (oth == null) { + return false; + } + if (oth instanceof Settlement) { + return id == ((Settlement)oth).id + && Objects.equals(name, ((Settlement)oth).name); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = id; + if (name != null) { + hashCode = hashCode * 31 + name.hashCode(); + } + return hashCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Settlement: {id="); + sb.append(id); + sb.append(", name="); + if (name != null) { + sb.append('"').append(name).append('"'); + } else { + sb.append(""); + } + sb.append('}'); + return sb.toString(); + } + +} diff --git a/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Stadium.java b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Stadium.java new file mode 100644 index 000000000..472268ab5 --- /dev/null +++ b/tests/integration/jpa/model/src/main/java/io/helidon/tests/integration/jpa/model/Stadium.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * + * Licensed 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 io.helidon.tests.integration.jpa.model; + +import java.util.Objects; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +/** + * Pokemon stadium. + */ +@Entity +public class Stadium { + + @Id + @GeneratedValue + private int id; + + private String name; + + @ManyToOne + @JoinColumn(name = "trainer_id") + private Trainer trainer; + + public Stadium() { + } + + public Stadium(String name, Trainer trainer) { + this.name = name; + this.trainer = trainer; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Trainer getTrainer() { + return trainer; + } + + public void setTrainer(Trainer trainer) { + this.trainer = trainer; + } + + @Override + public boolean equals(Object oth) { + if (this == oth) { + return true; + } + if (oth == null) { + return false; + } + if (oth instanceof Stadium) { + return id == ((Stadium) oth).id + && Objects.equals(name, ((Stadium) oth).name) + && Objects.equals(trainer, ((Stadium) oth).trainer); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = id; + if (name != null) { + hashCode = hashCode * 31 + name.hashCode(); + } + if (name != null) { + hashCode = hashCode * 31 + trainer.hashCode(); + } + return hashCode; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Stadium: {id="); + sb.append(id); + sb.append(", name="); + if (name != null) { + sb.append('"').append(name).append('"'); + } else { + sb.append(""); + } + sb.append(", trainer="); + if (trainer != null) { + sb.append('"').append(trainer.toString()).append('"'); + } else { + sb.append(""); + } + sb.append('}'); + return sb.toString(); + } + +} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java index 6a0ab3227..268ef6f0a 100644 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java +++ b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/DbUtils.java @@ -42,34 +42,15 @@ public class DbUtils { * @param pu persistence unit context */ public static void dbInit(PU pu) { - dbInsertTypes(pu); - dbInsertAsh(pu); + Create.dbInsertTypes(pu.getEm()); + ASH_ID = Create.dbInsertAsh(pu.getEm()); + Create.dbInsertCeladon(pu.getEm()); pu.getEm().flush(); pu.getEm().clear(); pu.getEm().getEntityManagerFactory().getCache().evictAll(); } - /** - * Insert pokemon types. - * - * @param pu persistence unit context - */ - public static void dbInsertTypes(PU pu) { - final EntityManager em = pu.getEm(); - Create.dbInsertTypes(em); - } - - /** - * Insert trainer Ash and his pokemons. - * - * @param pu persistence unit context - */ - public static void dbInsertAsh(PU pu) { - final EntityManager em = pu.getEm(); - ASH_ID = Create.dbInsertAsh(em); - } - /** * Delete all database records. * @@ -108,4 +89,22 @@ public class DbUtils { return q.getResultList(); } + /** + * Find pokemon by name from pokemon List. + * + * @param pokemons List to search + * @param name name of pokemon + * @return found pokemon or null when no such pokemon exists + */ + public static Pokemon findPokemonByName(List pokemons, String name) { + if (pokemons != null && !pokemons.isEmpty()) { + for (Pokemon pokemon : pokemons) { + if (pokemon.getName().equals(name)) { + return pokemon; + } + } + } + return null; + } + } diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java index 280afc906..10bf7e52d 100644 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java +++ b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/DeleteIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (city) 2020 Oracle and/or its affiliates. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,10 @@ import javax.persistence.criteria.Root; import io.helidon.tests.integration.jpa.dao.Create; import io.helidon.tests.integration.jpa.dao.Delete; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; +import io.helidon.tests.integration.jpa.model.Trainer; import io.helidon.tests.integration.jpa.simple.PU; import org.junit.jupiter.api.AfterAll; @@ -48,6 +51,7 @@ public class DeleteIT { pu.tx(pu -> { final EntityManager em = pu.getEm(); Create.dbInsertMisty(em); + Create.dbInsertViridian(em); }); } @@ -56,6 +60,7 @@ public class DeleteIT { pu.tx(pu -> { final EntityManager em = pu.getEm(); Delete.dbDeleteMisty(em); + Delete.dbDeleteViridian(em); }); } @@ -145,4 +150,32 @@ public class DeleteIT { }); } + /** + * Delete Viridian City. + */ + @Test + public void testDeleteViridianCity() { + pu.tx(pu -> { + final EntityManager em = pu.getEm(); + City city = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", "Viridian City") + .getSingleResult(); + Stadium stadium = city.getStadium(); + Trainer trainer = stadium.getTrainer(); + List pokemons = trainer.getPokemons(); + em.remove(city); + em.remove(trainer); + pokemons.forEach(poklemon -> em.remove(poklemon)); + }); + pu.tx(pu -> { + final EntityManager em = pu.getCleanEm(); + List cities = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", "Viridian City") + .getResultList(); + assertTrue(cities.isEmpty()); + }); + } + } diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java index cb41c8ef6..73005ced9 100644 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java +++ b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/InsertIT.java @@ -17,11 +17,14 @@ package io.helidon.tests.integration.jpa.simple.test; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.persistence.EntityManager; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Trainer; import io.helidon.tests.integration.jpa.model.Type; import io.helidon.tests.integration.jpa.simple.PU; @@ -31,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Verify create/insert operations of ORM. @@ -41,6 +45,8 @@ public class InsertIT { private static final Set DELETE_POKEMONS = new HashSet<>(); private static final Set DELETE_TRAINERS = new HashSet<>(); + private static final Set DELETE_STADIUMS = new HashSet<>(); + private static final Set DELETE_TOWNS = new HashSet<>(); @BeforeAll public static void setup() { @@ -55,18 +61,30 @@ public class InsertIT { em.createQuery("DELETE FROM Type t WHERE t.id = :id") .setParameter("id", 20) .executeUpdate(); + // Towns cleanup + DELETE_TOWNS.forEach((id) -> { + em.createQuery("DELETE FROM City c WHERE c.id = :id") + .setParameter("id", id) + .executeUpdate(); + }); + // Stadiums cleanup + DELETE_STADIUMS.forEach((id) -> { + em.createQuery("DELETE FROM Stadium s WHERE s.id = :id") + .setParameter("id", id) + .executeUpdate(); + }); // Pokemons cleanup - for (int id : DELETE_POKEMONS) { + DELETE_POKEMONS.forEach((id) -> { em.createQuery("DELETE FROM Pokemon p WHERE p.id = :id") .setParameter("id", id) .executeUpdate(); - } + }); // Trainers cleanup - for (int id : DELETE_TRAINERS) { + DELETE_TRAINERS.forEach((id) -> { em.createQuery("DELETE FROM Trainer t WHERE t.id = :id") .setParameter("id", id) .executeUpdate(); - } + }); }); pu = null; } @@ -142,4 +160,63 @@ public class InsertIT { }); } + /** + * Verify complex create operation (persist) on a full ORM model (Lt. Surge in Vermilion City). + */ + @Test + void testTownWithStadium() { + final Trainer[] trainers = new Trainer[1]; + final Pokemon[] pokemons = new Pokemon[6]; + final Stadium[] stadiums = new Stadium[1]; + final City[] cities = new City[1]; + pu.tx(pu -> { + final EntityManager em = pu.getEm(); + Type steel = em.find(Type.class, 9); + Type electric = em.find(Type.class, 13); + trainers[0] = new Trainer("Lt. Surge", 28); + pokemons[0] = new Pokemon(trainers[0], "Raichu", 1521, Arrays.asList(electric)); + pokemons[1] = new Pokemon(trainers[0], "Manectric", 1589, Arrays.asList(electric)); + pokemons[2] = new Pokemon(trainers[0], "Magnezone", 1853, Arrays.asList(electric)); + pokemons[3] = new Pokemon(trainers[0], "Electrode", 1237, Arrays.asList(electric)); + pokemons[4] = new Pokemon(trainers[0], "Pachirisu", 942, Arrays.asList(electric)); + pokemons[5] = new Pokemon(trainers[0], "Electivire", 1931, Arrays.asList(electric)); + stadiums[0] = new Stadium("Vermilion Gym", trainers[0]); + cities[0] = new City("Vermilion City", "Mina", stadiums[0]); + em.persist(trainers[0]); + em.persist(pokemons[0]); + em.persist(pokemons[1]); + em.persist(pokemons[2]); + em.persist(pokemons[3]); + em.persist(pokemons[4]); + em.persist(pokemons[5]); + //em.persist(stadiums[0]); + em.persist(cities[0]); + em.flush(); + }); + pu.tx(pu -> { + final EntityManager em = pu.getCleanEm(); + City dbCity = em.find(City.class, cities[0].getId()); + Stadium dbStadium = dbCity.getStadium(); + Trainer dbTrainer = dbStadium.getTrainer(); + List dbPokemons = dbTrainer.getPokemons(); + Set pokemonSet = new HashSet<>(pokemons.length); + for (Pokemon pokemon : pokemons) { + pokemonSet.add(pokemon); + } + for (Pokemon dbPokemon : dbPokemons) { + assertTrue(pokemonSet.remove(dbPokemon)); + } + assertTrue(pokemonSet.isEmpty()); + assertEquals(trainers[0], dbTrainer); + assertEquals(stadiums[0], dbStadium); + assertEquals(cities[0], dbCity); + for (Pokemon pokemon : pokemons) { + DELETE_POKEMONS.add(pokemon.getId()); + } + DELETE_TRAINERS.add(dbTrainer.getId()); + DELETE_STADIUMS.add(dbStadium.getId()); + DELETE_TOWNS.add(dbCity.getId()); + }); + } + } diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java index 24570d429..1e6a34fb0 100644 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java +++ b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/QueryIT.java @@ -20,9 +20,12 @@ import java.util.List; import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; import javax.persistence.criteria.Root; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; import io.helidon.tests.integration.jpa.model.Trainer; import io.helidon.tests.integration.jpa.simple.DbUtils; import io.helidon.tests.integration.jpa.simple.PU; @@ -31,6 +34,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -83,7 +87,7 @@ public class QueryIT { } /** - * Query trainer Ash and his pokemons using JPQL. + * Query trainer Ash and his pokemons using CriteriaQuery. */ @Test public void testQueryCriteria() { @@ -101,4 +105,46 @@ public class QueryIT { }); } -} \ No newline at end of file + /** + * Query Celadon city using JPQL. + */ + @Test + public void testQueryCeladonJPQL() { + pu.tx(pu -> { + final EntityManager em = pu.getCleanEm(); + City city = em.createQuery( + "SELECT c FROM City c " + + "JOIN FETCH c.stadium s " + + "JOIN FETCH s.trainer t " + + "WHERE c.name = :name", City.class) + .setParameter("name", "Celadon City") + .getSingleResult(); + assertEquals(city.getName(), "Celadon City"); + assertEquals(city.getStadium().getName(), "Celadon Gym"); + assertEquals(city.getStadium().getTrainer().getName(), "Erika"); + }); + } + + /** + * Query Celadon city using CriteriaQuery. + */ + @Test + public void testQueryCeladonCriteria() { + pu.tx(pu -> { + final EntityManager em = pu.getCleanEm(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(City.class); + Root cityRoot = cq.from(City.class); + cityRoot + .fetch("stadium") + .fetch("trainer"); + cq.select(cityRoot) + .where(cb.equal(cityRoot.get("name"), "Celadon City")); + City city = em.createQuery(cq).getSingleResult(); + assertEquals(city.getName(), "Celadon City"); + assertEquals(city.getStadium().getName(), "Celadon Gym"); + assertEquals(city.getStadium().getTrainer().getName(), "Erika"); + }); + } + +} diff --git a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java index 22547380f..9ecdc83ef 100644 --- a/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java +++ b/tests/integration/jpa/simple/src/test/java/io/helidon/tests/integration/jpa/simple/test/UpdateIT.java @@ -15,6 +15,11 @@ */ package io.helidon.tests.integration.jpa.simple.test; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import javax.persistence.EntityManager; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -23,7 +28,11 @@ import javax.persistence.criteria.Root; import io.helidon.tests.integration.jpa.dao.Create; import io.helidon.tests.integration.jpa.dao.Delete; +import io.helidon.tests.integration.jpa.model.City; import io.helidon.tests.integration.jpa.model.Pokemon; +import io.helidon.tests.integration.jpa.model.Stadium; +import io.helidon.tests.integration.jpa.model.Trainer; +import io.helidon.tests.integration.jpa.simple.DbUtils; import io.helidon.tests.integration.jpa.simple.PU; import org.junit.jupiter.api.AfterAll; @@ -31,6 +40,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Verify update operations on ORM. @@ -39,32 +49,24 @@ public class UpdateIT { private static PU pu; - // Brock and his pokemons are used for update tests only - private static void dbInsertBrock() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Create.dbInsertBrock(em); - }); - } - - // Delete Brock and his pokemons after update tests - private static void dbDeleteBrock() { - pu.tx(pu -> { - final EntityManager em = pu.getEm(); - Delete.dbDeleteBrock(em); - }); - } - @BeforeAll public static void setup() { pu = PU.getInstance(); - dbInsertBrock(); + pu.tx(pu -> { + final EntityManager em = pu.getEm(); + Create.dbInsertBrock(em); + Create.dbInsertSaffron(em); + }); } @AfterAll public static void destroy() { - dbDeleteBrock(); - pu = null; + pu.tx(pu -> { + final EntityManager em = pu.getEm(); + Delete.dbDeleteBrock(em); + Delete.dbDeleteSaffron(em); + pu = null; + }); } /** @@ -146,6 +148,61 @@ public class UpdateIT { assertEquals(1568, dbUrsaring.getCp()); }); } - - + + /** + * Update Saffron City data structure. + * Replace stadium trainer with new guy who will get all pokemons from previous trainer. + * Also Alakazam evolves to Mega Alakazam at the same time. + */ + @Test + public void testUpdateSaffron() { + City[] cities = new City[1]; + Set pokemonNames = new HashSet<>(6); + pu.tx(pu -> { + final EntityManager em = pu.getEm(); + cities[0] = em.createQuery( + "SELECT c FROM City c WHERE c.name = :name", City.class) + .setParameter("name", "Saffron City") + .getSingleResult(); + Stadium stadium = cities[0].getStadium(); + Trainer sabrina = stadium.getTrainer(); + Trainer janine = new Trainer("Janine", 24); + stadium.setTrainer(janine); + List pokemons = sabrina.getPokemons(); + janine.setPokemons(pokemons); + sabrina.setPokemons(Collections.EMPTY_LIST); + em.remove(sabrina); + em.persist(janine); + for (Pokemon pokemon : pokemons) { + pokemon.setTrainer(janine); + pokemonNames.add(pokemon.getName()); + em.persist(pokemon); + } + em.persist(stadium); + Pokemon alkazam = DbUtils.findPokemonByName(pokemons, "Alakazam"); + System.out.println("TRAINER: "+alkazam.getTrainer().getName()); + // Update pokemon by query + em.createQuery( + "UPDATE Pokemon p SET p.name = :newName, p.cp = :newCp WHERE p.id = :id") + .setParameter("newName", "Mega Alakazam") + .setParameter("newCp", 4348) + .setParameter("id", alkazam.getId()) + .executeUpdate(); + pokemonNames.remove("Alakazam"); + pokemonNames.add("Mega Alakazam"); + }); + pu.tx(pu -> { + final EntityManager em = pu.getCleanEm(); + City city = em.find(City.class, cities[0].getId()); + Stadium stadium = city.getStadium(); + Trainer trainer = stadium.getTrainer(); + em.refresh(trainer); + List pokemons = trainer.getPokemons(); + assertEquals(trainer.getName(), "Janine"); + for (Pokemon pokemon : pokemons) { + assertTrue(pokemonNames.remove(pokemon.getName()), "Pokemon " + pokemon.getName() + " is missing"); + } + }); + } + } diff --git a/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml b/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml index 9be383ad5..479ea28d7 100644 --- a/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml +++ b/tests/integration/jpa/simple/src/test/resources/META-INF/persistence.xml @@ -25,6 +25,8 @@ io.helidon.tests.integration.jpa.model.Type io.helidon.tests.integration.jpa.model.Trainer io.helidon.tests.integration.jpa.model.Pokemon + io.helidon.tests.integration.jpa.model.Stadium + io.helidon.tests.integration.jpa.model.City