This project uses Quarkus, the Supersonic Subatomic Java Framework. This project has been used to present Quarkus in several conferences and meetups. The slides are available here.
This project is the result of following the Quarkus Spring Web and Quarkus Spring Data JPA guides.
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
Generate project by running the following command
mvn io.quarkus:quarkus-maven-plugin:1.5.2.Final:create \
-DprojectGroupId=org.acme.spring.web \
-DprojectArtifactId=spring-on-quarkus-demo \
-DclassName="org.acme.spring.web.GreetingController" \
-Dpath="/greeting" \
-Dextensions="spring-web,resteasy-jsonb"
Navigate to the directory and launch the application
cd spring-on-quarkus-demo
mvn compile quarkus:dev- Open browser to http://localhost:8080
- Open browser to http://localhost:8080/greeting
- Add a
@RequestParamto hello method:@GetMapping public String hello(@RequestParam(defaultValue = "world")String name) { return "hello "+name; } - Open browser to
http://localhost:8080/greeting?name=folks
- Create class
Greetingin theorg.acme.spring.webpackage with the following content:package org.acme.spring.web; public class Greeting { private String message; public Greeting(String message) { this.message = message; } public String getMessage() { return message; } } - Update the content of
GreetingControllerto become:package org.acme.spring.web; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/greeting") public class GreetingController { @GetMapping public Greeting hello(@RequestParam(defaultValue = "world")String name) { return new Greeting("hello "+name); } } - Open browser to http://localhost:8080/greeting?name=folks
- Create class
GreetingServicein theorg.acme.spring.webpackage with the following content:package org.acme.spring.web; import org.springframework.stereotype.Service; public class GreetingService { public Greeting greet(String name){ return new Greeting(name); } } - Update the content of
GreetingControllerto become:package org.acme.spring.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/greeting") public class GreetingController { @Autowired private GreetingService greetingService; @GetMapping public Greeting hello(@RequestParam(defaultValue = "world")String name) { return greetingService.greeting("hello "+name); } } - Open browser to http://localhost:8080/greeting?name=folks
- We get an error because we have not made the Service class a bean.
- Modify the service class to add an annotation to make it a bean, for instance
@Service
package org.acme.spring.web;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public Greeting greeting(String name){
return new Greeting(name);
}
}
- Refresh browser
-
In the
GreetingService, add amessagefield. -
Use Constructor injection with
@Valueannotation which is what the Spring world use to use. -
Change the
greetmethod to use the message field.package org.acme.spring.web; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service public class GreetingService { private String message; public GreetingService(@Value("${greeting.message}")String message) { this.message = message; } public Greeting greet(String name){ return new Greeting(message + " " + name); } } -
Open browser to http://localhost:8080/greeting?name=folks
-
We get an error because we have not added the property
greeting.messageto the configuration file -
Open the
application.propertiesfile and add:greeting.message=hola -
Refresh browser
- Open the
pom.xmland add thequarkus-spring-data-jpaandquarkus-jdbc-postgresql<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-spring-data-jpa</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-jdbc-postgresql</artifactId> </dependency>
You can also add the extensions to your project by running the following command in your project base directory
./mvnw quarkus:add-extension -Dextensions="quarkus-spring-data-jpa,quarkus-jdbc-postgresql"-
Create class
Bookin theorg.acme.spring.webpackage with the following content:package org.acme.spring.web; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Book { @Id private Integer id; private String name; private Integer publicationYear; public Integer getId() { return id; } public String getName() { return name; } public Integer getPublicationYear() { return publicationYear; } } -
Create a
BookRepositoryinterface and make it a Spring repository extending the SpringCrudRepositorypackage org.acme.spring.web; import org.springframework.data.repository.CrudRepository; import java.util.List; public interface BookRepostory extends CrudRepository<Book, Integer> { List<Book> findByPublicationYearBetween(Integer lower,Integer higher); } -
Create the
BookControllerclass in order to expose the BookRepository via REST.package org.acme.spring.web; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/book") public class BookController { private final BookRepository bookRepository; public BookController(BookRepository bookRepository) { this.bookRepository = bookRepository; } @GetMapping public Iterable<Book> findAll() { return bookRepository.findAll(); } } -
Open the
application.propertiesfile and add database access configurationquarkus.datasource.url=jdbc:postgresql:quarkus_test quarkus.datasource.driver=org.postgresql.Driver quarkus.datasource.username=quarkus_test quarkus.datasource.password=quarkus_test quarkus.datasource.max-size=8 quarkus.datasource.min-size=2 -
Add database population script
import.sqlin resources folder with the following contentINSERT INTO book(id, name, publicationYear) VALUES (1, 'Sapiens' , 2011); INSERT INTO book(id, name, publicationYear) VALUES (2, 'Homo Deus' , 2015); INSERT INTO book(id, name, publicationYear) VALUES (3, 'Enlightenment Now' , 2018); INSERT INTO book(id, name, publicationYear) VALUES (4, 'Factfulness' , 2018); INSERT INTO book(id, name, publicationYear) VALUES (5, 'Sleepwalkers' , 2012); INSERT INTO book(id, name, publicationYear) VALUES (6, 'The Silk Roads' , 2015); -
Configure the loading of data adding the following properties in the
application.propertiesfilequarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.sql-load-script=import.sql -
At last, start a postgresql database by running the following command:
docker run --ulimit memlock=-1:-1 -it --rm=true --memory-swappiness=0 --name quarkus_test -e POSTGRES_USER=quarkus_test -e POSTGRES_PASSWORD=quarkus_test -e POSTGRES_DB=quarkus_test -p 5432:5432 postgres:11.5 -
Open browser to http://localhost:8080/book
-
Modify the
BookRepositoryto add a custom method allowing retrieve books between publication dates.package org.acme.spring.web; import org.springframework.data.repository.CrudRepository; import java.util.List; public interface BookRepository extends CrudRepository<Book, Integer> { List<Book> findByPublicationYearBetween(Integer lower,Integer higher); } -
Add the following method to the
BookController@GetMapping("year/{lower}/{higher}") public List<Book> findByPublicationYear(@PathVariable Integer lower, @PathVariable Integer higher){ return bookRepository.findByPublicationYearBetween(lower,higher); } -
Open browser to http://localhost:8080/book/year/2012/2015
-
Add a delete method in the
BookControlleras follows:@DeleteMapping("/{id}") public void deleteBook(@PathVariable Integer id){ bookRepository.deleteById(id); } -
Try to delete the book with
id10 by running the following commandcurl -X DELETE localhost:8080/book/10We get an
500 Internal Server Errorbecause any book with id 10 exist. -
Create a
MissingBookExceptionclass with following content:package org.acme.spring.web; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.BAD_REQUEST) public class MissingBookException extends RuntimeException { } -
Use this custom Exception in the delete method in the
BookController@DeleteMapping("/{id}") public void deleteBook(@PathVariable Integer id){ try { bookRepository.deleteById(id); } catch (Exception e) { throw new MissingBookException(); } } -
Retry to delete the book with
id10 by running the following commandhttp DELETE localhost:8080/book/10We get a
Bad RequestresponseHTTP/1.1 400 Bad Request Content-Length: 0 Content-Type: text/plain
The application can be packaged using ./mvnw package.
It produces the spring-on-quarkus-demo-1.0-SNAPSHOT-runner.jar file in the /target directory.
The application is now runnable using java -jar target/spring-on-quarkus-demo-1.0-SNAPSHOT-runner.jar.
You can create a native executable using: ./mvnw package -Pnative.
You can then execute your native executable with: ./target/spring-on-quarkus-demo-1.0-SNAPSHOT-runner
Or, if you don't have GraalVM installed, you can run the native executable build in a container using: ./mvnw package -Pnative -Dquarkus.native.container-build=true.
Then, build the docker image with docker build -f src/main/docker/Dockerfile.native -t quarkus/spring-on-quarkus-demo .
Finally, run the container using docker run -i --net=host --rm -p 8080:8080 quarkus/spring-on-quarkus-demo
If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image.
docker run -i --net=host --rm -p 8080:8080 quay.io/amunozhe/spring-boot-crud docker run -i --net=host --rm -p 8080:8080 quay.io/amunozhe/spring-quarkus-fruits-jvm docker run -i --net=host --rm -p 8080:8080 quay.io/amunozhe/spring-quarkus-fruits-native