From 18bab38851dab444c061af2c6a98dacf1c04d633 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Wed, 17 Jan 2024 11:57:31 +0300 Subject: [PATCH 1/8] Spring Data JPA Example Delete un use files Delete spring-boot-parent Set plugin versions Fix issue Fix pom.xml Delete version Spring Data JPA Sample move to jdbc module Spring Data JPA Sample move to jdbc module Spring Data JPA Sample move to jdbc module Spring Data JPA Sample move to jdbc module --- jdbc/pom.xml | 12 + jdbc/spring-data-jpa/pom.xml | 154 ++++++++++++ .../kotlin/tech/ydb/jpa/pagination/Author.kt | 17 ++ .../kotlin/tech/ydb/jpa/pagination/Book.kt | 22 ++ .../tech/ydb/jpa/pagination/BookRepository.kt | 39 +++ .../ydb/jpa/pagination/PagingApplication.kt | 6 + .../ydb/jpa/simple/SimpleUserRepository.kt | 150 +++++++++++ .../main/kotlin/tech/ydb/jpa/simple/User.kt | 57 +++++ .../tech/ydb/jpa/simple/UserApplication.kt | 11 + .../resources/application-postgres.properties | 1 + .../main/resources/application-ydb.properties | 4 + .../src/main/resources/log4j2.xml | 14 ++ .../kotlin/tech/ydb/jpa/PostgresDockerTest.kt | 31 +++ .../test/kotlin/tech/ydb/jpa/YdbDockerTest.kt | 29 +++ .../ydb/jpa/pagination/PaginationTests.kt | 168 +++++++++++++ .../ydb/jpa/simple/SimpleRepositoryTest.kt | 236 ++++++++++++++++++ .../resources/application-test.properties | 5 + 17 files changed, 956 insertions(+) create mode 100644 jdbc/spring-data-jpa/pom.xml create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/User.kt create mode 100644 jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt create mode 100644 jdbc/spring-data-jpa/src/main/resources/application-postgres.properties create mode 100644 jdbc/spring-data-jpa/src/main/resources/application-ydb.properties create mode 100644 jdbc/spring-data-jpa/src/main/resources/log4j2.xml create mode 100644 jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt create mode 100644 jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt create mode 100644 jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt create mode 100644 jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt create mode 100644 jdbc/spring-data-jpa/src/test/resources/application-test.properties diff --git a/jdbc/pom.xml b/jdbc/pom.xml index ed83a57..becc23a 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -38,4 +38,16 @@ + + + + jdk17-examples + + [17 + + + spring-data-jpa + + + \ No newline at end of file diff --git a/jdbc/spring-data-jpa/pom.xml b/jdbc/spring-data-jpa/pom.xml new file mode 100644 index 0000000..84480c8 --- /dev/null +++ b/jdbc/spring-data-jpa/pom.xml @@ -0,0 +1,154 @@ + + 4.0.0 + + tech.ydb.jdbc.examples + ydb-jdbc-examples + 1.1.0-SNAPSHOT + + + spring-data-jpa + Spring Data JPA Example + Basic example for SpringBoot3 and Hibernate 6 + + 17 + 1.9.22 + 0.9.1 + 2.0.5 + 3.2.1 + + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring.boot.version} + + + org.springframework.data + spring-data-commons + ${spring.boot.version} + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + tech.ydb.dialects + hibernate-ydb-dialect + ${hibernate.ydb.dialect.version} + + + tech.ydb.jdbc + ydb-jdbc-driver + ${ydb.jdbc.driver.version} + + + com.github.javafaker + javafaker + 1.0.2 + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + 1.7.3 + + + org.postgresql + postgresql + 42.7.1 + + + org.testcontainers + postgresql + 1.19.1 + test + + + tech.ydb.test + ydb-junit5-support + test + + + org.junit.jupiter + junit-jupiter-api + + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + jpa + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + + + \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt new file mode 100644 index 0000000..a0f516b --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt @@ -0,0 +1,17 @@ +package tech.ydb.jpa.pagination + +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Entity +@Table(name = "authors") +class Author { + + @Id + lateinit var id: String + + lateinit var firstName: String + + lateinit var lastName: String +} diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt new file mode 100644 index 0000000..3141a48 --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt @@ -0,0 +1,22 @@ +package tech.ydb.jpa.pagination + +import jakarta.persistence.Entity +import jakarta.persistence.Id +import jakarta.persistence.ManyToOne +import jakarta.persistence.Table +import java.util.* + +@Entity +@Table(name = "books") +class Book { + + @Id + lateinit var id: String + + lateinit var title: String + lateinit var isbn10: String + lateinit var publicationDate: Date + + @ManyToOne + lateinit var author: Author +} diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt new file mode 100644 index 0000000..d55c29d --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt @@ -0,0 +1,39 @@ +package tech.ydb.jpa.pagination + +import org.springframework.data.domain.* +import org.springframework.data.repository.ListCrudRepository +import org.springframework.data.repository.query.Param + +interface BookRepository : ListCrudRepository { + + /** + * Uses an offset based pagination that first sorts the entries by their [ publication_date][Book.getPublicationDate] + * and then limits the result by dropping the number of rows specified in the + * [offset][Pageable.getOffset] clause. To retrieve [Page.getTotalElements] an additional count query + * is executed. + * + * @param title + * @param pageable + */ + fun findByTitleContainsOrderByPublicationDate(@Param("title") title: String, pageable: Pageable): Page + + /** + * Uses an offset based slicing that first sorts the entries by their [ publication_date][Book.getPublicationDate] and then limits the result by dropping the number of rows specified in the + * [offset][Pageable.getOffset] clause. + * + * @param title + * @param pageable + */ + fun findBooksByTitleContainsOrderByPublicationDate(title: String, pageable: Pageable): Slice + + /** + * Depending on the provided [ScrollPosition] either [ offset][org.springframework.data.domain.OffsetScrollPosition] + * or [keyset][org.springframework.data.domain.KeysetScrollPosition] scrolling is possible. Scrolling + * through results requires a stable [org.springframework.data.domain.Sort] which is different from what + * [Pageable.getSort] offers. The limit is defined via the Top keyword. + * + * @param title + * @param scrollPosition + */ + fun findTop2ByTitleContainsOrderByPublicationDate(title: String, scrollPosition: ScrollPosition): Window +} diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt new file mode 100644 index 0000000..89f961d --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt @@ -0,0 +1,6 @@ +package tech.ydb.jpa.pagination + +import org.springframework.boot.autoconfigure.SpringBootApplication + +@SpringBootApplication +class PagingApplication diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt new file mode 100644 index 0000000..a877d9c --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt @@ -0,0 +1,150 @@ +package tech.ydb.jpa.simple; + +import org.springframework.data.domain.Limit; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.ListCrudRepository; +import org.springframework.scheduling.annotation.Async; +import java.util.concurrent.CompletableFuture +import java.util.stream.Stream + +/** + * Simple repository interface for {@link User} instances. The interface is used to declare the so-called query methods, + * i.e. methods to retrieve single entities or collections of them. + */ +interface SimpleUserRepository : ListCrudRepository { + + /** + * Find the user with the given username. This method will be translated into a query using the + * {@link jakarta.persistence.NamedQuery} annotation at the {@link User} class. + * + * @param username + */ + fun findByTheUsersName(username: String): User + + /** + * Uses {@link Optional} as return and parameter type. + * + * @param username + */ + fun findByUsername(username: String?): User? + + /** + * Find all users with the given lastname. This method will be translated into a query by constructing it directly + * from the method name as there is no other query declared. + * + * @param lastname + */ + fun findByLastname(lastname: String): List + + /** + * Find at most the number of users defined via maxResults with the given lastname. + * This method will be translated into a query by constructing it directly from the method name as there is no other + * query declared. + * + * @param lastname + * @param maxResults the maximum number of results returned. + */ + fun findByLastname(lastname: String, maxResults: Limit): List + + /** + * Returns all users with the given firstname. This method will be translated into a query using the one declared in + * the {@link Query} annotation declared one. + * + * @param firstname + */ + @Query("select u from User u where u.firstname = :firstname") + fun findByFirstname(firstname: String): List + + /** + * Returns at most the number of users defined via {@link Limit} with the given firstname. This method will be + * translated into a query using the one declared in the {@link Query} annotation declared one. + * + * @param firstname + * @param maxResults the maximum number of results returned. + */ + @Query("select u from User u where u.firstname = :firstname") + fun findByFirstname(firstname: String, maxResults: Limit): List + + /** + * Returns all users with the given name as first- or lastname. This makes the query to method relation much more + * refactoring-safe as the order of the method parameters is completely irrelevant. + * + * @param name + */ + @Query("select u from User u where u.firstname = :name or u.lastname = :name") + fun findByFirstnameOrLastname(name: String): List + + /** + * Returns the total number of entries deleted as their lastnames match the given one. + * + * @param lastname + * @return + */ + fun removeByLastname(lastname: String): Long + + /** + * Returns a {@link Slice} counting a maximum number of {@link Pageable#getPageSize()} users matching given criteria + * starting at {@link Pageable#getOffset()} without prior count of the total number of elements available. + * + * @param lastname + * @param page + */ + fun findByLastnameOrderByUsernameAsc(lastname: String, page: Pageable): Slice + + /** + * Return the first 2 users ordered by their lastname asc. + * + *
+	 * Example for findFirstK / findTopK functionality.
+	 * 
+ */ + fun findFirst2ByOrderByLastnameAsc(): List + + /** + * Return the first 2 users ordered by the given {@code sort} definition. + * + *
+	 * This variant is very flexible because one can ask for the first K results when a ASC ordering
+	 * is used as well as for the last K results when a DESC ordering is used.
+	 * 
+ * + * @param sort + */ + fun findTop2By(sort: Sort): List + + /** + * Return all the users with the given firstname or lastname. Makes use of SpEL (Spring Expression Language). + * + * @param user + */ + @Query("select u from User u where u.firstname = :#{#user.firstname} or u.lastname = :#{#user.lastname}") + fun findByFirstnameOrLastname(user: User): Iterable + + /** + * Sample default method. + * + * @param user + */ + fun findByLastname(user: User): List { + return findByLastname(user.lastname); + } + + /** + * Sample method to demonstrate support for {@link Stream} as a return type with a custom query. The query is executed + * in a streaming fashion which means that the method returns as soon as the first results are ready. + */ + @Query("select u from User u") + fun streamAllCustomers(): Stream + + /** + * Sample method to demonstrate support for {@link Stream} as a return type with a derived query. The query is + * executed in a streaming fashion which means that the method returns as soon as the first results are ready. + */ + fun findAllByLastnameIsNotNull(): Stream + + @Async + fun readAllBy(): CompletableFuture> +} diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/User.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/User.kt new file mode 100644 index 0000000..69f44ab --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/User.kt @@ -0,0 +1,57 @@ +package tech.ydb.jpa.simple + +import jakarta.persistence.* +import org.hibernate.annotations.GenericGenerator +import org.hibernate.engine.spi.SharedSessionContractImplementor +import org.hibernate.id.IdentifierGenerator +import org.springframework.data.util.ProxyUtils +import java.util.concurrent.ThreadLocalRandom + +@Entity +@Table(name = "Users", indexes = [Index(name = "username_index", columnList = "username")]) +@NamedQuery(name = "User.findByTheUsersName", query = "from User u where u.username = ?1") +class User { + + @Id + @GeneratedValue(generator = "random-int-id") + @GenericGenerator(name = "random-int-id", type = RandomLongGenerator::class) + var id: Long = 0 + + lateinit var username: String + + lateinit var firstname: String + + lateinit var lastname: String + + class RandomLongGenerator : IdentifierGenerator { + override fun generate(session: SharedSessionContractImplementor, `object`: Any): Any { + return ThreadLocalRandom.current().nextLong() + } + } + + override fun equals(other: Any?): Boolean { + if (null == other) { + return false + } + + if (this === other) { + return true + } + + if (javaClass != ProxyUtils.getUserClass(other)) { + return false + } + + val that: User = other as User + + return this.id == that.id + } + + override fun hashCode(): Int { + var hashCode = 17 + + hashCode += id.hashCode() * 31 + + return hashCode + } +} diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt new file mode 100644 index 0000000..35f39d0 --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt @@ -0,0 +1,11 @@ +package tech.ydb.jpa.simple + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.scheduling.annotation.EnableAsync + +/** + * @author Kirill Kurdyukov + */ +@EnableAsync +@SpringBootApplication +class UserApplication \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/main/resources/application-postgres.properties b/jdbc/spring-data-jpa/src/main/resources/application-postgres.properties new file mode 100644 index 0000000..fbba779 --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/resources/application-postgres.properties @@ -0,0 +1 @@ +spring.datasource.driver-class-name=org.postgresql.Driver \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/main/resources/application-ydb.properties b/jdbc/spring-data-jpa/src/main/resources/application-ydb.properties new file mode 100644 index 0000000..73bd993 --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/resources/application-ydb.properties @@ -0,0 +1,4 @@ +spring.jpa.properties.hibernate.dialect=tech.ydb.hibernate.dialect.YdbDialect + +spring.datasource.driver-class-name=tech.ydb.jdbc.YdbDriver +spring.datasource.url=jdbc:ydb:grpc://localhost:2136/local diff --git a/jdbc/spring-data-jpa/src/main/resources/log4j2.xml b/jdbc/spring-data-jpa/src/main/resources/log4j2.xml new file mode 100644 index 0000000..72f8e14 --- /dev/null +++ b/jdbc/spring-data-jpa/src/main/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt new file mode 100644 index 0000000..2400a3d --- /dev/null +++ b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt @@ -0,0 +1,31 @@ +package tech.ydb.jpa + +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.containers.wait.strategy.Wait + +/** + * @author Kirill Kurdyukov + */ +@ActiveProfiles("test", "postgres") +abstract class PostgresDockerTest { + + companion object { + private val postgresContainer: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:latest") + .withDatabaseName("testdb") + .withUsername("test") + .withPassword("test") + .waitingFor(Wait.forListeningPort()) + + @JvmStatic + @DynamicPropertySource + fun prepareProperties(registry: DynamicPropertyRegistry) { + postgresContainer.start() + registry.add("spring.datasource.url", postgresContainer::getJdbcUrl) + registry.add("spring.datasource.password", postgresContainer::getPassword) + registry.add("spring.datasource.username", postgresContainer::getUsername) + } + } +} \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt new file mode 100644 index 0000000..4b743b4 --- /dev/null +++ b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt @@ -0,0 +1,29 @@ +package tech.ydb.jpa + +import org.junit.jupiter.api.extension.RegisterExtension +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import tech.ydb.test.junit5.YdbHelperExtension + +/** + * @author Kirill Kurdyukov + */ +@ActiveProfiles("test", "ydb") +abstract class YdbDockerTest { + + companion object { + @JvmField + @RegisterExtension + val ydb = YdbHelperExtension() + + @JvmStatic + @DynamicPropertySource + fun propertySource(registry: DynamicPropertyRegistry) { + registry.add("spring.datasource.url") { + "jdbc:ydb:${if (ydb.useTls()) "grpcs://" else "grpc://"}" + + "${ydb.endpoint()}${ydb.database()}${ydb.authToken()?.let { "?token=$it" } ?: ""}" + } + } + } +} \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt new file mode 100644 index 0000000..c8ba42d --- /dev/null +++ b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt @@ -0,0 +1,168 @@ +package tech.ydb.jpa.pagination + +import com.github.javafaker.Faker +import jakarta.persistence.EntityManager +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Window +import org.springframework.data.domain.ScrollPosition +import org.springframework.data.domain.Slice +import org.springframework.data.support.WindowIterator +import org.springframework.transaction.annotation.Transactional +import tech.ydb.jpa.PostgresDockerTest +import java.util.* +import java.util.concurrent.TimeUnit + +/** + * Show different types of paging styles using [Page], [Slice] and [Window]. + * + * @author Christoph Strobl + * @author Mark Paluch + */ +@SpringBootTest +@Transactional +internal class PaginationTests : PostgresDockerTest() /* TODO ESCAPE '\' don't support */{ + + @Autowired + lateinit var books: BookRepository + + @BeforeEach + fun setUp() { + val faker = Faker() + + val authorList = createAuthors(faker) + createBooks(faker, authorList) + } + + /** + * Page through the results using an offset/limit approach where the server skips over the number of results specified + * via [Pageable.getOffset]. The [Page] return type will run an additional count query to + * read the total number of matching rows on each request. + */ + @Test + fun pageThroughResultsWithSkipAndLimit() { + var page: Page + var pageRequest: Pageable = PageRequest.of(0, 2) + + do { + page = books.findByTitleContainsOrderByPublicationDate("the", pageRequest) + assertThat(page.content.size).isGreaterThanOrEqualTo(1).isLessThanOrEqualTo(2) + + pageRequest = page.nextPageable() + } while (page.hasNext()) + } + + /** + * Run through the results using an offset/limit approach where the server skips over the number of results specified + * via [Pageable.getOffset]. No additional count query to read the total number of matching rows is + * issued. Still [Slice] requests, but does not emit, one row more than specified via [Page.getSize] to + * feed [Slice.hasNext] + */ + @Test + fun sliceThroughResultsWithSkipAndLimit() { + var slice: Slice + var pageRequest: Pageable = PageRequest.of(0, 2) + + do { + slice = books.findBooksByTitleContainsOrderByPublicationDate("the", pageRequest) + assertThat(slice.content.size).isGreaterThanOrEqualTo(1).isLessThanOrEqualTo(2) + + pageRequest = slice.nextPageable() + } while (slice.hasNext()) + } + + /** + * Scroll through the results using an offset/limit approach where the server skips over the number of results + * specified via [OffsetScrollPosition.getOffset]. + * + * + * This approach is similar to the [slicing one][.sliceThroughResultsWithSkipAndLimit]. + */ + @Test + fun scrollThroughResultsWithSkipAndLimit() { + var window: Window + var scrollPosition: ScrollPosition = ScrollPosition.offset() + + do { + window = books.findTop2ByTitleContainsOrderByPublicationDate("the", scrollPosition) + assertThat(window.content.size).isGreaterThanOrEqualTo(1).isLessThanOrEqualTo(2) + + scrollPosition = window.positionAt(window.content.size - 1) + } while (window.hasNext()) + } + + /** + * Scroll through the results using an offset/limit approach where the server skips over the number of results + * specified via [OffsetScrollPosition.getOffset] using [WindowIterator]. + * + * + * This approach is similar to the [slicing one][.sliceThroughResultsWithSkipAndLimit]. + */ + @Test + fun scrollThroughResultsUsingWindowIteratorWithSkipAndLimit() { + val iterator: WindowIterator = WindowIterator + .of { scrollPosition: ScrollPosition -> books.findTop2ByTitleContainsOrderByPublicationDate("the-crazy-book-", scrollPosition) } + .startingAt(ScrollPosition.offset()) + + val allBooks: List = iterator.asSequence().toList() + + assertThat(allBooks).hasSize(50) + } + + /** + * Scroll through the results using an index based approach where the [keyset][KeysetScrollPosition.getKeys] + * keeps track of already seen values to resume scrolling by altering the where clause to only return rows after the + * values contained in the keyset. Set logging.level.org.hibernate.SQL=debug to show the modified query in + * the log. + */ + @Test + fun scrollThroughResultsWithKeyset() { + var window: Window + var scrollPosition: ScrollPosition = ScrollPosition.keyset() + do { + window = books.findTop2ByTitleContainsOrderByPublicationDate("the", scrollPosition) + assertThat(window.content.size).isGreaterThanOrEqualTo(1).isLessThanOrEqualTo(2) + + scrollPosition = window.positionAt(window.content.size - 1) + } while (window.hasNext()) + } + + // --> Test Data + @Autowired + lateinit var em: EntityManager + + private fun createAuthors(faker: Faker): List { + val authors = List(11) { id: Int -> + val author = Author() + author.id = "author-%s".format(id) + author.firstName = faker.name().firstName() + author.lastName = faker.name().lastName() + + em.persist(author) + author + } + + return authors + } + + private fun createBooks(faker: Faker, authors: List): List { + val rand = Random() + return List(100) { id: Int -> + val book = Book() + book.id = "book-%03d".format(id) + book.title = (if (id % 2 == 0) "the-crazy-book-" else "") + faker.book().title() + book.isbn10 = UUID.randomUUID().toString().substring(0, 10) + book.publicationDate = faker.date().past(5000, TimeUnit.DAYS) + book.author = authors[rand.nextInt(authors.size)] + + em.persist(book) + book + } + } +} diff --git a/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt new file mode 100644 index 0000000..0821c50 --- /dev/null +++ b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt @@ -0,0 +1,236 @@ +package tech.ydb.jpa.simple + +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.dao.InvalidDataAccessApiUsageException +import org.springframework.data.domain.Limit +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.annotation.Transactional +import tech.ydb.jpa.YdbDockerTest +import java.util.stream.Collectors + +/** + * @author Kirill Kurdyukov + */ +@SpringBootTest +@Transactional +class SimpleRepositoryTest : YdbDockerTest() { + + @Autowired + lateinit var repository: SimpleUserRepository + + lateinit var user: User + + @BeforeEach + fun setUp() { + user = User().apply { + username = "foobar" + firstname = "firstname" + lastname = "lastname" + } + } + + @Test + fun findSavedUserById() { + user = repository.save(user) + + assertThat(repository.findById(user.id)).hasValue(user) + } + + @Test + fun findSavedUserByLastname() { + user = repository.save(user) + + assertThat(repository.findByLastname("lastname")).contains(user) + } + + @Test + fun findLimitedNumberOfUsersViaDerivedQuery() { + (0..10).forEach { _ -> repository.save(User().apply { lastname = "lastname" }) } + + assertThat(repository.findByLastname("lastname", Limit.of(5))).hasSize(5) + } + + @Test + fun findLimitedNumberOfUsersViaAnnotatedQuery() { + (0..10).forEach { _ -> repository.save(User().apply { firstname = "firstname" }) } + + assertThat(repository.findByFirstname("firstname", Limit.of(5))).hasSize(5) + } + + @Test + fun findByFirstnameOrLastname() { + user = repository.save(user) + + assertThat(repository.findByFirstnameOrLastname("lastname")).contains(user) + } + + @Test + fun useOptionalAsReturnAndParameterType() { + assertNull(repository.findByUsername("foobar")) + + repository.save(user) + + assertNotNull(repository.findByUsername("foobar")) + } + + @Test + fun removeByLastname() { + // create a 2nd user with the same lastname as user + + val user2 = User().apply { lastname = user.lastname } + + // create a 3rd user as control group + val user3 = User().apply { lastname = "no-positive-match" } + + repository.saveAll(listOf(user, user2, user3)) + + assertThat(repository.removeByLastname(user.lastname)).isEqualTo(2L) + assertThat(repository.existsById(user3.id)).isTrue() + } + + @Test + fun useSliceToLoadContent() { + val totalNumberUsers = 11 + val source: MutableList = ArrayList(totalNumberUsers) + + for (i in 1..totalNumberUsers) { + val user = User().apply { + lastname = user.lastname + username = "${user.lastname}-${String.format("%03d", i)}" + } + + source.add(user) + } + + repository.saveAll(source) + + val users = repository.findByLastnameOrderByUsernameAsc(user.lastname, PageRequest.of(1, 5)) + + assertThat(users).containsAll(source.subList(5, 10)) + } + + @Test + fun findFirst2ByOrderByLastnameAsc() { + val user0 = User().apply { lastname = "lastname-0" } + + val user1 = User().apply { lastname = "lastname-1" } + + val user2 = User().apply { lastname = "lastname-2" } + + // we deliberately save the items in reverse + repository.saveAll(listOf(user2, user1, user0)) + + val result = repository.findFirst2ByOrderByLastnameAsc() + + assertThat(result).containsExactly(user0, user1) + } + + @Test + fun findTop2ByWithSort() { + val user0 = User().apply { lastname = "lastname-0" } + + val user1 = User().apply { lastname = "lastname-1" } + + val user2 = User().apply { lastname = "lastname-2" } + + // we deliberately save the items in reverse + repository.saveAll(listOf(user2, user1, user0)) + + val resultAsc = repository.findTop2By(Sort.by(Sort.Direction.ASC, "lastname")) + + assertThat(resultAsc).containsExactly(user0, user1) + + val resultDesc = repository.findTop2By(Sort.by(Sort.Direction.DESC, "lastname")) + + assertThat(resultDesc).containsExactly(user2, user1) + } + + @Test + fun findByFirstnameOrLastnameUsingSpEL() { + val first = User().apply { lastname = "lastname" } + + val second = User().apply { firstname = "firstname" } + + val third = User() + + repository.saveAll(listOf(first, second, third)) + + val reference = User().apply { firstname = "firstname"; lastname = "lastname" } + + val users = repository.findByFirstnameOrLastname(reference) + + assertThat(users).contains(first) + assertThat(users).contains(second) + assertThat(users).hasSize(2) + } + + /** + * Streaming data from the store by using a repository method that returns a [Stream]. Note, that since the + * resulting [Stream] contains state it needs to be closed explicitly after use! + */ + @Test + fun useJava8StreamsWithCustomQuery() { + val user1 = repository.save(User().apply { firstname = "Customer1"; lastname = "Foo" }) + val user2 = repository.save(User().apply { firstname = "Customer2"; lastname = "Bar" }) + + repository.streamAllCustomers().use { stream -> + assertThat(stream.collect(Collectors.toList())).contains(user1, user2) + } + } + + /** + * Streaming data from the store by using a repository method that returns a [Stream] with a derived query. + * Note, that since the resulting [Stream] contains state it needs to be closed explicitly after use! + */ + @Test + fun useJava8StreamsWithDerivedQuery() { + val user1 = repository.save(User().apply { firstname = "Customer1"; lastname = "Foo" }) + val user2 = repository.save(User().apply { firstname = "Customer2"; lastname = "Bar" }) + + repository.findAllByLastnameIsNotNull().use { stream -> + assertThat(stream.collect(Collectors.toList())).contains(user1, user2) + } + } + + /** + * Query methods using streaming need to be used inside a surrounding transaction to keep the connection open while + * the stream is consumed. We simulate that not being the case by actively disabling the transaction here. + */ + @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) + fun rejectsStreamExecutionIfNoSurroundingTransactionActive() { + Assertions.assertThrows(InvalidDataAccessApiUsageException::class.java) { + repository.findAllByLastnameIsNotNull() + } + } + + /** + * Here we demonstrate the usage of [CompletableFuture] as a result wrapper for asynchronous repository query + * methods. Note, that we need to disable the surrounding transaction to be able to asynchronously read the written + * data from another thread within the same test method. + */ + @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) + fun supportsCompletableFuturesAsReturnTypeWrapper() { + repository.save(User().apply { firstname = "Customer1"; lastname = "Foo" }) + repository.save(User().apply { firstname = "Customer2"; lastname = "Bar" }) + + runBlocking { + val users = repository.readAllBy().await() + assertThat(users).hasSize(2) + } + + repository.deleteAll() + } +} \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/test/resources/application-test.properties b/jdbc/spring-data-jpa/src/test/resources/application-test.properties new file mode 100644 index 0000000..8f8996a --- /dev/null +++ b/jdbc/spring-data-jpa/src/test/resources/application-test.properties @@ -0,0 +1,5 @@ +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.highlight_sql=true + +spring.jpa.properties.hibernate.hbm2ddl.auto=create \ No newline at end of file From 02e645104ed118c9053e34ceea591bc8e5f19cf9 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 22 Jan 2024 16:00:17 +0300 Subject: [PATCH 2/8] Spring Data JPA 2.5.7 Example & Hibernate 5 --- jdbc/pom.xml | 1 + jdbc/spring-data-jpa-v5/pom.xml | 150 +++++++++++++ .../kotlin/tech/ydb/jpa/pagination/Author.kt | 17 ++ .../kotlin/tech/ydb/jpa/pagination/Book.kt | 19 ++ .../tech/ydb/jpa/pagination/BookRepository.kt | 39 ++++ .../ydb/jpa/pagination/PagingApplication.kt | 6 + .../ydb/jpa/simple/SimpleUserRepository.kt | 124 +++++++++++ .../main/kotlin/tech/ydb/jpa/simple/User.kt | 45 ++++ .../tech/ydb/jpa/simple/UserApplication.kt | 12 ++ .../resources/application-postgres.properties | 1 + .../main/resources/application-ydb.properties | 4 + .../src/main/resources/log4j2.xml | 14 ++ .../kotlin/tech/ydb/jpa/PostgresDockerTest.kt | 33 +++ .../test/kotlin/tech/ydb/jpa/YdbDockerTest.kt | 29 +++ .../ydb/jpa/pagination/PaginationTests.kt | 110 ++++++++++ .../ydb/jpa/simple/SimpleRepositoryTest.kt | 200 ++++++++++++++++++ .../resources/application-test.properties | 5 + .../tech/ydb/jpa/pagination/BookRepository.kt | 10 + .../kotlin/tech/ydb/jpa/PostgresDockerTest.kt | 2 + 19 files changed, 821 insertions(+) create mode 100644 jdbc/spring-data-jpa-v5/pom.xml create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/User.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt create mode 100644 jdbc/spring-data-jpa-v5/src/main/resources/application-postgres.properties create mode 100644 jdbc/spring-data-jpa-v5/src/main/resources/application-ydb.properties create mode 100644 jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml create mode 100644 jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt create mode 100644 jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt create mode 100644 jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt create mode 100644 jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt create mode 100644 jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties diff --git a/jdbc/pom.xml b/jdbc/pom.xml index becc23a..93a3c84 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -21,6 +21,7 @@ basic-example + spring-data-jpa-v5 diff --git a/jdbc/spring-data-jpa-v5/pom.xml b/jdbc/spring-data-jpa-v5/pom.xml new file mode 100644 index 0000000..56f3efd --- /dev/null +++ b/jdbc/spring-data-jpa-v5/pom.xml @@ -0,0 +1,150 @@ + + 4.0.0 + + tech.ydb.jdbc.examples + ydb-jdbc-examples + 1.1.0-SNAPSHOT + + + spring-data-jpa-v5 + Spring Data JPA Example Hibernate 5 + Basic example for SpringBoot3 and Hibernate 6 + + 8 + 1.9.22 + 0.9.1-SNAPSHOT + 2.0.5 + 2.5.7 + + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring.boot.version} + + + org.springframework.data + spring-data-commons + ${spring.boot.version} + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin.version} + + + tech.ydb.dialects + hibernate-ydb-dialect-v5 + ${hibernate.ydb.dialect.version} + + + tech.ydb.jdbc + ydb-jdbc-driver + ${ydb.jdbc.driver.version} + + + com.github.javafaker + javafaker + 1.0.2 + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + 1.7.3 + + + org.postgresql + postgresql + 42.7.1 + + + org.testcontainers + postgresql + 1.19.1 + test + + + tech.ydb.test + ydb-junit5-support + test + + + org.junit.jupiter + junit-jupiter-api + + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + + test-compile + + + + + + -Xjsr305=strict + + + spring + jpa + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-maven-noarg + ${kotlin.version} + + + + + + \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt new file mode 100644 index 0000000..8690d96 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Author.kt @@ -0,0 +1,17 @@ +package tech.ydb.jpa.pagination + +import javax.persistence.Entity +import javax.persistence.Id +import javax.persistence.Table + +@Entity +@Table(name = "authors") +class Author { + + @Id + lateinit var id: String + + lateinit var firstName: String + + lateinit var lastName: String +} diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt new file mode 100644 index 0000000..54af00f --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/Book.kt @@ -0,0 +1,19 @@ +package tech.ydb.jpa.pagination + +import java.util.* +import javax.persistence.* + +@Entity +@Table(name = "books") +class Book { + + @Id + lateinit var id: String + + lateinit var title: String + lateinit var isbn10: String + lateinit var publicationDate: Date + + @ManyToOne + lateinit var author: Author +} diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt new file mode 100644 index 0000000..67be22d --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt @@ -0,0 +1,39 @@ +package tech.ydb.jpa.pagination + +import org.springframework.data.domain.* +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.CrudRepository +import org.springframework.data.repository.query.Param + +interface BookRepository : CrudRepository { + + /** + * Uses an offset based pagination that first sorts the entries by their [ publication_date][Book.getPublicationDate] + * and then limits the result by dropping the number of rows specified in the + * [offset][Pageable.getOffset] clause. To retrieve [Page.getTotalElements] an additional count query + * is executed. + * + * @param title + * @param pageable + */ + @Query( + "SELECT * FROM books WHERE books.title LIKE %:title% ORDER BY books.publication_date", + countQuery = "SELECT count(*) FROM books WHERE books.title LIKE %:title%", + nativeQuery = true + ) + fun findByTitleContainsOrderByPublicationDate(@Param("title") title: String, pageable: Pageable): Page + + /** + * Uses an offset based slicing that first sorts the entries by their [ publication_date][Book.getPublicationDate] + * and then limits the result by dropping the number of rows specified in the + * [offset][Pageable.getOffset] clause. + * + * @param title + * @param pageable + */ + @Query( + "SELECT * FROM books WHERE books.title LIKE %:title% ORDER BY books.publication_date", + nativeQuery = true + ) + fun findBooksByTitleContainsOrderByPublicationDate(title: String, pageable: Pageable): Slice +} diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt new file mode 100644 index 0000000..89f961d --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/pagination/PagingApplication.kt @@ -0,0 +1,6 @@ +package tech.ydb.jpa.pagination + +import org.springframework.boot.autoconfigure.SpringBootApplication + +@SpringBootApplication +class PagingApplication diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt new file mode 100644 index 0000000..a2e9ff4 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/SimpleUserRepository.kt @@ -0,0 +1,124 @@ +package tech.ydb.jpa.simple; + +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice +import org.springframework.data.jpa.repository.Query +import org.springframework.data.repository.CrudRepository +import org.springframework.scheduling.annotation.Async +import java.util.concurrent.CompletableFuture +import java.util.stream.Stream + +/** + * Simple repository interface for {@link User} instances. The interface is used to declare the so-called query methods, + * i.e. methods to retrieve single entities or collections of them. + */ +interface SimpleUserRepository : CrudRepository { + + /** + * Find the user with the given username. This method will be translated into a query using the + * {@link jakarta.persistence.NamedQuery} annotation at the {@link User} class. + * + * @param username + */ + fun findByTheUsersName(username: String): User + + /** + * Uses {@link Optional} as return and parameter type. + * + * @param username + */ + fun findByUsername(username: String?): User? + + /** + * Find all users with the given lastname. This method will be translated into a query by constructing it directly + * from the method name as there is no other query declared. + * + * @param lastname + */ + fun findByLastname(lastname: String): List + + + /** + * Returns all users with the given firstname. This method will be translated into a query using the one declared in + * the {@link Query} annotation declared one. + * + * @param firstname + */ + @Query("select u from User u where u.firstname = :firstname") + fun findByFirstname(firstname: String): List + + + /** + * Returns all users with the given name as first- or lastname. This makes the query to method relation much more + * refactoring-safe as the order of the method parameters is completely irrelevant. + * + * @param name + */ + @Query("select u from User u where u.firstname = :name or u.lastname = :name") + fun findByFirstnameOrLastname(name: String): List + + /** + * Returns the total number of entries deleted as their lastnames match the given one. + * + * @param lastname + * @return + */ + fun removeByLastname(lastname: String): Long + + /** + * Returns a {@link Slice} counting a maximum number of {@link Pageable#getPageSize()} users matching given criteria + * starting at {@link Pageable#getOffset()} without prior count of the total number of elements available. + * + * @param lastname + * @param page + */ + @Query( + "SELECT * FROM users WHERE users.lastname = :lastname ORDER BY users.username", + nativeQuery = true + ) + fun findByLastnameOrderByUsernameAsc(lastname: String, page: Pageable): Slice + + /** + * Return the first 2 users ordered by their lastname asc. + * + *
+	 * Example for findFirstK / findTopK functionality.
+	 * 
+ */ + @Query("SELECT * FROM users ORDER BY lastname LIMIT 2", nativeQuery = true) + fun findFirst2ByOrderByLastnameAsc(): List + + + /** + * Return all the users with the given firstname or lastname. Makes use of SpEL (Spring Expression Language). + * + * @param user + */ + @Query("select u from User u where u.firstname = :#{#user.firstname} or u.lastname = :#{#user.lastname}") + fun findByFirstnameOrLastname(user: User): Iterable + + /** + * Sample default method. + * + * @param user + */ + fun findByLastname(user: User): List { + return findByLastname(user.lastname); + } + + /** + * Sample method to demonstrate support for {@link Stream} as a return type with a custom query. The query is executed + * in a streaming fashion which means that the method returns as soon as the first results are ready. + */ + @Query("select u from User u") + fun streamAllCustomers(): Stream + + /** + * Sample method to demonstrate support for {@link Stream} as a return type with a derived query. The query is + * executed in a streaming fashion which means that the method returns as soon as the first results are ready. + */ + fun findAllByLastnameIsNotNull(): Stream + + @Async + fun readAllBy(): CompletableFuture> +} diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/User.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/User.kt new file mode 100644 index 0000000..676b7b5 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/User.kt @@ -0,0 +1,45 @@ +package tech.ydb.jpa.simple + +import javax.persistence.* +import org.springframework.data.util.ProxyUtils + +@Entity +@Table(name = "Users", indexes = [Index(name = "username_index", columnList = "username")]) +@NamedQuery(name = "User.findByTheUsersName", query = "from User u where u.username = ?1") +class User { + + @Id + var id: Long = 0 + + lateinit var username: String + + lateinit var firstname: String + + lateinit var lastname: String + + override fun equals(other: Any?): Boolean { + if (null == other) { + return false + } + + if (this === other) { + return true + } + + if (javaClass != ProxyUtils.getUserClass(other)) { + return false + } + + val that: User = other as User + + return this.id == that.id + } + + override fun hashCode(): Int { + var hashCode = 17 + + hashCode += id.hashCode() * 31 + + return hashCode + } +} diff --git a/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt new file mode 100644 index 0000000..5b745c4 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/kotlin/tech/ydb/jpa/simple/UserApplication.kt @@ -0,0 +1,12 @@ +package tech.ydb.jpa.simple + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.domain.EntityScan +import org.springframework.scheduling.annotation.EnableAsync + +/** + * @author Kirill Kurdyukov + */ +@EnableAsync +@SpringBootApplication +class UserApplication \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/main/resources/application-postgres.properties b/jdbc/spring-data-jpa-v5/src/main/resources/application-postgres.properties new file mode 100644 index 0000000..fbba779 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/resources/application-postgres.properties @@ -0,0 +1 @@ +spring.datasource.driver-class-name=org.postgresql.Driver \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/main/resources/application-ydb.properties b/jdbc/spring-data-jpa-v5/src/main/resources/application-ydb.properties new file mode 100644 index 0000000..73bd993 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/resources/application-ydb.properties @@ -0,0 +1,4 @@ +spring.jpa.properties.hibernate.dialect=tech.ydb.hibernate.dialect.YdbDialect + +spring.datasource.driver-class-name=tech.ydb.jdbc.YdbDriver +spring.datasource.url=jdbc:ydb:grpc://localhost:2136/local diff --git a/jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml b/jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml new file mode 100644 index 0000000..72f8e14 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt new file mode 100644 index 0000000..fa7bb09 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt @@ -0,0 +1,33 @@ +package tech.ydb.jpa + +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import org.testcontainers.containers.PostgreSQLContainer +import org.testcontainers.containers.wait.strategy.Wait + +/** + * Debug mode + * + * @author Kirill Kurdyukov + */ +@ActiveProfiles("test", "postgres") +abstract class PostgresDockerTest { + + companion object { + private val postgresContainer: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:latest") + .withDatabaseName("testdb") + .withUsername("test") + .withPassword("test") + .waitingFor(Wait.forListeningPort()) + + @JvmStatic + @DynamicPropertySource + fun prepareProperties(registry: DynamicPropertyRegistry) { + postgresContainer.start() + registry.add("spring.datasource.url", postgresContainer::getJdbcUrl) + registry.add("spring.datasource.password", postgresContainer::getPassword) + registry.add("spring.datasource.username", postgresContainer::getUsername) + } + } +} \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt new file mode 100644 index 0000000..4b743b4 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/YdbDockerTest.kt @@ -0,0 +1,29 @@ +package tech.ydb.jpa + +import org.junit.jupiter.api.extension.RegisterExtension +import org.springframework.test.context.ActiveProfiles +import org.springframework.test.context.DynamicPropertyRegistry +import org.springframework.test.context.DynamicPropertySource +import tech.ydb.test.junit5.YdbHelperExtension + +/** + * @author Kirill Kurdyukov + */ +@ActiveProfiles("test", "ydb") +abstract class YdbDockerTest { + + companion object { + @JvmField + @RegisterExtension + val ydb = YdbHelperExtension() + + @JvmStatic + @DynamicPropertySource + fun propertySource(registry: DynamicPropertyRegistry) { + registry.add("spring.datasource.url") { + "jdbc:ydb:${if (ydb.useTls()) "grpcs://" else "grpc://"}" + + "${ydb.endpoint()}${ydb.database()}${ydb.authToken()?.let { "?token=$it" } ?: ""}" + } + } + } +} \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt new file mode 100644 index 0000000..ffb803f --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/pagination/PaginationTests.kt @@ -0,0 +1,110 @@ +package tech.ydb.jpa.pagination + +import com.github.javafaker.Faker +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.domain.Page +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.Slice +import org.springframework.transaction.annotation.Transactional +import tech.ydb.jpa.PostgresDockerTest +import tech.ydb.jpa.YdbDockerTest +import java.util.* +import java.util.concurrent.TimeUnit +import javax.persistence.EntityManager + +/** + * Show different types of paging styles using [Page], [Slice] and [Window]. + * + * @author Christoph Strobl + * @author Mark Paluch + */ +@SpringBootTest +@Transactional +internal class PaginationTests : YdbDockerTest() { + + @Autowired + lateinit var books: BookRepository + + @BeforeEach + fun setUp() { + val faker = Faker() + + val authorList = createAuthors(faker) + createBooks(faker, authorList) + } + + /** + * Page through the results using an offset/limit approach where the server skips over the number of results specified + * via [Pageable.getOffset]. The [Page] return type will run an additional count query to + * read the total number of matching rows on each request. + */ + @Test + fun pageThroughResultsWithSkipAndLimit() { + var page: Page + var pageRequest: Pageable = PageRequest.of(0, 2) + + do { + page = books.findByTitleContainsOrderByPublicationDate("the", pageRequest) + assertThat(page.content.size).isGreaterThanOrEqualTo(1).isLessThanOrEqualTo(2) + + pageRequest = page.nextPageable() + } while (page.hasNext()) + } + + /** + * Run through the results using an offset/limit approach where the server skips over the number of results specified + * via [Pageable.getOffset]. No additional count query to read the total number of matching rows is + * issued. Still [Slice] requests, but does not emit, one row more than specified via [Page.getSize] to + * feed [Slice.hasNext] + */ + @Test + fun sliceThroughResultsWithSkipAndLimit() { + var slice: Slice + var pageRequest: Pageable = PageRequest.of(0, 2) + + do { + slice = books.findBooksByTitleContainsOrderByPublicationDate("the", pageRequest) + assertThat(slice.content.size).isGreaterThanOrEqualTo(1).isLessThanOrEqualTo(2) + + pageRequest = slice.nextPageable() + } while (slice.hasNext()) + } + + // --> Test Data + @Autowired + lateinit var em: EntityManager + + private fun createAuthors(faker: Faker): List { + val authors = List(11) { id: Int -> + val author = Author() + author.id = "author-%s".format(id) + author.firstName = faker.name().firstName() + author.lastName = faker.name().lastName() + + em.persist(author) + author + } + + return authors + } + + private fun createBooks(faker: Faker, authors: List): List { + val rand = Random() + return List(100) { id: Int -> + val book = Book() + book.id = "book-%03d".format(id) + book.title = (if (id % 2 == 0) "the-crazy-book-" else "") + faker.book().title() + book.isbn10 = UUID.randomUUID().toString().substring(0, 10) + book.publicationDate = faker.date().past(5000, TimeUnit.DAYS) + book.author = authors[rand.nextInt(authors.size)] + + em.persist(book) + book + } + } +} diff --git a/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt new file mode 100644 index 0000000..ccd0eee --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/test/kotlin/tech/ydb/jpa/simple/SimpleRepositoryTest.kt @@ -0,0 +1,200 @@ +package tech.ydb.jpa.simple + +import kotlinx.coroutines.future.await +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.dao.InvalidDataAccessApiUsageException +import org.springframework.data.domain.PageRequest +import org.springframework.transaction.annotation.Propagation +import org.springframework.transaction.annotation.Transactional +import tech.ydb.jpa.YdbDockerTest +import java.util.stream.Collectors + +/** + * @author Kirill Kurdyukov + */ +@SpringBootTest +@Transactional +class SimpleRepositoryTest : YdbDockerTest() { + + @Autowired + lateinit var repository: SimpleUserRepository + + lateinit var user: User + + @BeforeEach + fun setUp() { + user = User().apply { + username = "foobar" + firstname = "firstname" + lastname = "lastname" + } + } + + @Test + fun findSavedUserById() { + user = repository.save(user) + + assertThat(repository.findById(user.id)).hasValue(user) + } + + @Test + fun findSavedUserByLastname() { + user = repository.save(user) + + assertThat(repository.findByLastname("lastname")).contains(user) + } + + @Test + fun findByFirstnameOrLastname() { + user = repository.save(user) + + assertThat(repository.findByFirstnameOrLastname("lastname")).contains(user) + } + + @Test + fun useOptionalAsReturnAndParameterType() { + assertNull(repository.findByUsername("foobar")) + + repository.save(user) + + assertNotNull(repository.findByUsername("foobar")) + } + + @Test + fun removeByLastname() { + // create a 2nd user with the same lastname as user + + val user2 = User().apply { id = 1; lastname = user.lastname } + + // create a 3rd user as control group + val user3 = User().apply { id = 2; lastname = "no-positive-match" } + + repository.saveAll(listOf(user, user2, user3)) + + assertThat(repository.removeByLastname(user.lastname)).isEqualTo(2L) + assertThat(repository.existsById(user3.id)).isTrue() + } + + @Test + fun useSliceToLoadContent() { + val totalNumberUsers = 11 + val source: MutableList = ArrayList(totalNumberUsers) + + for (i in 1..totalNumberUsers) { + val user = User().apply { + id = i.toLong() + lastname = user.lastname + username = "${user.lastname}-${String.format("%03d", i)}" + } + + source.add(user) + } + + repository.saveAll(source) + + val users = repository.findByLastnameOrderByUsernameAsc(user.lastname, PageRequest.of(1, 5)) + + assertThat(users).containsAll(source.subList(5, 10)) + } + + @Test + fun findFirst2ByOrderByLastnameAsc() { + val user0 = User().apply { id = 1; lastname = "lastname-0" } + + val user1 = User().apply { id = 2; lastname = "lastname-1" } + + val user2 = User().apply { id = 3; lastname = "lastname-2" } + + // we deliberately save the items in reverse + repository.saveAll(listOf(user2, user1, user0)) + + val result = repository.findFirst2ByOrderByLastnameAsc() + + assertThat(result).containsExactly(user0, user1) + } + + @Test + fun findByFirstnameOrLastnameUsingSpEL() { + val first = User().apply { id = 1; lastname = "lastname" } + + val second = User().apply { id = 2; firstname = "firstname" } + + val third = User() + + repository.saveAll(listOf(first, second, third)) + + val reference = User().apply { id = 3; firstname = "firstname"; lastname = "lastname" } + + val users = repository.findByFirstnameOrLastname(reference) + + assertThat(users).contains(first) + assertThat(users).contains(second) + assertThat(users).hasSize(2) + } + + /** + * Streaming data from the store by using a repository method that returns a [Stream]. Note, that since the + * resulting [Stream] contains state it needs to be closed explicitly after use! + */ + @Test + fun useJava8StreamsWithCustomQuery() { + val user1 = repository.save(User().apply { id = 1; firstname = "Customer1"; lastname = "Foo" }) + val user2 = repository.save(User().apply { id = 2; firstname = "Customer2"; lastname = "Bar" }) + + repository.streamAllCustomers().use { stream -> + assertThat(stream.collect(Collectors.toList())).contains(user1, user2) + } + } + + /** + * Streaming data from the store by using a repository method that returns a [Stream] with a derived query. + * Note, that since the resulting [Stream] contains state it needs to be closed explicitly after use! + */ + @Test + fun useJava8StreamsWithDerivedQuery() { + val user1 = repository.save(User().apply { id = 1; firstname = "Customer1"; lastname = "Foo" }) + val user2 = repository.save(User().apply { id = 2; firstname = "Customer2"; lastname = "Bar" }) + + repository.findAllByLastnameIsNotNull().use { stream -> + assertThat(stream.collect(Collectors.toList())).contains(user1, user2) + } + } + + /** + * Query methods using streaming need to be used inside a surrounding transaction to keep the connection open while + * the stream is consumed. We simulate that not being the case by actively disabling the transaction here. + */ + @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) + fun rejectsStreamExecutionIfNoSurroundingTransactionActive() { + Assertions.assertThrows(InvalidDataAccessApiUsageException::class.java) { + repository.findAllByLastnameIsNotNull() + } + } + + /** + * Here we demonstrate the usage of [CompletableFuture] as a result wrapper for asynchronous repository query + * methods. Note, that we need to disable the surrounding transaction to be able to asynchronously read the written + * data from another thread within the same test method. + */ + @Test + @Transactional(propagation = Propagation.NOT_SUPPORTED) + fun supportsCompletableFuturesAsReturnTypeWrapper() { + repository.save(User().apply { id = 1; firstname = "Customer1"; lastname = "Foo" }) + repository.save(User().apply { id = 2; firstname = "Customer2"; lastname = "Bar" }) + + runBlocking { + val users = repository.readAllBy().await() + assertThat(users).hasSize(2) + } + + repository.deleteAll() + } +} \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties b/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties new file mode 100644 index 0000000..8f8996a --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties @@ -0,0 +1,5 @@ +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.highlight_sql=true + +spring.jpa.properties.hibernate.hbm2ddl.auto=create \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt index d55c29d..1505d66 100644 --- a/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt +++ b/jdbc/spring-data-jpa/src/main/kotlin/tech/ydb/jpa/pagination/BookRepository.kt @@ -1,6 +1,7 @@ package tech.ydb.jpa.pagination import org.springframework.data.domain.* +import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.ListCrudRepository import org.springframework.data.repository.query.Param @@ -15,6 +16,11 @@ interface BookRepository : ListCrudRepository { * @param title * @param pageable */ + @Query( + value = "SELECT * FROM books WHERE title LIKE %:title% ORDER BY books.publication_date", + countQuery = "SELECT count(*) FROM books WHERE title LIKE %:title% ", + nativeQuery = true + ) fun findByTitleContainsOrderByPublicationDate(@Param("title") title: String, pageable: Pageable): Page /** @@ -24,6 +30,10 @@ interface BookRepository : ListCrudRepository { * @param title * @param pageable */ + @Query( + value = "SELECT * FROM books WHERE title LIKE %:title% ORDER BY books.publication_date", + nativeQuery = true + ) fun findBooksByTitleContainsOrderByPublicationDate(title: String, pageable: Pageable): Slice /** diff --git a/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt index 2400a3d..fa7bb09 100644 --- a/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt +++ b/jdbc/spring-data-jpa/src/test/kotlin/tech/ydb/jpa/PostgresDockerTest.kt @@ -7,6 +7,8 @@ import org.testcontainers.containers.PostgreSQLContainer import org.testcontainers.containers.wait.strategy.Wait /** + * Debug mode + * * @author Kirill Kurdyukov */ @ActiveProfiles("test", "postgres") From d065cb816d47611c8f10e78f20e3fcd10d224b00 Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 22 Jan 2024 16:03:29 +0300 Subject: [PATCH 3/8] Make 0.9.1 dialect version --- jdbc/spring-data-jpa-v5/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jdbc/spring-data-jpa-v5/pom.xml b/jdbc/spring-data-jpa-v5/pom.xml index 56f3efd..7f0d312 100644 --- a/jdbc/spring-data-jpa-v5/pom.xml +++ b/jdbc/spring-data-jpa-v5/pom.xml @@ -12,7 +12,7 @@ 8 1.9.22 - 0.9.1-SNAPSHOT + 0.9.1 2.0.5 2.5.7 From a05814e4e6051deccadc69ae6d38679e61b3b51a Mon Sep 17 00:00:00 2001 From: KirillKurdyukov Date: Mon, 22 Jan 2024 16:14:28 +0300 Subject: [PATCH 4/8] Delete maven.compiler.release.version & ydb.jdbc.driver.version --- jdbc/spring-data-jpa-v5/pom.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jdbc/spring-data-jpa-v5/pom.xml b/jdbc/spring-data-jpa-v5/pom.xml index 7f0d312..86c1e36 100644 --- a/jdbc/spring-data-jpa-v5/pom.xml +++ b/jdbc/spring-data-jpa-v5/pom.xml @@ -10,10 +10,8 @@ Spring Data JPA Example Hibernate 5 Basic example for SpringBoot3 and Hibernate 6 - 8 1.9.22 0.9.1 - 2.0.5 2.5.7 @@ -44,8 +42,7 @@ tech.ydb.jdbc - ydb-jdbc-driver - ${ydb.jdbc.driver.version} + ydb-jdbc-driver-shaded com.github.javafaker From 5d0615980eff2c10ff7c0c890236d21a6a8b0a19 Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Wed, 31 Jan 2024 13:32:43 +0000 Subject: [PATCH 5/8] Update dependencies and plugins versions --- jdbc/pom.xml | 4 ++-- pom.xml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/jdbc/pom.xml b/jdbc/pom.xml index 93a3c84..9984f39 100644 --- a/jdbc/pom.xml +++ b/jdbc/pom.xml @@ -15,8 +15,8 @@ pom - 2.0.2 - 1.7.25 + 2.0.6 + 1.7.36 diff --git a/pom.xml b/pom.xml index 1dfcae2..1f1188a 100644 --- a/pom.xml +++ b/pom.xml @@ -15,10 +15,10 @@ UTF-8 - 2.20.0 + 2.22.1 1.82 - 2.1.7 + 2.1.11 @@ -66,7 +66,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.12.1 1.8 1.8 @@ -81,7 +81,7 @@ org.apache.maven.plugins maven-surefire-plugin - 3.1.0 + 3.2.5 true @@ -91,7 +91,7 @@ org.apache.maven.plugins maven-dependency-plugin - 3.5.0 + 3.6.1 copy-dependencies From 478ee548313929a8ddc4c11add111eb3ed6aee96 Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Fri, 2 Feb 2024 11:07:05 +0000 Subject: [PATCH 6/8] Fixed logging settings for Spring examples --- .../src/main/resources/log4j2.xml | 14 ------------- .../resources/application-test.properties | 2 +- .../src/test/resources/logback.xml | 19 +++++++++++++++++ jdbc/spring-data-jpa/pom.xml | 21 +++++++------------ .../src/main/resources/log4j2.xml | 14 ------------- .../resources/application-test.properties | 2 +- .../src/test/resources/logback.xml | 19 +++++++++++++++++ 7 files changed, 48 insertions(+), 43 deletions(-) delete mode 100644 jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml create mode 100644 jdbc/spring-data-jpa-v5/src/test/resources/logback.xml delete mode 100644 jdbc/spring-data-jpa/src/main/resources/log4j2.xml create mode 100644 jdbc/spring-data-jpa/src/test/resources/logback.xml diff --git a/jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml b/jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml deleted file mode 100644 index 72f8e14..0000000 --- a/jdbc/spring-data-jpa-v5/src/main/resources/log4j2.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - %d %5p %40.40c:%4L - %m%n - - - - - - - - \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties b/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties index 8f8996a..4abc7c5 100644 --- a/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties +++ b/jdbc/spring-data-jpa-v5/src/test/resources/application-test.properties @@ -1,5 +1,5 @@ spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.format_sql=false spring.jpa.properties.hibernate.highlight_sql=true spring.jpa.properties.hibernate.hbm2ddl.auto=create \ No newline at end of file diff --git a/jdbc/spring-data-jpa-v5/src/test/resources/logback.xml b/jdbc/spring-data-jpa-v5/src/test/resources/logback.xml new file mode 100644 index 0000000..77232d4 --- /dev/null +++ b/jdbc/spring-data-jpa-v5/src/test/resources/logback.xml @@ -0,0 +1,19 @@ + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jdbc/spring-data-jpa/pom.xml b/jdbc/spring-data-jpa/pom.xml index 84480c8..30294b5 100644 --- a/jdbc/spring-data-jpa/pom.xml +++ b/jdbc/spring-data-jpa/pom.xml @@ -13,7 +13,6 @@ 17 1.9.22 0.9.1 - 2.0.5 3.2.1 @@ -44,18 +43,13 @@ tech.ydb.jdbc - ydb-jdbc-driver - ${ydb.jdbc.driver.version} + ydb-jdbc-driver-shaded com.github.javafaker javafaker 1.0.2 - - org.apache.logging.log4j - log4j-slf4j-impl - org.jetbrains.kotlinx kotlinx-coroutines-core @@ -72,6 +66,13 @@ 1.19.1 test + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + tech.ydb.test ydb-junit5-support @@ -83,12 +84,6 @@ - - org.springframework.boot - spring-boot-starter-test - ${spring.boot.version} - test - ${project.basedir}/src/main/kotlin diff --git a/jdbc/spring-data-jpa/src/main/resources/log4j2.xml b/jdbc/spring-data-jpa/src/main/resources/log4j2.xml deleted file mode 100644 index 72f8e14..0000000 --- a/jdbc/spring-data-jpa/src/main/resources/log4j2.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - %d %5p %40.40c:%4L - %m%n - - - - - - - - \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/test/resources/application-test.properties b/jdbc/spring-data-jpa/src/test/resources/application-test.properties index 8f8996a..4abc7c5 100644 --- a/jdbc/spring-data-jpa/src/test/resources/application-test.properties +++ b/jdbc/spring-data-jpa/src/test/resources/application-test.properties @@ -1,5 +1,5 @@ spring.jpa.show-sql=true -spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.format_sql=false spring.jpa.properties.hibernate.highlight_sql=true spring.jpa.properties.hibernate.hbm2ddl.auto=create \ No newline at end of file diff --git a/jdbc/spring-data-jpa/src/test/resources/logback.xml b/jdbc/spring-data-jpa/src/test/resources/logback.xml new file mode 100644 index 0000000..5a56f30 --- /dev/null +++ b/jdbc/spring-data-jpa/src/test/resources/logback.xml @@ -0,0 +1,19 @@ + + + + + %d %5p %40.40c:%4L - %m%n + + + + + + + + + + + + + + \ No newline at end of file From 78ad05c4d8175e6f504badee7cbfd9c83c36bfa7 Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Fri, 2 Feb 2024 11:11:20 +0000 Subject: [PATCH 7/8] Upgrade github actions version --- .github/workflows/build.yaml | 4 ++-- .github/workflows/validate.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 779a587..0c8fe7d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index b9dc0b4..e6c8d64 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -19,10 +19,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: java-version: ${{ matrix.java }} distribution: 'temurin' From 9bebfc29f67613fa86308caac7537e2c05488ea9 Mon Sep 17 00:00:00 2001 From: Alexandr Gorshenin Date: Fri, 2 Feb 2024 12:25:35 +0000 Subject: [PATCH 8/8] Fixed usage of deprecated methods --- .../src/main/java/tech/ydb/examples/simple/AlterTable.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ydb-cookbook/src/main/java/tech/ydb/examples/simple/AlterTable.java b/ydb-cookbook/src/main/java/tech/ydb/examples/simple/AlterTable.java index 6242f43..3b9dada 100644 --- a/ydb-cookbook/src/main/java/tech/ydb/examples/simple/AlterTable.java +++ b/ydb-cookbook/src/main/java/tech/ydb/examples/simple/AlterTable.java @@ -1,6 +1,7 @@ package tech.ydb.examples.simple; import java.time.Duration; + import tech.ydb.core.grpc.GrpcTransport; import tech.ydb.examples.SimpleExample; import tech.ydb.table.Session; @@ -8,7 +9,6 @@ import tech.ydb.table.description.TableColumn; import tech.ydb.table.description.TableDescription; import tech.ydb.table.settings.AlterTableSettings; -import tech.ydb.table.values.OptionalType; import tech.ydb.table.values.PrimitiveType; @@ -42,8 +42,8 @@ protected void run(GrpcTransport transport, String pathPrefix) { session.alterTable(tablePath, new AlterTableSettings() .setTraceId("some-trace-id") - .addColumn("name", OptionalType.of(PrimitiveType.Text)) - .addColumn("age", OptionalType.of(PrimitiveType.Uint32)) + .addNullableColumn("name", PrimitiveType.Text) + .addNullableColumn("age", PrimitiveType.Uint32) .dropColumn("value") ).join().expectSuccess("cannot alter table");