diff --git a/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SqlFileConfigBuilderCustomizer.java b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SqlFileConfigBuilderCustomizer.java new file mode 100644 index 0000000000000..b296ecb9053fe --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SqlFileConfigBuilderCustomizer.java @@ -0,0 +1,36 @@ +package io.quarkus.spring.data.deployment; + +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfigBuilder; +import io.smallrye.config.SmallRyeConfigBuilderCustomizer; + +public class SqlFileConfigBuilderCustomizer implements SmallRyeConfigBuilderCustomizer { + @Override + public void configBuilder(SmallRyeConfigBuilder builder) { + List supportedSqlFiles = List.of("import.sql", "data.sql"); + List sqlFilesThatExist = new ArrayList<>(); + + for (String sqlFile : supportedSqlFiles) { + URL resource = Thread.currentThread().getContextClassLoader().getResource(sqlFile); + // we only check for files that are part of the application itself, + // this is done as to follow what the HibernateOrmProcessor does + if ((resource != null) && !resource.getProtocol().equals("jar")) { + sqlFilesThatExist.add(sqlFile); + } + } + + // use a priority of 50 to make sure that this is overridable by any of the standard methods + if (!sqlFilesThatExist.isEmpty()) { + builder.withSources( + new PropertiesConfigSource( + Map.of("quarkus.hibernate-orm.sql-load-script", String.join(",", sqlFilesThatExist)), + "quarkus-spring-data-jpa", 50)); + } + + } +} diff --git a/extensions/spring-data-jpa/deployment/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer b/extensions/spring-data-jpa/deployment/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer new file mode 100644 index 0000000000000..5de637f10302e --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/main/resources/META-INF/services/io.smallrye.config.SmallRyeConfigBuilderCustomizer @@ -0,0 +1 @@ +io.quarkus.spring.data.deployment.SqlFileConfigBuilderCustomizer diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BothImportAndDataSqlTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BothImportAndDataSqlTest.java new file mode 100644 index 0000000000000..6a8a6db69b249 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/BothImportAndDataSqlTest.java @@ -0,0 +1,33 @@ +package io.quarkus.spring.data.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class BothImportAndDataSqlTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("users1.sql", "data.sql") + .addAsResource("users2.sql", "import.sql") + .addClasses(User.class, LoginEvent.class, UserRepository.class)) + .withConfigurationResource("application.properties"); + + @Inject + UserRepository repo; + + @Test + @Transactional + public void test() { + assertThat(repo.count()).isEqualTo(2); + } +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/ModifyingQueryWithFlushAndClearUsingDataSqlTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/ModifyingQueryWithFlushAndClearUsingDataSqlTest.java new file mode 100644 index 0000000000000..d30f2370e34b9 --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/deployment/ModifyingQueryWithFlushAndClearUsingDataSqlTest.java @@ -0,0 +1,119 @@ +package io.quarkus.spring.data.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ModifyingQueryWithFlushAndClearUsingDataSqlTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("import_users.sql", "data.sql") + .addClasses(User.class, LoginEvent.class, UserRepository.class)) + .withConfigurationResource("application.properties"); + + @Inject + UserRepository repo; + + @BeforeEach + @Transactional + public void setUp() { + final User user = getUser("JOHN"); + user.setLoginCounter(0); + repo.save(user); + } + + @Test + @Transactional + public void testNoAutoClear() { + getUser("JOHN"); // read user to attach it to entity manager + + repo.incrementLoginCounterPlain("JOHN"); + + final User userAfterIncrement = getUser("JOHN"); // we get the cached entity + // the read doesn't re-read the incremented counter and is therefore equal to the old value + assertThat(userAfterIncrement.getLoginCounter()).isEqualTo(0); + } + + @Test + @Transactional + public void testAutoClear() { + getUser("JOHN"); // read user to attach it to entity manager + + repo.incrementLoginCounterAutoClear("JOHN"); + + final User userAfterIncrement = getUser("JOHN"); + assertThat(userAfterIncrement.getLoginCounter()).isEqualTo(1); + } + + @Test + @Transactional + public void testNoAutoFlush() { + final User user = getUser("JOHN"); + createLoginEvent(user); + + repo.processLoginEventsPlain(); + + final User verifyUser = getUser("JOHN"); + // processLoginEvents did not see the new login event + final boolean allProcessed = verifyUser.getLoginEvents().stream() + .allMatch(loginEvent -> loginEvent.isProcessed()); + assertThat(allProcessed).describedAs("all LoginEvents are marked as processed").isFalse(); + } + + @Test + @Transactional + public void testAutoFlush() { + final User user = getUser("JOHN"); + createLoginEvent(user); + + repo.processLoginEventsPlainAutoClearAndFlush(); + + final User verifyUser = getUser("JOHN"); + final boolean allProcessed = verifyUser.getLoginEvents().stream() + .allMatch(loginEvent -> loginEvent.isProcessed()); + assertThat(allProcessed).describedAs("all LoginEvents are marked as processed").isTrue(); + } + + @Test + @Transactional + public void testNamedQueryOnEntities() { + User user = repo.getUserByFullNameUsingNamedQuery("John Doe"); + assertThat(user).isNotNull(); + } + + @Test + @Transactional + public void testNamedQueriesOnEntities() { + User user = repo.getUserByFullNameUsingNamedQueries("John Doe"); + assertThat(user).isNotNull(); + } + + private LoginEvent createLoginEvent(User user) { + final LoginEvent loginEvent = new LoginEvent(); + loginEvent.setUser(user); + loginEvent.setZonedDateTime(ZonedDateTime.now()); + user.addEvent(loginEvent); + return loginEvent; + } + + private User getUser(String userId) { + final Optional user = repo.findById(userId); + assertThat(user).describedAs("user <%s>", userId).isPresent(); + return user.get(); + } + +} diff --git a/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadWithDataSqlTest.java b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadWithDataSqlTest.java new file mode 100644 index 0000000000000..c6c0cf2b0385a --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/java/io/quarkus/spring/data/devmode/RepositoryReloadWithDataSqlTest.java @@ -0,0 +1,39 @@ +package io.quarkus.spring.data.devmode; + +import static org.hamcrest.CoreMatchers.containsString; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class RepositoryReloadWithDataSqlTest { + + @RegisterExtension + static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addAsResource("application.properties") + .addAsResource("import_books.sql", "data.sql") + .addClasses(Book.class, BookRepository.class, BookResource.class)); + + @Test + public void testRepositoryIsReloaded() { + RestAssured.get("/book").then() + .statusCode(200) + .body(containsString("Strangers"), containsString("Ascent"), containsString("Everything")); + + TEST.modifySourceFile("BookRepository.java", s -> s.replace("// ", + "java.util.Optional findById(Integer id);")); + + TEST.modifySourceFile("BookResource.java", s -> s.replace("// ", + "@GET @Path(\"/{id}\") @Produces(MediaType.APPLICATION_JSON)\n" + + " public java.util.Optional findById(@jakarta.ws.rs.PathParam(\"id\") Integer id) {\n" + + " return bookRepository.findById(id);\n" + + " }")); + + RestAssured.get("/book/1").then() + .statusCode(200) + .body(containsString("Strangers")); + } +} diff --git a/extensions/spring-data-jpa/deployment/src/test/resources/users1.sql b/extensions/spring-data-jpa/deployment/src/test/resources/users1.sql new file mode 100644 index 0000000000000..d04eefd2895af --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/resources/users1.sql @@ -0,0 +1 @@ +INSERT INTO user_(userid, fullname, logincounter, active) VALUES ('JOHN', 'John Doe', 0, true); diff --git a/extensions/spring-data-jpa/deployment/src/test/resources/users2.sql b/extensions/spring-data-jpa/deployment/src/test/resources/users2.sql new file mode 100644 index 0000000000000..8de8f2dd7c9eb --- /dev/null +++ b/extensions/spring-data-jpa/deployment/src/test/resources/users2.sql @@ -0,0 +1 @@ +INSERT INTO user_(userid, fullname, logincounter, active) VALUES ('JANE', 'Jane Doe', 1, true);