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
@RequestParam
to 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
Greeting
in theorg.acme.spring.web
package 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
GreetingController
to 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
GreetingService
in theorg.acme.spring.web
package 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
GreetingController
to 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 amessage
field. -
Use Constructor injection with
@Value
annotation which is what the Spring world use to use. -
Change the
greet
method 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.message
to the configuration file -
Open the
application.properties
file and add:greeting.message=hola
-
Refresh browser
- Open the
pom.xml
and add thequarkus-spring-data-jpa
andquarkus-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
Book
in theorg.acme.spring.web
package 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
BookRepository
interface and make it a Spring repository extending the SpringCrudRepository
package 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
BookController
class 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.properties
file 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.sql
in 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.properties
filequarkus.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
BookRepository
to 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
BookController
as follows:@DeleteMapping("/{id}") public void deleteBook(@PathVariable Integer id){ bookRepository.deleteById(id); }
-
Try to delete the book with
id
10 by running the following commandcurl -X DELETE localhost:8080/book/10
We get an
500 Internal Server Error
because any book with id 10 exist. -
Create a
MissingBookException
class 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
id
10 by running the following commandhttp DELETE localhost:8080/book/10
We get a
Bad Request
responseHTTP/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