diff --git a/data/data-jpa/README.adoc b/data/data-jpa/README.adoc new file mode 100644 index 0000000..7a1eec1 --- /dev/null +++ b/data/data-jpa/README.adoc @@ -0,0 +1 @@ +Tests if Spring Data JPA with Hibernate, MySQL & HikariCP is working. diff --git a/data/data-jpa/build.gradle b/data/data-jpa/build.gradle new file mode 100644 index 0000000..7306fd1 --- /dev/null +++ b/data/data-jpa/build.gradle @@ -0,0 +1,24 @@ +plugins { + id "java" + id "org.springframework.boot" + id "org.springframework.cr.smoke-test" +} + +dependencies { + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + + implementation("org.crac:crac:$cracVersion") + implementation(project(":cr-listener")) + + runtimeOnly("com.mysql:mysql-connector-j") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + + appTestImplementation(project(":cr-smoke-test-support")) + appTestImplementation("org.awaitility:awaitility:4.2.0") +} + +crSmokeTest { + webApplication = false +} diff --git a/data/data-jpa/docker-compose.yml b/data/data-jpa/docker-compose.yml new file mode 100644 index 0000000..56371d7 --- /dev/null +++ b/data/data-jpa/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3.1' +services: + mysql: + image: 'mysql/mysql-server:8.0' + environment: + - 'MYSQL_DATABASE=test' + - 'MYSQL_USER=mysql' + - 'MYSQL_PASSWORD=password' + ports: + - '3306' diff --git a/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationTests.java b/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationTests.java new file mode 100644 index 0000000..1feba3d --- /dev/null +++ b/data/data-jpa/src/appTest/java/com/example/data/jpa/DataJpaApplicationTests.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 the original author or authors. + * + * 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 + * + * https://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 com.example.data.jpa; + +import static org.assertj.core.api.Assertions.*; + +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; +import org.springframework.cr.smoketest.support.assertj.AssertableOutput; +import org.springframework.cr.smoketest.support.junit.ApplicationTest; + +@ApplicationTest +public class DataJpaApplicationTests { + + @Test + void connectionTest(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> { + assertThat(output).hasLineMatching("Starting DataReader: was (INITIALIZED|STOPPED)"); + assertThat(output).hasLineMatching("count \\d+: Author\\{id=\\d, name='.*'}"); + }); + } + +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/Author.java b/data/data-jpa/src/main/java/com/example/data/jpa/Author.java new file mode 100644 index 0000000..48ef1a7 --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/Author.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023 the original author or authors. + * + * 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 + * + * https://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 com.example.data.jpa; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Author { + + @Id + @GeneratedValue + private Integer id; + + private String name; + + protected Author() { + } + + public Author(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Author author = (Author) o; + return Objects.equals(id, author.id) && Objects.equals(name, author.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public String toString() { + return "Author{" + "id=" + id + ", name='" + name + '\'' + '}'; + } + +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/AuthorRepository.java b/data/data-jpa/src/main/java/com/example/data/jpa/AuthorRepository.java new file mode 100644 index 0000000..3990fae --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/AuthorRepository.java @@ -0,0 +1,22 @@ +/* + * Copyright 2023 the original author or authors. + * + * 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 + * + * https://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 com.example.data.jpa; + +import org.springframework.data.repository.ListCrudRepository; + +public interface AuthorRepository extends ListCrudRepository { + +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/DataJpaApplication.java b/data/data-jpa/src/main/java/com/example/data/jpa/DataJpaApplication.java new file mode 100644 index 0000000..c8ff62f --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/DataJpaApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 2023 the original author or authors. + * + * 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 + * + * https://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 com.example.data.jpa; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@EnableTransactionManagement +public class DataJpaApplication { + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(DataJpaApplication.class, args); + } + +} diff --git a/data/data-jpa/src/main/java/com/example/data/jpa/DataReader.java b/data/data-jpa/src/main/java/com/example/data/jpa/DataReader.java new file mode 100644 index 0000000..e368776 --- /dev/null +++ b/data/data-jpa/src/main/java/com/example/data/jpa/DataReader.java @@ -0,0 +1,122 @@ +/* + * Copyright 2023 the original author or authors. + * + * 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 + * + * https://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 com.example.data.jpa; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; +import org.hibernate.Session; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.SmartLifecycle; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.ObjectUtils; + +@Component +@Transactional +@EnableScheduling +class DataReader implements SmartLifecycle { + + private final Object lifecycleMonitor = new Object(); + + private final AtomicInteger counter = new AtomicInteger(0); + + private State state = State.INITIALIZED; + + private List authorIds; + + @Autowired + private AuthorRepository authorRepository; + + @PersistenceContext + private EntityManager em; + + enum State { + + INITIALIZED, STARTED, STOPPED + + } + + @Override + public void start() { + + synchronized (lifecycleMonitor) { + switch (state) { + case INITIALIZED, STOPPED -> { + System.out.printf("Starting DataReader: was %s\n", state); + if (ObjectUtils.isEmpty(authorIds)) { + authorIds = insertAuthors().stream().map(Author::getId).collect(Collectors.toList()); + } + state = State.STARTED; + } + } + } + } + + @Scheduled(fixedDelay = 1000) + public void scheduled() { + + if (isRunning()) { + + int count = counter.incrementAndGet(); + Author author = authorRepository.findById(authorIds.get(count % 3)).orElse(null); + System.out.printf("count %03d: %s\n", count, author); + } + } + + @Override + public void stop() { + + synchronized (lifecycleMonitor) { + switch (state) { + case INITIALIZED, STARTED -> { + System.out.printf("Stopping DataReader: was %s\n", state); + state = State.STOPPED; + } + } + } + } + + @Override + public boolean isRunning() { + return State.STARTED.equals(state); + } + + private List insertAuthors() { + + List authors = authorRepository.saveAll(List.of( // + new Author(null, "Brandon Sanderson"), // + new Author(null, "Brent Weeks"), // + new Author(null, "Peter V. Brett"))); + + em.flush(); + em.getEntityManagerFactory().getCache().evictAll(); + + Session session = em.unwrap(Session.class); + + if (session != null) { + session.clear(); // internal cache clear + } + + return authors; + } + +} diff --git a/data/data-jpa/src/main/resources/application.properties b/data/data-jpa/src/main/resources/application.properties new file mode 100644 index 0000000..7d5a815 --- /dev/null +++ b/data/data-jpa/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect +spring.jpa.hibernate.ddl-auto=create + +spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT_3306:3306}/test +spring.datasource.username=mysql +spring.datasource.password=password +spring.datasource.hikari.allow-pool-suspension=true