├── .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 | ![](./img/postgres.png) 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 | ![](./img/book-swagger.png) 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 | ![](./img/book-post-req.png) 59 | 60 | Here is the response you get back. Please notice the book title and the author gets captitalized before insertion. 61 | ![](./img/book-post-rsp.png) 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 | ![](./img/book-get-rsp.png) 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 | --------------------------------------------------------------------------------