From 5af0cb7983f6e477fbbaf23d1933884418512b28 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 12 Oct 2022 10:38:27 +0200 Subject: [PATCH] Add native jpa sample. See #654 --- jpa/graalvm-native/README.adoc | 48 +++++++++ jpa/graalvm-native/pom.xml | 65 +++++++++++ .../example/data/jpa/AuthorRepository.java | 32 ++++++ .../main/java/com/example/data/jpa/CLR.java | 101 ++++++++++++++++++ .../example/data/jpa/DataJpaApplication.java | 28 +++++ .../com/example/data/jpa/model/Author.java | 87 +++++++++++++++ .../java/com/example/data/jpa/model/Book.java | 75 +++++++++++++ .../src/main/resources/application.properties | 0 .../src/main/resources/schema.sql | 12 +++ .../data/jpa/DataJpaApplicationTests.java | 14 +++ jpa/pom.xml | 1 + 11 files changed, 463 insertions(+) create mode 100644 jpa/graalvm-native/README.adoc create mode 100644 jpa/graalvm-native/pom.xml create mode 100644 jpa/graalvm-native/src/main/java/com/example/data/jpa/AuthorRepository.java create mode 100644 jpa/graalvm-native/src/main/java/com/example/data/jpa/CLR.java create mode 100644 jpa/graalvm-native/src/main/java/com/example/data/jpa/DataJpaApplication.java create mode 100644 jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Author.java create mode 100644 jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Book.java create mode 100644 jpa/graalvm-native/src/main/resources/application.properties create mode 100644 jpa/graalvm-native/src/main/resources/schema.sql create mode 100644 jpa/graalvm-native/src/test/java/com/example/data/jpa/DataJpaApplicationTests.java diff --git a/jpa/graalvm-native/README.adoc b/jpa/graalvm-native/README.adoc new file mode 100644 index 00000000..1048cc27 --- /dev/null +++ b/jpa/graalvm-native/README.adoc @@ -0,0 +1,48 @@ +== Spring Data Jpa - GraalVM native image + +This example compiles a basic Spring Data Jpa application into a GraalVM native image. + +=== Install GraalVM & native image tooling + +Download and install GraalVM using https://sdkman.io/[SDKMAN!]. + +``` +$> sdk install java .r17-grl +$> gu install native-image +``` + +=== Compile to native image + +The maven build uses a dedicated profile `native` to trigger the native image creation. + +``` +$> maven clean package -P native +``` + +This will create the native executable in the target folder. + +=== Run the image + +Run the image directly from your console as shown below. +This will print results of crud functions invoked via a `CommandLineRunner`. + +``` +$> ./target/spring-data-jpa-graalvm-native + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v3.0.0-SNAPSHOT) + +INFO 82562 --- [ main] e.s.j.g.GraalvmNativeApplication : Starting GraalvmNativeApplication using Java 17.0.4 with PID 51404 +INFO 51404 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' +INFO 51404 --- [ main] c.example.data.jpa.DataJpaApplication : Started DataJpaApplication in 0.079 seconds (process running for 0.097) +insertAuthors(): author1 = Author{name='Josh Long'} +insertAuthors(): author2 = Author{name='Martin Kleppmann'} +listAllAuthors(): author = Author{name='Josh Long'} + Book{title='Reactive Spring'} +... +``` diff --git a/jpa/graalvm-native/pom.xml b/jpa/graalvm-native/pom.xml new file mode 100644 index 00000000..21f67ade --- /dev/null +++ b/jpa/graalvm-native/pom.xml @@ -0,0 +1,65 @@ + + 4.0.0 + + spring-data-jpa-graalvm-native + + + org.springframework.data.examples + spring-data-examples + 2.0.0.BUILD-SNAPSHOT + + + Spring Data JPA - GraalVM Native Image + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + jakarta.persistence + jakarta.persistence-api + 3.0.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.hibernate.orm.tooling + hibernate-enhance-maven-plugin + 6.1.4.Final + + + + true + true + true + + + enhance + + + + + + + + diff --git a/jpa/graalvm-native/src/main/java/com/example/data/jpa/AuthorRepository.java b/jpa/graalvm-native/src/main/java/com/example/data/jpa/AuthorRepository.java new file mode 100644 index 00000000..7154a86a --- /dev/null +++ b/jpa/graalvm-native/src/main/java/com/example/data/jpa/AuthorRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright 2022 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.Optional; + +import com.example.data.jpa.model.Author; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.ListCrudRepository; + +public interface AuthorRepository extends ListCrudRepository { + + Optional findByNameContainingIgnoreCase(String partialName); + + @Query("SELECT a FROM Author a WHERE a.name = :name") + Optional queryFindByName(String name); + +} diff --git a/jpa/graalvm-native/src/main/java/com/example/data/jpa/CLR.java b/jpa/graalvm-native/src/main/java/com/example/data/jpa/CLR.java new file mode 100644 index 00000000..a8d48e57 --- /dev/null +++ b/jpa/graalvm-native/src/main/java/com/example/data/jpa/CLR.java @@ -0,0 +1,101 @@ +/* + * Copyright 2022 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.Arrays; +import java.util.List; +import java.util.Set; + +import com.example.data.jpa.model.Author; +import com.example.data.jpa.model.Book; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +class CLR implements CommandLineRunner { + + private final AuthorRepository authorRepository; + + CLR(AuthorRepository authorRepository) { + this.authorRepository = authorRepository; + } + + @Override + @Transactional + public void run(String... args) { + var authors = insertAuthors(); + listAllAuthors(); + findById(authors); + findByPartialName(); + queryFindByName(); + deleteAll(); + } + + private void deleteAll() { + this.authorRepository.deleteAll(); + long count = this.authorRepository.count(); + System.out.printf("deleteAll(): count = %d%n", count); + } + + private void queryFindByName() { + Author author1 = this.authorRepository.queryFindByName("Josh Long").orElse(null); + Author author2 = this.authorRepository.queryFindByName("Martin Kleppmann").orElse(null); + + System.out.printf("queryFindByName(): author1 = %s%n", author1); + System.out.printf("queryFindByName(): author2 = %s%n", author2); + } + + private void findByPartialName() { + Author author1 = this.authorRepository.findByNameContainingIgnoreCase("sh lo").orElse(null); + Author author2 = this.authorRepository.findByNameContainingIgnoreCase("in kl").orElse(null); + + System.out.printf("findByPartialName(): author1 = %s%n", author1); + System.out.printf("findByPartialName(): author2 = %s%n", author2); + } + + private void findById(List authors) { + Author author1 = this.authorRepository.findById(authors.get(0).getId()).orElse(null); + Author author2 = this.authorRepository.findById(authors.get(1).getId()).orElse(null); + + System.out.printf("findById(): author1 = %s%n", author1); + System.out.printf("findById(): author2 = %s%n", author2); + } + + private void listAllAuthors() { + List authors = this.authorRepository.findAll(); + for (Author author : authors) { + System.out.printf("listAllAuthors(): author = %s%n", author); + for (Book book : author.getBooks()) { + System.out.printf("\t%s%n", book); + } + } + } + + private List insertAuthors() { + Author author1 = this.authorRepository.save(new Author(null, "Josh Long", + Set.of(new Book(null, "Reactive Spring"), new Book(null, "Cloud Native Java")))); + Author author2 = this.authorRepository.save( + new Author(null, "Martin Kleppmann", Set.of(new Book(null, "Designing Data Intensive Applications")))); + + System.out.printf("insertAuthors(): author1 = %s%n", author1); + System.out.printf("insertAuthors(): author2 = %s%n", author2); + + return Arrays.asList(author1, author2); + } + +} diff --git a/jpa/graalvm-native/src/main/java/com/example/data/jpa/DataJpaApplication.java b/jpa/graalvm-native/src/main/java/com/example/data/jpa/DataJpaApplication.java new file mode 100644 index 00000000..25c84817 --- /dev/null +++ b/jpa/graalvm-native/src/main/java/com/example/data/jpa/DataJpaApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2022 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; + +@SpringBootApplication +public class DataJpaApplication { + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(DataJpaApplication.class, args); + } + +} diff --git a/jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Author.java b/jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Author.java new file mode 100644 index 00000000..73ec2886 --- /dev/null +++ b/jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Author.java @@ -0,0 +1,87 @@ +/* + * Copyright 2022 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.model; + +import java.util.Objects; +import java.util.Set; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; + +@Entity +public class Author { + + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + private Set books; + + protected Author() { + } + + public Author(Long id, String name, Set books) { + this.id = id; + this.name = name; + this.books = books; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getBooks() { + return books; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + 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) + && Objects.equals(books, author.books); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, books); + } + + @Override + public String toString() { + return "Author{" + "name='" + name + '\'' + '}'; + } + +} diff --git a/jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Book.java b/jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Book.java new file mode 100644 index 00000000..2e7c256d --- /dev/null +++ b/jpa/graalvm-native/src/main/java/com/example/data/jpa/model/Book.java @@ -0,0 +1,75 @@ +/* + * Copyright 2022 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.model; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Book { + + @Id + @GeneratedValue + private Long id; + + private String title; + + protected Book() { + } + + public Book(Long id, String title) { + this.id = id; + this.title = title; + } + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Book book = (Book) o; + return Objects.equals(id, book.id) && Objects.equals(title, book.title); + } + + @Override + public int hashCode() { + return Objects.hash(id, title); + } + + @Override + public String toString() { + return "Book{" + "title='" + title + '\'' + '}'; + } + +} diff --git a/jpa/graalvm-native/src/main/resources/application.properties b/jpa/graalvm-native/src/main/resources/application.properties new file mode 100644 index 00000000..e69de29b diff --git a/jpa/graalvm-native/src/main/resources/schema.sql b/jpa/graalvm-native/src/main/resources/schema.sql new file mode 100644 index 00000000..14e51639 --- /dev/null +++ b/jpa/graalvm-native/src/main/resources/schema.sql @@ -0,0 +1,12 @@ +create table author +( + id bigint auto_increment primary key, + name varchar not null +); + +create table book +( + id bigint auto_increment primary key, + author bigint not null references author (id), + title varchar not null +); diff --git a/jpa/graalvm-native/src/test/java/com/example/data/jpa/DataJpaApplicationTests.java b/jpa/graalvm-native/src/test/java/com/example/data/jpa/DataJpaApplicationTests.java new file mode 100644 index 00000000..aedc738b --- /dev/null +++ b/jpa/graalvm-native/src/test/java/com/example/data/jpa/DataJpaApplicationTests.java @@ -0,0 +1,14 @@ +package com.example.data.jpa; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DataJpaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/jpa/pom.xml b/jpa/pom.xml index 84621c9f..a7018607 100644 --- a/jpa/pom.xml +++ b/jpa/pom.xml @@ -28,6 +28,7 @@ showcase vavr multitenant + graalvm-native