├── .gitignore
├── .travis.yml
├── README.md
├── img
├── book-get-rsp.png
├── book-post-req.png
├── book-post-rsp.png
├── book-swagger.png
└── postgres.png
├── jpa-postgres-spring-model
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── basaki
│ └── example
│ └── postgres
│ └── spring
│ └── model
│ ├── Book.java
│ ├── BookRequest.java
│ └── Genre.java
├── jpa-postgres-spring-service
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── basaki
│ │ │ └── example
│ │ │ └── postgres
│ │ │ └── spring
│ │ │ ├── boot
│ │ │ └── BookApplication.java
│ │ │ ├── config
│ │ │ ├── SpringConfiguration.java
│ │ │ └── SwaggerConfiguration.java
│ │ │ ├── controller
│ │ │ ├── BookController.java
│ │ │ └── LandingController.java
│ │ │ ├── data
│ │ │ ├── entity
│ │ │ │ └── BookEntity.java
│ │ │ ├── repository
│ │ │ │ └── BookRepository.java
│ │ │ └── usertype
│ │ │ │ └── PGEnumUserType.java
│ │ │ ├── error
│ │ │ ├── DataNotFoundException.java
│ │ │ ├── ErrorInfo.java
│ │ │ ├── ExceptionProcessor.java
│ │ │ └── InvalidSearchException.java
│ │ │ ├── service
│ │ │ └── BookService.java
│ │ │ └── util
│ │ │ └── UuidBeanFactory.java
│ └── resources
│ │ ├── config
│ │ ├── application.yml
│ │ └── bootstrap.yml
│ │ └── db
│ │ └── create-db.sql
│ └── test
│ └── java
│ └── com
│ └── basaki
│ └── example
│ └── postgres
│ └── spring
│ └── controller
│ └── BookControllerUnitTests.java
└── pom.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
5 |
6 | *.iml
7 |
8 | ## Directory-based project format:
9 | .idea
10 | .idea/*.xml
11 |
12 | # if you remove the above rule, at least ignore the following:
13 |
14 | # User-specific stuff:
15 | .idea/workspace.xml
16 | .idea/tasks.xml
17 | .idea/dictionaries
18 | .idea/vcs.xml
19 | .idea/jsLibraryMappings.xml
20 |
21 | # Sensitive or high-churn files:
22 | .idea/dataSources.ids
23 | .idea/dataSources.xml
24 | .idea/dataSources.local.xml
25 | .idea/sqlDataSources.xml
26 | .idea/dynamic.xml
27 | .idea/uiDesigner.xml
28 |
29 | # Gradle:
30 | .idea/gradle.xml
31 | .idea/libraries
32 |
33 | # Mongo Explorer plugin:
34 | .idea/mongoSettings.xml
35 |
36 | ## File-based project format:
37 | *.iws
38 |
39 | ## Plugin-specific files:
40 |
41 | # IntelliJ
42 | /out/
43 |
44 | # mpeltonen/sbt-idea plugin
45 | .idea_modules/
46 |
47 | # JIRA plugin
48 | atlassian-ide-plugin.xml
49 |
50 | # Crashlytics plugin (for Android Studio and IntelliJ)
51 | com_crashlytics_export_strings.xml
52 | crashlytics.properties
53 | crashlytics-build.properties
54 | fabric.properties
55 | ### OSX template
56 | *.DS_Store
57 | .AppleDouble
58 | .LSOverride
59 |
60 | # Icon must end with two \r
61 | Icon
62 |
63 | # Thumbnails
64 | ._*
65 |
66 | # Files that might appear in the root of a volume
67 | .DocumentRevisions-V100
68 | .fseventsd
69 | .Spotlight-V100
70 | .TemporaryItems
71 | .Trashes
72 | .VolumeIcon.icns
73 | .com.apple.timemachine.donotpresent
74 |
75 | # Directories potentially created on remote AFP share
76 | .AppleDB
77 | .AppleDesktop
78 | Network Trash Folder
79 | Temporary Items
80 | .apdisk
81 | ### EiffelStudio template
82 | # The compilation directory
83 | EIFGENs
84 | ### Xcode template
85 | # Xcode
86 | #
87 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
88 |
89 | ## Build generated
90 | build/
91 | DerivedData/
92 |
93 | ## Various settings
94 | *.pbxuser
95 | !default.pbxuser
96 | *.mode1v3
97 | !default.mode1v3
98 | *.mode2v3
99 | !default.mode2v3
100 | *.perspectivev3
101 | !default.perspectivev3
102 | xcuserdata/
103 |
104 | ## Other
105 | *.moved-aside
106 | *.xccheckout
107 | *.xcscmblueprint
108 | ### Java template
109 | *.class
110 |
111 | # Mobile Tools for Java (J2ME)
112 | .mtj.tmp/
113 |
114 | # Package Files #
115 | *.jar
116 | *.war
117 | *.ear
118 |
119 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
120 | hs_err_pid*
121 |
122 | ## Eclipse
123 | .project
124 | .classpath
125 |
126 | ## Directory-based project format:
127 | .settings/
128 | # if you remove the above rule, at least ignore the following:
129 |
130 | ### Maven template
131 | target
132 | pom.xml.tag
133 | pom.xml.releaseBackup
134 | pom.xml.versionsBackup
135 | pom.xml.next
136 | release.properties
137 | dependency-reduced-pom.xml
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # Travis Build File
2 | language: java
3 | jdk:
4 | - oraclejdk8
5 | before_script:
6 | - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
7 | script: mvn clean compile test
8 | notifications:
9 | email:
10 | - indra.basak1@gmail.com
11 | on_success: change
12 | on_failure: always
13 | use_notice: true
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Build Status][travis-badge]][travis-badge-url]
2 |
3 | 
4 |
5 | JPA PostgreSQL Spring Service Example
6 | =========================================
7 | This is a [**Spring Boot**](https://projects.spring.io/spring-boot/) based microservice example backed by
8 | [**PostgreSQL**](https://www.postgresql.org/) database. This examples shows the following:
9 | * Use `spring.datasource` properties and Spring Data auto configuration.
10 | * How to use JPA's `CrudRepository`
11 | * How to insert `UUID` field in Postgres database and generate `UUID `index.
12 | * How to convert Java `Enum` to Postgres `Enum` type.
13 | * How to use `Dozer` Java Bean mapper.
14 |
15 | ### PostgreSQL Assumptions
16 | * You have a PostgreSQL database server running on your `localhost` and in port `5432`.
17 | * You have a database named `postgres` running on the server
18 | * The server has a user named `postgres` with password `postgres`.
19 | * If any of the assumptions doesn't hold true, change the `spring.datasource` properties in the `application.yml` file.
20 |
21 | ### Create Database Entities
22 | Execute the `create-db.sql` script under `resources` directory on your PostgreSQL server either using PostgreSQL administration and management tools, [pgAdmin](https://www.pgadmin.org/),
23 | or from the PostgreSQL interactive terminal program, called `psql`.
24 |
25 | ### Build
26 | Execute the following command from the parent directory:
27 | ```
28 | mvn clean install
29 | ```
30 |
31 | ### Start the Service
32 | The main entry point `jpa-postgres-spring` example is `com.basaki.example.postgres.boot.BookApplication` class.
33 | You can start the application from an IDE by starting the `BookApplication` class.
34 | ```
35 |
36 | . ____ _ __ _ _
37 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
38 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
39 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
40 | ' |____| .__|_| |_|_| |_\__, | / / / /
41 | =========|_|==============|___/=/_/_/_/
42 | :: Spring Boot :: (v1.4.1.RELEASE)
43 | ...
44 | 2017-03-08 21:50:17.987 INFO 62548 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
45 | 2017-03-08 21:50:17.992 INFO 62548 --- [ main] c.b.e.p.spring.boot.BookApplication : Started BookApplication in 7.152 seconds (JVM running for 7.566)
46 |
47 | ```
48 | The application starts up at port `8080`.
49 |
50 | ### Accessing Swagger
51 | On your browser, navigate to `http://localhost:8080/` to view the Swagger.
52 | 
53 |
54 | Click the `Show/Hide` link to view all the operations exposed by Book API.
55 |
56 | #### POST Example
57 | Once expanded, create a new Book entry by clicking `POST` and entering the following JSON snippet in the `request` field and click `Try it out!`.
58 | 
59 |
60 | Here is the response you get back. Please notice the book title and the author gets captitalized before insertion.
61 | 
62 |
63 | #### GET Example
64 | To view all books, click `GET` and entry either `title`, `author`, `genre` or any combination of them and click lick `Try it out!`.
65 | The `title` and `author` parameters are case insensitive.
66 | Here is the response you get back:
67 | 
68 |
69 | [travis-badge]: https://travis-ci.org/indrabasak/jpa-postgres-spring.svg?branch=master
70 | [travis-badge-url]: https://travis-ci.org/indrabasak/jpa-postgres-spring/
--------------------------------------------------------------------------------
/img/book-get-rsp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrabasak/jpa-postgres-spring/bd969603daea00d1ba44ee12af5e16fc02e349fc/img/book-get-rsp.png
--------------------------------------------------------------------------------
/img/book-post-req.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrabasak/jpa-postgres-spring/bd969603daea00d1ba44ee12af5e16fc02e349fc/img/book-post-req.png
--------------------------------------------------------------------------------
/img/book-post-rsp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrabasak/jpa-postgres-spring/bd969603daea00d1ba44ee12af5e16fc02e349fc/img/book-post-rsp.png
--------------------------------------------------------------------------------
/img/book-swagger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrabasak/jpa-postgres-spring/bd969603daea00d1ba44ee12af5e16fc02e349fc/img/book-swagger.png
--------------------------------------------------------------------------------
/img/postgres.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/indrabasak/jpa-postgres-spring/bd969603daea00d1ba44ee12af5e16fc02e349fc/img/postgres.png
--------------------------------------------------------------------------------
/jpa-postgres-spring-model/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | com.basaki.example
5 | jpa-postgres-spring
6 | 1.0
7 |
8 | 4.0.0
9 | com.basaki.example
10 | jpa-postgres-spring-model
11 | jar
12 | 1.0
13 | JPA Postgres Spring Model
14 |
15 |
16 |
17 |
18 |
19 | org.springframework.boot
20 | spring-boot-maven-plugin
21 |
22 | exec
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-model/src/main/java/com/basaki/example/postgres/spring/model/Book.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.model;
2 |
3 | import java.util.UUID;
4 | import lombok.AllArgsConstructor;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 |
8 | /**
9 | * {@code Book} represents a book entity.
10 | *
11 | *
12 | * @author Indra Basak
13 | * @since 3/8/17
14 | */
15 | @Data
16 | @NoArgsConstructor
17 | @AllArgsConstructor
18 | public class Book {
19 |
20 | private UUID id;
21 | private String title;
22 | private String author;
23 | private Genre genre;
24 | }
25 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-model/src/main/java/com/basaki/example/postgres/spring/model/BookRequest.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.model;
2 |
3 | import lombok.Data;
4 |
5 | /**
6 | * {@code BookRequest} represents a request to create or update a book entity.
7 | *
8 | *
9 | * @author Indra Basak
10 | * @since 3/8/17
11 | */
12 | @Data
13 | public class BookRequest {
14 | private String title;
15 | private String author;
16 | private Genre genre;
17 | }
18 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-model/src/main/java/com/basaki/example/postgres/spring/model/Genre.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonValue;
5 |
6 | /**
7 | * {@code Genre} represents a genre of a {@code Book}.
8 | *
9 | *
10 | * @author Indra Basak
11 | * @since 3/8/17
12 | */
13 | public enum Genre {
14 |
15 | DRAMA, ROMANCE, GUIDE, TRAVEL;
16 |
17 | /**
18 | * Returns a Genre enum based on string matching
19 | *
20 | * @param value string stored in database
21 | * @return a matching Genre
22 | */
23 | @JsonCreator
24 | public static Genre fromValue(String value) {
25 | return valueOf(value.toUpperCase());
26 | }
27 |
28 | /**
29 | * Converts a Genre to matching type string
30 | *
31 | * @param genre service enum
32 | * @return matching type string
33 | */
34 | @JsonValue
35 | public static String toValue(Genre genre) {
36 | return genre.name().toLowerCase();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | com.basaki.example
5 | jpa-postgres-spring
6 | 1.0
7 |
8 |
9 | 4.0.0
10 | com.basaki.example
11 | jpa-postgres-spring-service
12 | jar
13 | 1.0
14 | JPA Postgres Spring Service
15 |
16 |
17 |
18 | com.basaki.example
19 | jpa-postgres-spring-model
20 | ${project.version}
21 |
22 |
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-data-jpa
27 |
28 |
29 | org.apache.logging.log4j
30 | log4j-slf4j-impl
31 |
32 |
33 |
34 |
35 | org.postgresql
36 | postgresql
37 |
38 |
39 |
40 | javax.interceptor
41 | javax.interceptor-api
42 | 1.2
43 |
44 |
45 |
46 | net.sf.dozer
47 | dozer
48 | 5.4.0
49 |
50 |
51 |
52 |
53 |
54 |
55 | org.springframework.boot
56 | spring-boot-maven-plugin
57 |
58 | true
59 | com.basaki.example.postgres.spring.boot.BookApplication
60 |
61 |
62 |
63 |
64 | repackage
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/boot/BookApplication.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.boot;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.autoconfigure.domain.EntityScan;
6 | import org.springframework.context.annotation.ComponentScan;
7 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
8 |
9 | /**
10 | * {@code BookApplication} represents the entry point for book controller
11 | * spring boot application.
12 | *
13 | *
14 | * @author Indra Basak
15 | * @since 2/23/17
16 | */
17 | @SpringBootApplication
18 | @ComponentScan(basePackages = {
19 | "com.basaki.example.postgres.spring.config",
20 | "com.basaki.example.postgres.spring.controller",
21 | "com.basaki.example.postgres.spring.data.entity",
22 | "com.basaki.example.postgres.spring.data.repository",
23 | "com.basaki.example.postgres.spring.error",
24 | "com.basaki.example.postgres.spring.model",
25 | "com.basaki.example.postgres.spring.service"})
26 | @EntityScan(basePackages = "com.basaki.example.postgres.spring.data.entity")
27 | @EnableJpaRepositories(basePackages = {"com.basaki.example.postgres.spring.data.repository"})
28 | public class BookApplication {
29 | public static void main(String[] args) {
30 | SpringApplication.run(BookApplication.class, args);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/config/SpringConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.config;
2 |
3 | import com.basaki.example.postgres.spring.util.UuidBeanFactory;
4 | import java.util.UUID;
5 | import org.dozer.DozerBeanMapper;
6 | import org.dozer.Mapper;
7 | import org.dozer.loader.api.BeanMappingBuilder;
8 | import org.dozer.loader.api.TypeMappingOptions;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 |
12 | /**
13 | * Created by indra.basak on 3/8/17.
14 | */
15 | @Configuration
16 | public class SpringConfiguration {
17 |
18 | @Bean
19 | public static Mapper getMapper() {
20 | BeanMappingBuilder builder = new BeanMappingBuilder() {
21 | protected void configure() {
22 | mapping(UUID.class, UUID.class, TypeMappingOptions.oneWay(),
23 | TypeMappingOptions.beanFactory(
24 | UuidBeanFactory.class.getName()));
25 | }
26 | };
27 |
28 | DozerBeanMapper mapper = new DozerBeanMapper();
29 | mapper.addMapping(builder);
30 |
31 | return mapper;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/config/SwaggerConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.config;
2 |
3 | import org.springframework.context.annotation.Bean;
4 | import org.springframework.context.annotation.Configuration;
5 | import springfox.documentation.builders.PathSelectors;
6 | import springfox.documentation.builders.RequestHandlerSelectors;
7 | import springfox.documentation.service.ApiInfo;
8 | import springfox.documentation.service.Contact;
9 | import springfox.documentation.spi.DocumentationType;
10 | import springfox.documentation.spring.web.plugins.Docket;
11 | import springfox.documentation.swagger2.annotations.EnableSwagger2;
12 |
13 | /**
14 | * {@code SwaggerConfiguration} is the configuration for setting up swagger for
15 | * the author controller. The swagger documentation can be viewed at {@code
16 | * http://:/swagger-ui-html}
17 | *
18 | *
19 | * @author Indra Basak
20 | * @since 2/23/17
21 | */
22 | @Configuration
23 | @EnableSwagger2
24 | public class SwaggerConfiguration {
25 |
26 | /**
27 | * Creates the Swagger configuration bean.
28 | *
29 | * @return docket bean
30 | */
31 | @Bean
32 | public Docket api() {
33 | return new Docket(DocumentationType.SWAGGER_2)
34 | .groupName("book")
35 | .select()
36 | .apis(RequestHandlerSelectors.basePackage(
37 | "com.basaki.example.postgres.spring.controller"))
38 | .paths(PathSelectors.any())
39 | .build()
40 | .apiInfo(apiInfo("Book API",
41 | "Book Service API Powered By PostgreSQL"));
42 | }
43 |
44 | /**
45 | * Creates an object containing API information including author name,
46 | * email, version, license, etc.
47 | *
48 | * @param title API title
49 | * @param description API description
50 | * @return API information
51 | */
52 | private ApiInfo apiInfo(String title, String description) {
53 | Contact contact = new Contact("Indra Basak", "",
54 | "developer@gmail.com");
55 | return new ApiInfo(title, description, "1.0", "terms of controller url",
56 | contact, "license", "license url");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/controller/BookController.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.controller;
2 |
3 | import com.basaki.example.postgres.spring.model.Book;
4 | import com.basaki.example.postgres.spring.model.BookRequest;
5 | import com.basaki.example.postgres.spring.model.Genre;
6 | import com.basaki.example.postgres.spring.service.BookService;
7 | import io.swagger.annotations.Api;
8 | import io.swagger.annotations.ApiOperation;
9 | import io.swagger.annotations.ApiResponse;
10 | import io.swagger.annotations.ApiResponses;
11 | import java.util.List;
12 | import java.util.UUID;
13 | import lombok.extern.slf4j.Slf4j;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.http.HttpStatus;
16 | import org.springframework.http.MediaType;
17 | import org.springframework.web.bind.annotation.PathVariable;
18 | import org.springframework.web.bind.annotation.RequestBody;
19 | import org.springframework.web.bind.annotation.RequestMapping;
20 | import org.springframework.web.bind.annotation.RequestMethod;
21 | import org.springframework.web.bind.annotation.RequestParam;
22 | import org.springframework.web.bind.annotation.ResponseBody;
23 | import org.springframework.web.bind.annotation.ResponseStatus;
24 | import org.springframework.web.bind.annotation.RestController;
25 |
26 | /**
27 | * {@code BookController} is the spring REST controller for book API. Exposes
28 | * all CRUD operations on book.
29 | *
30 | *
31 | * @author Indra Basak
32 | * @since 3/8/17
33 | */
34 | @RestController
35 | @Slf4j
36 | @Api(value = "Book API",
37 | description = "Book API",
38 | produces = "application/json", tags = {"API"})
39 | public class BookController {
40 |
41 | public static final String BOOK_URL = "/books";
42 |
43 | public static final String BOOK_BY_ID_URL = BOOK_URL + "/{id}";
44 |
45 |
46 | @Autowired
47 | private BookService service;
48 |
49 | @ApiOperation(
50 | value = "Creates an book.",
51 | notes = "Requires book title and name of the author.",
52 | response = Book.class)
53 | @ApiResponses({
54 | @ApiResponse(code = 201, response = Override.class,
55 | message = "Override override created successfully")})
56 | @RequestMapping(method = RequestMethod.POST, value = BOOK_URL,
57 | consumes = MediaType.APPLICATION_JSON_VALUE,
58 | produces = MediaType.APPLICATION_JSON_VALUE)
59 | @ResponseStatus(HttpStatus.CREATED)
60 | public Book create(@RequestBody BookRequest request) {
61 | return service.create(request);
62 | }
63 |
64 | @ApiOperation(
65 | value = "Retrieves a book by ID.",
66 | notes = "Requires a book identifier",
67 | response = Book.class)
68 | @RequestMapping(method = RequestMethod.GET, value = BOOK_BY_ID_URL,
69 | produces = {MediaType.APPLICATION_JSON_VALUE})
70 | @ResponseBody
71 | public Book read(@PathVariable("id") UUID id) {
72 | return service.read(id);
73 | }
74 |
75 | @ApiOperation(
76 | value = "Retrieves all books associated with a title, author, category or combination of them.",
77 | response = Book.class, responseContainer = "List")
78 | @RequestMapping(method = RequestMethod.GET, value = BOOK_URL,
79 | produces = {MediaType.APPLICATION_JSON_VALUE})
80 | @ResponseBody
81 | public List read(
82 | @RequestParam(value = "title", required = false) String title,
83 | @RequestParam(value = "author", required = false) String author,
84 | @RequestParam(value = "genre", required = false) Genre genre) {
85 | return service.read(title, author, genre);
86 | }
87 |
88 | @ApiOperation(value = "Updates a book.", response = Book.class)
89 | @ApiResponses({
90 | @ApiResponse(code = 201, response = Book.class,
91 | message = "Updated a book created successfully")})
92 | @RequestMapping(method = RequestMethod.PUT, value = BOOK_BY_ID_URL,
93 | consumes = MediaType.APPLICATION_JSON_VALUE,
94 | produces = MediaType.APPLICATION_JSON_VALUE)
95 | @ResponseStatus(HttpStatus.CREATED)
96 | public Book update(@PathVariable("id") UUID id,
97 | @RequestBody BookRequest request) {
98 | return service.update(id, request);
99 | }
100 |
101 | @ApiOperation(value = "Deletes a book by ID.")
102 | @RequestMapping(method = RequestMethod.DELETE, value = BOOK_BY_ID_URL)
103 | @ResponseBody
104 | public void deleteById(@PathVariable("id") UUID id) {
105 | service.delete(id);
106 | }
107 |
108 | @ApiOperation(value = "Deletes all books.")
109 | @RequestMapping(method = RequestMethod.DELETE, value = BOOK_URL)
110 | @ResponseBody
111 | public void deleteAll() {
112 | service.deleteAll();
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/controller/LandingController.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.controller;
2 |
3 | import java.io.IOException;
4 | import javax.servlet.http.HttpServletResponse;
5 | import org.springframework.web.bind.annotation.RequestMapping;
6 | import org.springframework.web.bind.annotation.RestController;
7 | import springfox.documentation.annotations.ApiIgnore;
8 |
9 | /**
10 | * {@code LandingController} is the REST controller for redirecting the home
11 | * page to swagger UI.
12 | *
13 | * @author Indra Basak
14 | * @since 2/23/17
15 | */
16 | @RestController
17 | @ApiIgnore
18 | public class LandingController {
19 |
20 | @RequestMapping("/")
21 | public void home(HttpServletResponse response) throws IOException {
22 | response.sendRedirect("/swagger-ui.html");
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/data/entity/BookEntity.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.data.entity;
2 |
3 | import com.basaki.example.postgres.spring.model.Genre;
4 | import java.io.Serializable;
5 | import java.util.UUID;
6 | import javax.persistence.Column;
7 | import javax.persistence.Entity;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.Id;
10 | import javax.persistence.Table;
11 | import lombok.AllArgsConstructor;
12 | import lombok.Data;
13 | import lombok.NoArgsConstructor;
14 | import org.hibernate.annotations.GenericGenerator;
15 | import org.hibernate.annotations.Parameter;
16 | import org.hibernate.annotations.Type;
17 |
18 | /**
19 | * {@code BookEntity} represents a row in the Books
database table.
20 | *
21 | *
22 | * @author Indra Basak
23 | * @since 3/8/17
24 | */
25 | @Data
26 | @NoArgsConstructor
27 | @AllArgsConstructor
28 | @Entity
29 | @Table(name = "books", schema = "example")
30 | public class BookEntity implements Serializable {
31 |
32 | @Id
33 | @Column(name = "id")
34 | @GenericGenerator(name = "uuid-gen", strategy = "uuid2")
35 | @GeneratedValue(generator = "uuid-gen")
36 | @Type(type = "pg-uuid")
37 | private UUID id;
38 |
39 | @Column(name = "title", nullable = false)
40 | private String title;
41 |
42 | @Column(name = "author", nullable = false)
43 | private String author;
44 |
45 | @Column(name = "genre", nullable = false)
46 | @Type(type = "com.basaki.example.postgres.spring.data.usertype.PGEnumUserType",
47 | parameters = {@Parameter(name = "enumClassName",
48 | value = "com.basaki.example.postgres.spring.model.Genre")})
49 | private Genre genre;
50 | }
51 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/data/repository/BookRepository.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.data.repository;
2 |
3 | import com.basaki.example.postgres.spring.data.entity.BookEntity;
4 | import com.basaki.example.postgres.spring.model.Genre;
5 | import java.util.List;
6 | import java.util.UUID;
7 | import org.springframework.data.repository.CrudRepository;
8 |
9 | /**
10 | * {@code BookRepository} exposes all CRUD operations on a data of type
11 | * {@code Book}.
12 | *
13 | *
14 | * @author Indra Basak
15 | * @since 3/8/17
16 | */
17 | public interface BookRepository extends CrudRepository {
18 |
19 | List findByTitleIgnoreCase(String title);
20 |
21 | List findByTitleIgnoreCaseAndAuthorIgnoreCase(String title,
22 | String author);
23 |
24 | List findByTitleIgnoreCaseAndAuthorIgnoreCaseAndGenre(
25 | String title,
26 | String author,
27 | Genre genre);
28 |
29 | List findByTitleIgnoreCaseAndGenre(String title, Genre genre);
30 |
31 | List findByAuthorIgnoreCase(String author);
32 |
33 | List findByAuthorIgnoreCaseAndGenre(String author, Genre genre);
34 |
35 | List findByGenre(Genre genre);
36 | }
37 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/data/usertype/PGEnumUserType.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.data.usertype;
2 |
3 | import java.io.Serializable;
4 | import java.sql.PreparedStatement;
5 | import java.sql.ResultSet;
6 | import java.sql.SQLException;
7 | import java.sql.Types;
8 | import java.util.Properties;
9 | import org.hibernate.HibernateException;
10 | import org.hibernate.engine.spi.SessionImplementor;
11 | import org.hibernate.usertype.EnhancedUserType;
12 | import org.hibernate.usertype.ParameterizedType;
13 | import org.postgresql.util.PGobject;
14 |
15 | /**
16 | * {@code PGEnumUserType} converts a Java Enum to Postgres Enum type and vice
17 | * versa.
18 | *
19 | *
20 | * @since 3/8/17
21 | */
22 | public class PGEnumUserType implements EnhancedUserType, ParameterizedType {
23 | // Enum class under observation
24 | private Class enumClass;
25 |
26 | public void setParameterValues(Properties parameters) {
27 | String enumClassName = parameters.getProperty("enumClassName");
28 | try {
29 | enumClass = (Class) Class.forName(enumClassName);
30 | } catch (ClassNotFoundException cnfe) {
31 | throw new HibernateException("Enum class not found", cnfe);
32 | }
33 | }
34 |
35 | public Object assemble(Serializable cached, Object owner)
36 | throws HibernateException {
37 | return cached;
38 | }
39 |
40 | public Object deepCopy(Object value) throws HibernateException {
41 | return value;
42 | }
43 |
44 | public Serializable disassemble(Object value) throws HibernateException {
45 | return (Enum) value;
46 | }
47 |
48 | public boolean equals(Object x, Object y) throws HibernateException {
49 | return x == y;
50 | }
51 |
52 | public int hashCode(Object x) throws HibernateException {
53 | return x.hashCode();
54 | }
55 |
56 | @Override
57 | public Object nullSafeGet(ResultSet rs, String[] names,
58 | SessionImplementor session,
59 | Object owner) throws HibernateException, SQLException {
60 | Object object = rs.getObject(names[0]);
61 | if (rs.wasNull()) {
62 | return null;
63 | }
64 |
65 | // Converts a PostGres Enum from PGobject to Java Enum
66 | if (object instanceof PGobject) {
67 | PGobject pg = (PGobject) object;
68 | return Enum.valueOf(enumClass, pg.getValue());
69 | }
70 | return null;
71 | }
72 |
73 | @Override
74 | public void nullSafeSet(PreparedStatement st, Object value, int index,
75 | SessionImplementor session) throws HibernateException, SQLException {
76 | if (value == null) {
77 | st.setNull(index, Types.VARCHAR);
78 | } else {
79 | // Types.OTHER (1111) gets mapped to Postgres Enum
80 | st.setObject(index, ((Enum) value), Types.OTHER);
81 | }
82 | }
83 |
84 | public boolean isMutable() {
85 | return false;
86 | }
87 |
88 | public Object replace(Object original, Object target, Object owner)
89 | throws HibernateException {
90 | return original;
91 | }
92 |
93 | public Class returnedClass() {
94 | return enumClass;
95 | }
96 |
97 | public int[] sqlTypes() {
98 | return new int[]{Types.VARCHAR};
99 | }
100 |
101 | public Object fromXMLString(String xmlValue) {
102 | return Enum.valueOf(enumClass, xmlValue);
103 | }
104 |
105 | public String objectToSQLString(Object value) {
106 | return '\'' + ((Enum) value).name() + '\'';
107 | }
108 |
109 | public String toXMLString(Object value) {
110 | return ((Enum) value).name();
111 | }
112 | }
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/error/DataNotFoundException.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.error;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | /**
9 | * {@code DataNotFoundException} is a runtime exception if no entities are found
10 | * in the database.
11 | *
12 | *
13 | * @author Indra Basak
14 | * @since 3/18/17
15 | */
16 | @NoArgsConstructor
17 | @ToString(callSuper = true)
18 | @Getter
19 | @Setter
20 | public class DataNotFoundException extends RuntimeException {
21 | public DataNotFoundException(String s) {
22 | super(s);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/error/ErrorInfo.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.error;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.NoArgsConstructor;
6 | import lombok.Setter;
7 |
8 | /**
9 | * {@code ErrorInfo} represents an error response object which is exposed to
10 | * the external client in a human readbable form.
11 | *
12 | *
13 | * @author Indra Basak
14 | * @since 3/18/17
15 | */
16 | @NoArgsConstructor
17 | @AllArgsConstructor
18 | @Getter
19 | @Setter
20 | public class ErrorInfo {
21 |
22 | private String path;
23 |
24 | private int code;
25 |
26 | private String type;
27 |
28 | private String message;
29 | }
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/error/ExceptionProcessor.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.error;
2 |
3 | /**
4 | * Created by indra.basak on 3/8/17.
5 | */
6 |
7 | import javax.servlet.http.HttpServletRequest;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.web.bind.annotation.ControllerAdvice;
11 | import org.springframework.web.bind.annotation.ExceptionHandler;
12 | import org.springframework.web.bind.annotation.ResponseBody;
13 | import org.springframework.web.bind.annotation.ResponseStatus;
14 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
15 |
16 | /**
17 | * {@code ExceptionProcessor} processes exceptions at the application level and
18 | * it is not restricted to any specific controller.
19 | *
20 | *
21 | * @author Indra Basak
22 | * @since 3/8/17
23 | */
24 | @ControllerAdvice
25 | @Slf4j
26 | public class ExceptionProcessor {
27 |
28 | /**
29 | * Handles DataNotFoundException exception.It unwraps the root case
30 | * and coverts it into an ErrorInfo object.
31 | *
32 | * @param req HTTP request to extract the URL
33 | * @param ex exception to be processed
34 | * @return ths error information that is sent to the client
35 | */
36 | @ExceptionHandler(DataNotFoundException.class)
37 | @ResponseStatus(value = HttpStatus.NOT_FOUND)
38 | @ResponseBody
39 | public ErrorInfo handleDataNotFoundException(
40 | HttpServletRequest req, DataNotFoundException ex) {
41 | ErrorInfo info = getErrorInfo(req, HttpStatus.NOT_FOUND);
42 | info.setMessage(ex.getMessage());
43 |
44 | return info;
45 | }
46 |
47 | /**
48 | * Handles InvalidSearchException exception.It unwraps the root
49 | * case
50 | * and coverts it into an ErrorInfo object.
51 | *
52 | * @param req HTTP request to extract the URL
53 | * @param ex exception to be processed
54 | * @return ths error information that is sent to the client
55 | */
56 | @ExceptionHandler(InvalidSearchException.class)
57 | @ResponseStatus(value = HttpStatus.BAD_REQUEST)
58 | @ResponseBody
59 | public ErrorInfo handleIllegalArgException(HttpServletRequest req,
60 | InvalidSearchException ex) {
61 | ErrorInfo info = getErrorInfo(req, HttpStatus.BAD_REQUEST);
62 | info.setMessage(ex.getMessage());
63 |
64 | return info;
65 | }
66 |
67 | private ErrorInfo getErrorInfo(HttpServletRequest req,
68 | HttpStatus httpStatus) {
69 | ErrorInfo info = new ErrorInfo();
70 | ServletUriComponentsBuilder builder =
71 | ServletUriComponentsBuilder.fromServletMapping(req);
72 | info.setPath(builder.path(
73 | req.getRequestURI()).build().getPath());
74 | info.setCode(httpStatus.value());
75 | info.setType(httpStatus.getReasonPhrase());
76 | return info;
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/error/InvalidSearchException.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.error;
2 |
3 | import lombok.Getter;
4 | import lombok.NoArgsConstructor;
5 | import lombok.Setter;
6 | import lombok.ToString;
7 |
8 | /**
9 | * {@code InvalidSearchException} is a runtime exception if invalid search
10 | * criteria is entered.
11 | *
12 | *
13 | * @author Indra Basak
14 | * @since 3/18/17
15 | */
16 | @NoArgsConstructor
17 | @ToString(callSuper = true)
18 | @Getter
19 | @Setter
20 | public class InvalidSearchException extends RuntimeException {
21 | public InvalidSearchException(String s) {
22 | super(s);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/service/BookService.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.service;
2 |
3 | import com.basaki.example.postgres.spring.data.entity.BookEntity;
4 | import com.basaki.example.postgres.spring.data.repository.BookRepository;
5 | import com.basaki.example.postgres.spring.error.DataNotFoundException;
6 | import com.basaki.example.postgres.spring.error.InvalidSearchException;
7 | import com.basaki.example.postgres.spring.model.Book;
8 | import com.basaki.example.postgres.spring.model.BookRequest;
9 | import com.basaki.example.postgres.spring.model.Genre;
10 | import java.util.List;
11 | import java.util.UUID;
12 | import java.util.stream.Collectors;
13 | import lombok.extern.slf4j.Slf4j;
14 | import org.dozer.Mapper;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.stereotype.Service;
17 | import org.springframework.transaction.annotation.Transactional;
18 | import org.springframework.util.Assert;
19 |
20 | /**
21 | * {@code BookService} service provides data access service for {@code Book}.
22 | *
23 | *
24 | * @author Indra Basak
25 | * @sice 3/8/17
26 | */
27 | @Service
28 | @Slf4j
29 | public class BookService {
30 |
31 | @Autowired
32 | private BookRepository repo;
33 |
34 | private Mapper mapper;
35 |
36 | @Autowired
37 | public BookService(BookRepository repo, Mapper mapper) {
38 | this.repo = repo;
39 | this.mapper = mapper;
40 | }
41 |
42 | public Book create(BookRequest request) {
43 | validate(request);
44 |
45 | BookEntity entity = mapper.map(request, BookEntity.class);
46 | entity = repo.save(entity);
47 | Book book = mapper.map(entity, Book.class);
48 |
49 | return book;
50 | }
51 |
52 | public Book read(UUID id) {
53 | BookEntity entity = repo.findOne(id);
54 | return map(entity);
55 | }
56 |
57 | public List read(String title, String author, Genre genre) {
58 | if (title != null && author == null && genre == null) {
59 | return map(repo.findByTitleIgnoreCase(title));
60 | } else if (title != null && author != null && genre == null) {
61 | return map(repo.findByTitleIgnoreCaseAndAuthorIgnoreCase(title,
62 | author));
63 | } else if (title != null && author != null && genre != null) {
64 | return map(
65 | repo.findByTitleIgnoreCaseAndAuthorIgnoreCaseAndGenre(title,
66 | author, genre));
67 | } else if (title == null && author != null && genre == null) {
68 | return map(repo.findByAuthorIgnoreCase(author));
69 | } else if (title == null && author != null && genre != null) {
70 | return map(repo.findByAuthorIgnoreCaseAndGenre(author, genre));
71 | } else if (title != null && author == null && genre != null) {
72 | return map(repo.findByTitleIgnoreCaseAndGenre(title, genre));
73 | } else if (title == null && author == null && genre != null) {
74 | return map(repo.findByGenre(genre));
75 | } else {
76 | throw new InvalidSearchException(
77 | "Specify at least one search critera!");
78 | }
79 | }
80 |
81 | @Transactional
82 | public Book update(UUID id, BookRequest request) {
83 | BookEntity entity = repo.findOne(id);
84 |
85 | if (entity == null) {
86 | throw new DataNotFoundException(
87 | "Book with id " + id + " not found!");
88 | }
89 |
90 | validate(request);
91 | mapper.map(request, entity);
92 | entity = repo.save(entity);
93 |
94 | Book book = mapper.map(entity, Book.class);
95 |
96 | return book;
97 | }
98 |
99 | public void delete(UUID id) {
100 | repo.delete(id);
101 | }
102 |
103 | public void deleteAll() {
104 | repo.deleteAll();
105 | }
106 |
107 | private List map(List entities) {
108 | if (entities == null || entities.isEmpty()) {
109 | throw new DataNotFoundException(
110 | "No books found with the search criteria!");
111 | }
112 |
113 | return entities.stream().map(
114 | r -> mapper.map(r, Book.class)).collect(
115 | Collectors.toList());
116 | }
117 |
118 | private void validate(BookRequest request) {
119 | Assert.notNull(request.getTitle(), "Title can't be null!");
120 | Assert.notNull(request.getAuthor(), "Author can't be null!");
121 | Assert.notNull(request.getGenre(), "Genre can't be null!");
122 | }
123 |
124 | private Book map(BookEntity entity) {
125 | if (entity == null) {
126 | throw new InvalidSearchException("Book not found!");
127 | }
128 |
129 | return mapper.map(entity, Book.class);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/java/com/basaki/example/postgres/spring/util/UuidBeanFactory.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.util;
2 |
3 | import java.util.UUID;
4 | import org.dozer.BeanFactory;
5 |
6 | /**
7 | * {@code UuidBeanFactory} configures Dozer mapper to map UUID fields.
8 | *
9 | *
10 | * @author Indra Basak
11 | * @since 3/18/17
12 | */
13 | public class UuidBeanFactory implements BeanFactory {
14 | @Override
15 | public Object createBean(Object source, Class> sourceClass,
16 | String targetBeanId) {
17 | if (source == null) {
18 | return null;
19 | }
20 |
21 | UUID uuidSrc = (UUID) source;
22 | UUID uuidDest = new UUID(uuidSrc.getMostSignificantBits(),
23 | uuidSrc.getLeastSignificantBits());
24 | return uuidDest;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/resources/config/application.yml:
--------------------------------------------------------------------------------
1 | project:
2 | artifactId: book
3 | name: book
4 | version: 1.0.0-SNAPSHOT
5 | description: Demo project for info endpoint
6 |
7 | # For Spring Actuator /info endpoint
8 | info:
9 | artifact: ${project.artifactId}
10 | name: ${project.name}
11 | description: ${project.description}
12 | version: ${project.version}
13 |
14 | eureka:
15 | instance:
16 | # interval for periodic heartbeat to the registry; default 10
17 | leaseRenewalIntervalInSeconds: 10
18 | client:
19 | enabled: true
20 | registryFetchIntervalSeconds: 5
21 | # by default, Eureka uses the client heartbeat to determine if a client is up. Unless specified otherwise the
22 | # Discovery Client will not propagate the current health check status of the application.
23 | # should only be set in application.yml
24 | healthcheck:
25 | enabled: true
26 | serviceUrl:
27 | defaultZone: ${EUREKA_ZONE}
28 |
29 | # use a HTTP port 8080
30 | server:
31 | port: 8080
32 |
33 | #Postgres persistence
34 | spring:
35 | datasource:
36 | url: jdbc:postgresql://localhost:5432/postgres
37 | username: postgres
38 | password: postgres
39 | platform: POSTGRESQL
40 | testWhileIdle: true
41 | validationQuery: SELECT 1
42 | jpa:
43 | show-sql: true
44 | hibernate:
45 | # ddl-auto: create-drop
46 | dialect: org.hibernate.dialect.PostgreSQLDialect
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/resources/config/bootstrap.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | application:
3 | name: book
4 | cloud:
5 | config:
6 | # uri: ${vcap.services.${PREFIX:}configserver.credentials.uri:http://user:password@localhost:8888}
7 | enabled: false
8 | discovery:
9 | enabled: true
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/main/resources/db/create-db.sql:
--------------------------------------------------------------------------------
1 | DROP SCHEMA IF EXISTS example CASCADE;
2 |
3 | DROP ROLE IF EXISTS owner_example;
4 |
5 | CREATE ROLE owner_example WITH
6 | NOLOGIN
7 | NOSUPERUSER
8 | INHERIT
9 | NOCREATEDB
10 | NOCREATEROLE
11 | NOREPLICATION;
12 |
13 | GRANT owner_example TO postgres;
14 |
15 | CREATE SCHEMA example AUTHORIZATION owner_example;
16 |
17 | GRANT ALL ON SCHEMA example TO owner_example;
18 |
19 | CREATE TYPE example.genre AS ENUM ('DRAMA', 'ROMANCE', 'GUIDE', 'TRAVEL');
20 |
21 | ALTER TYPE example.genre OWNER TO owner_example;
22 |
23 | CREATE TABLE example.books
24 | (
25 | id uuid,
26 | title text NOT NULL,
27 | author text NOT NULL,
28 | genre example.genre NOT NULL,
29 | PRIMARY KEY (id))
30 | WITH (OIDS = FALSE);
31 |
32 | ALTER TABLE example.books OWNER to owner_example;
33 |
--------------------------------------------------------------------------------
/jpa-postgres-spring-service/src/test/java/com/basaki/example/postgres/spring/controller/BookControllerUnitTests.java:
--------------------------------------------------------------------------------
1 | package com.basaki.example.postgres.spring.controller;
2 |
3 | import com.basaki.example.postgres.spring.model.Book;
4 | import com.basaki.example.postgres.spring.model.BookRequest;
5 | import com.basaki.example.postgres.spring.model.Genre;
6 | import com.basaki.example.postgres.spring.service.BookService;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.UUID;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.mockito.InjectMocks;
13 | import org.mockito.Mock;
14 | import org.mockito.MockitoAnnotations;
15 |
16 | import static org.junit.Assert.assertEquals;
17 | import static org.junit.Assert.assertNotNull;
18 | import static org.mockito.Matchers.any;
19 | import static org.mockito.Mockito.doNothing;
20 | import static org.mockito.Mockito.when;
21 |
22 | /**
23 | * {@code BookControllerUnitTests} unit test class for {@code BookController}.
24 | *
25 | *
26 | * @author Indra Basak
27 | * @since 4/15/17
28 | */
29 | public class BookControllerUnitTests {
30 |
31 | @Mock
32 | private BookService service;
33 |
34 | @InjectMocks
35 | private BookController controller;
36 |
37 | @Before
38 | public void setUp() {
39 | MockitoAnnotations.initMocks(this);
40 | }
41 |
42 | @Test
43 | public void testCreate() {
44 | Book book = getBook();
45 | when(service.create(any(BookRequest.class))).thenReturn(book);
46 |
47 | Book returnedObj = controller.create(new BookRequest());
48 | validate(book, returnedObj);
49 | }
50 |
51 | @Test
52 | public void testReadById() {
53 | Book book = getBook();
54 | when(service.read(any(UUID.class))).thenReturn(book);
55 |
56 | Book returnedObj = controller.read(book.getId());
57 | validate(book, returnedObj);
58 | }
59 |
60 | @Test
61 | public void testRead() {
62 | Book book = getBook();
63 | List books = new ArrayList<>();
64 | books.add(book);
65 | when(service.read(any(String.class), any(String.class),
66 | any(Genre.class))).thenReturn(books);
67 |
68 | List returnedObjs =
69 | controller.read("title", "author", Genre.DRAMA);
70 | assertEquals(1, returnedObjs.size());
71 | validate(book, returnedObjs.get(0));
72 | }
73 |
74 | @Test
75 | public void testUpdate() {
76 | Book book = getBook();
77 | when(service.update(any(UUID.class),
78 | any(BookRequest.class))).thenReturn(book);
79 |
80 | Book returnedObj =
81 | controller.update(UUID.randomUUID(), new BookRequest());
82 | validate(book, returnedObj);
83 | }
84 |
85 | @Test
86 | public void testDeleteById() {
87 | doNothing().when(service).delete(any(UUID.class));
88 |
89 | controller.deleteById(UUID.randomUUID());
90 | }
91 |
92 | @Test
93 | public void testDeleteAll() {
94 | doNothing().when(service).deleteAll();
95 |
96 | controller.deleteAll();
97 | }
98 |
99 | private void validate(Book expected, Book actual) {
100 | assertNotNull(expected);
101 | assertNotNull(actual);
102 | assertEquals(expected.getId(), actual.getId());
103 | assertEquals(expected.getTitle(), actual.getTitle());
104 | assertEquals(expected.getGenre(), actual.getGenre());
105 | assertEquals(expected.getAuthor(), actual.getAuthor());
106 | }
107 |
108 | private Book getBook() {
109 | Book book = new Book();
110 | book.setId(UUID.randomUUID());
111 | book.setTitle("Ethan Frome");
112 | book.setGenre(Genre.DRAMA);
113 | book.setAuthor("Edith Wharton");
114 |
115 | return book;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.basaki.example
5 | jpa-postgres-spring
6 | pom
7 | 1.0
8 | JPA Postgres Spring Boot Example
9 |
10 |
11 | 1.8
12 | 1.4.5.RELEASE
13 | Camden.RELEASE
14 | 1.16.8
15 | 2.5.0
16 | 2.0.10-beta
17 | 2.3.2
18 |
19 |
20 |
21 | jpa-postgres-spring-model
22 | jpa-postgres-spring-service
23 |
24 |
25 |
26 |
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-dependencies
31 | ${spring.boot.version}
32 | pom
33 | import
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | org.springframework.boot
42 | spring-boot-starter-web
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-starter-actuator
47 |
48 |
49 | org.springframework.boot
50 | spring-boot-starter-test
51 | test
52 |
53 |
54 |
55 | org.projectlombok
56 | lombok
57 | ${lombok.version}
58 | provided
59 |
60 |
61 | io.springfox
62 | springfox-swagger2
63 | ${swagger.version}
64 |
65 |
66 | io.springfox
67 | springfox-swagger-ui
68 | ${swagger.version}
69 |
70 |
71 |
72 |
73 | junit
74 | junit
75 |
76 |
77 | com.github.tomakehurst
78 | wiremock
79 | ${wiremock.version}
80 | test
81 |
82 |
83 | io.rest-assured
84 | rest-assured
85 | 3.0.0
86 | test
87 |
88 |
89 | io.rest-assured
90 | spring-mock-mvc
91 | 3.0.0
92 | test
93 |
94 |
95 | org.hamcrest
96 | hamcrest-core
97 | 1.3
98 | test
99 |
100 |
101 |
102 |
103 |
104 |
105 | maven-compiler-plugin
106 |
107 | ${java.version}
108 | ${java.version}
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | spring-snapshots
117 | Spring Snapshots
118 | https://repo.spring.io/snapshot
119 |
120 | true
121 |
122 |
123 |
124 | spring-milestones
125 | Spring Milestones
126 | https://repo.spring.io/milestone
127 |
128 | false
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------