├── .circleci └── config.yml ├── .gitignore ├── README.md ├── pom.xml ├── renovate.json └── src ├── main ├── java │ └── pl │ │ └── piomin │ │ └── samples │ │ └── springboot │ │ └── tips │ │ ├── TipsApp.java │ │ ├── config │ │ └── TipsAppProperties.java │ │ ├── controller │ │ ├── PersonController.java │ │ └── TipController.java │ │ ├── data │ │ ├── model │ │ │ ├── Address.java │ │ │ ├── Contact.java │ │ │ ├── Gender.java │ │ │ ├── Person.java │ │ │ ├── Person2.java │ │ │ └── Tip.java │ │ └── repository │ │ │ ├── PersonRepository.java │ │ │ └── TipRepository.java │ │ ├── exception │ │ └── handler │ │ │ └── TipNotFoundHandler.java │ │ ├── listener │ │ └── ApplicationStartupListener.java │ │ └── service │ │ ├── PersonService.java │ │ └── TipService.java └── resources │ ├── additional.yml │ ├── app.properties │ ├── application.yml │ ├── data.sql │ ├── db │ └── changeLog.sql │ └── global.properties └── test └── java └── pl └── piomin └── samples └── springboot └── tips ├── AppTest.java ├── PersonsControllerTests.java ├── PersonsControllerViaRestClientTests.java ├── PersonsServiceTest.java ├── TipsAppTest.java ├── TipsControllerTest.java └── TipsRepositoryTest.java /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: 'cimg/openjdk:21.0.6' 7 | steps: 8 | - checkout 9 | - run: 10 | name: Analyze on SonarCloud 11 | command: mvn verify sonar:sonar 12 | 13 | executors: 14 | jdk: 15 | docker: 16 | - image: 'cimg/openjdk:21.0.6' 17 | 18 | orbs: 19 | maven: circleci/maven@2.0.0 20 | 21 | workflows: 22 | maven_test: 23 | jobs: 24 | - maven/test: 25 | executor: jdk 26 | - build: 27 | context: SonarCloud -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Spring Boot Tips & Tricks Demo Project [![Twitter](https://img.shields.io/twitter/follow/piotr_minkowski.svg?style=social&logo=twitter&label=Follow%20Me)](https://twitter.com/piotr_minkowski) 2 | 3 | [![CircleCI](https://circleci.com/gh/piomin/spring-boot-tips.svg?style=svg)](https://circleci.com/gh/piomin/spring-boot-tips) 4 | 5 | [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/dashboard?id=piomin_spring-boot-tips) 6 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=piomin_spring-boot-tips&metric=bugs)](https://sonarcloud.io/dashboard?id=piomin_spring-boot-tips) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=piomin_spring-boot-tips&metric=coverage)](https://sonarcloud.io/dashboard?id=piomin_spring-boot-tips) 8 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=piomin_spring-boot-tips&metric=ncloc)](https://sonarcloud.io/dashboard?id=piomin_spring-boot-tips) 9 | 10 | This repository shows the most interesting tips and tricks with the Spring Boot framework. 11 | 12 | A detailed description can be found here: [Spring Boot Tips, Tricks and Techniques](https://piotrminkowski.com/2021/01/13/spring-boot-tips-tricks-and-techniques/) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | org.springframework.boot 9 | spring-boot-starter-parent 10 | 3.5.0 11 | 12 | 13 | pl.piomin.samples 14 | spring-boot-tips 15 | 1.3-SNAPSHOT 16 | 17 | 18 | 21 19 | piomin_spring-boot-tips 20 | piomin 21 | https://sonarcloud.io 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-validation 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-data-jpa 40 | 41 | 42 | com.h2database 43 | h2 44 | runtime 45 | 46 | 47 | org.projectlombok 48 | lombok 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-starter-test 53 | test 54 | 55 | 56 | org.instancio 57 | instancio-junit 58 | 5.4.1 59 | 60 | 61 | net.datafaker 62 | datafaker 63 | 2.4.3 64 | 65 | 66 | org.liquibase 67 | liquibase-core 68 | 69 | 70 | 71 | 72 | 73 | 74 | com.blazebit 75 | blaze-persistence-bom 76 | 1.6.15 77 | pom 78 | import 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-maven-plugin 88 | 89 | 90 | 91 | build-info 92 | 93 | 94 | 95 | 96 | 97 | org.jacoco 98 | jacoco-maven-plugin 99 | 0.8.13 100 | 101 | 102 | 103 | prepare-agent 104 | 105 | 106 | 107 | report 108 | test 109 | 110 | report 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base",":dependencyDashboard" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": true 10 | } 11 | ], 12 | "prCreation": "not-pending" 13 | } -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/TipsApp.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 11 | import org.springframework.boot.info.BuildProperties; 12 | import org.springframework.boot.info.GitProperties; 13 | import pl.piomin.samples.springboot.tips.config.TipsAppProperties; 14 | import pl.piomin.samples.springboot.tips.controller.TipController; 15 | import pl.piomin.samples.springboot.tips.data.model.Person2; 16 | 17 | import java.util.Optional; 18 | 19 | @SpringBootApplication 20 | @EnableConfigurationProperties({TipsAppProperties.class, Person2.class}) 21 | public class TipsApp { 22 | 23 | private static final Logger LOG = LoggerFactory.getLogger(TipsApp.class); 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(TipsApp.class, args); 27 | } 28 | 29 | @Autowired 30 | private TipsAppProperties properties; 31 | @Autowired 32 | private Optional git; 33 | @Autowired 34 | private Optional buildProperties; 35 | 36 | @PostConstruct 37 | void init() { 38 | LOG.info("properties: {}", properties); 39 | if (git.isPresent()) { 40 | LOG.info("Git data: {} {}", git.get().getCommitId(), git.get().getCommitTime()); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/config/TipsAppProperties.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.config; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties("app") 6 | public class TipsAppProperties { 7 | 8 | private final String name; 9 | private final String version; 10 | 11 | public TipsAppProperties(String name, String version) { 12 | this.name = name; 13 | this.version = version; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public String getVersion() { 21 | return version; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "TipsAppProperties{" + 27 | "name='" + name + '\'' + 28 | ", version='" + version + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/controller/PersonController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.controller; 2 | 3 | import jakarta.validation.Valid; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.web.bind.annotation.*; 7 | import pl.piomin.samples.springboot.tips.data.model.Person; 8 | import pl.piomin.samples.springboot.tips.data.repository.PersonRepository; 9 | 10 | @RestController 11 | @RequestMapping("/persons") 12 | public class PersonController { 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(PersonController.class); 15 | 16 | private PersonRepository repository; 17 | 18 | public PersonController(PersonRepository repository) { 19 | this.repository = repository; 20 | } 21 | 22 | @PostMapping 23 | public Person add(@Valid @RequestBody Person person) { 24 | return repository.save(person); 25 | } 26 | 27 | @GetMapping("/{id}") 28 | public Person findById(@PathVariable("id") Long id, @RequestHeader("X-Version") String version) { 29 | LOG.info("FindById: id={}, version={}", id, version); 30 | return repository.findById(id).orElseThrow(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/controller/TipController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import org.springframework.web.server.ResponseStatusException; 11 | import pl.piomin.samples.springboot.tips.data.model.Tip; 12 | import pl.piomin.samples.springboot.tips.data.repository.TipRepository; 13 | 14 | import java.util.NoSuchElementException; 15 | 16 | @RestController 17 | @RequestMapping("/tips") 18 | public class TipController { 19 | 20 | private static final Logger LOG = LoggerFactory.getLogger(TipController.class); 21 | private TipRepository repository; 22 | 23 | public TipController(TipRepository repository) { 24 | this.repository = repository; 25 | } 26 | 27 | @GetMapping 28 | public Iterable findAll() { 29 | return repository.findAll(); 30 | } 31 | 32 | @GetMapping("/{id}") 33 | public Tip findById(@PathVariable("id") Long id) { 34 | try { 35 | return repository.findById(id).orElseThrow(); 36 | } catch (NoSuchElementException e) { 37 | LOG.error("Not found", e); 38 | throw new ResponseStatusException(HttpStatus.NO_CONTENT); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/model/Address.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.model; 2 | 3 | import jakarta.persistence.Embeddable; 4 | 5 | import java.util.Objects; 6 | 7 | @Embeddable 8 | public class Address { 9 | private String country; 10 | private String city; 11 | private String street; 12 | private int houseNumber; 13 | private int flatNumber; 14 | 15 | public Address() { 16 | 17 | } 18 | 19 | public Address(String country, String city, String street, int houseNumber, int flatNumber) { 20 | this.country = country; 21 | this.city = city; 22 | this.street = street; 23 | this.houseNumber = houseNumber; 24 | this.flatNumber = flatNumber; 25 | } 26 | 27 | public String getCountry() { 28 | return country; 29 | } 30 | 31 | public void setCountry(String country) { 32 | this.country = country; 33 | } 34 | 35 | public String getCity() { 36 | return city; 37 | } 38 | 39 | public void setCity(String city) { 40 | this.city = city; 41 | } 42 | 43 | public String getStreet() { 44 | return street; 45 | } 46 | 47 | public void setStreet(String street) { 48 | this.street = street; 49 | } 50 | 51 | public int getHouseNumber() { 52 | return houseNumber; 53 | } 54 | 55 | public void setHouseNumber(int houseNumber) { 56 | this.houseNumber = houseNumber; 57 | } 58 | 59 | public int getFlatNumber() { 60 | return flatNumber; 61 | } 62 | 63 | public void setFlatNumber(int flatNumber) { 64 | this.flatNumber = flatNumber; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return "Address{" + 70 | "country='" + country + '\'' + 71 | ", city='" + city + '\'' + 72 | ", street='" + street + '\'' + 73 | ", houseNumber=" + houseNumber + 74 | ", flatNumber=" + flatNumber + 75 | '}'; 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (o == null || getClass() != o.getClass()) return false; 81 | 82 | Address address = (Address) o; 83 | return houseNumber == address.houseNumber && flatNumber == address.flatNumber && Objects.equals(country, address.country) && Objects.equals(city, address.city) && Objects.equals(street, address.street); 84 | } 85 | 86 | @Override 87 | public int hashCode() { 88 | int result = Objects.hashCode(country); 89 | result = 31 * result + Objects.hashCode(city); 90 | result = 31 * result + Objects.hashCode(street); 91 | result = 31 * result + houseNumber; 92 | result = 31 * result + flatNumber; 93 | return result; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/model/Contact.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.model; 2 | 3 | import jakarta.annotation.Nullable; 4 | import jakarta.persistence.Embeddable; 5 | import jakarta.validation.constraints.Email; 6 | import jakarta.validation.constraints.Pattern; 7 | 8 | @Embeddable 9 | public class Contact { 10 | 11 | @Email 12 | private String email; 13 | @Pattern(regexp="^\\+\\d{1,3} \\d{3} \\d{3} \\d{3}$", message = "Invalid phone number format") 14 | @Nullable 15 | private String phoneNumber; 16 | 17 | public String getEmail() { 18 | return email; 19 | } 20 | 21 | public void setEmail(String email) { 22 | this.email = email; 23 | } 24 | 25 | public String getPhoneNumber() { 26 | return phoneNumber; 27 | } 28 | 29 | public void setPhoneNumber(String phoneNumber) { 30 | this.phoneNumber = phoneNumber; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Contact{" + 36 | "email='" + email + '\'' + 37 | ", phoneNumber='" + phoneNumber + '\'' + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/model/Gender.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.model; 2 | 3 | public enum Gender { 4 | MALE, FEMALE; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/model/Person.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.model; 2 | 3 | import jakarta.annotation.Nullable; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.validation.Valid; 9 | import jakarta.validation.constraints.Email; 10 | import jakarta.validation.constraints.NotBlank; 11 | import jakarta.validation.constraints.NotNull; 12 | import jakarta.validation.constraints.Null; 13 | import lombok.AllArgsConstructor; 14 | import lombok.Data; 15 | import lombok.NoArgsConstructor; 16 | 17 | import java.util.Objects; 18 | 19 | @Entity 20 | @Data 21 | public class Person { 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | private Long id; 25 | private String name; 26 | private int age; 27 | private Gender gender; 28 | private Address address; 29 | @Valid 30 | private Contact contact; 31 | 32 | public Person() { 33 | } 34 | 35 | public Person(Long id, String name, int age, Gender gender, Address address, Contact contact) { 36 | this.id = id; 37 | this.name = name; 38 | this.age = age; 39 | this.gender = gender; 40 | this.address = address; 41 | this.contact = contact; 42 | } 43 | 44 | public Long getId() { 45 | return id; 46 | } 47 | 48 | public void setId(Long id) { 49 | this.id = id; 50 | } 51 | 52 | public String getName() { 53 | return name; 54 | } 55 | 56 | public void setName(String name) { 57 | this.name = name; 58 | } 59 | 60 | public int getAge() { 61 | return age; 62 | } 63 | 64 | public void setAge(int age) { 65 | this.age = age; 66 | } 67 | 68 | public Gender getGender() { 69 | return gender; 70 | } 71 | 72 | public void setGender(Gender gender) { 73 | this.gender = gender; 74 | } 75 | 76 | public Address getAddress() { 77 | return address; 78 | } 79 | 80 | public void setAddress(Address address) { 81 | this.address = address; 82 | } 83 | 84 | public Contact getContact() { 85 | return contact; 86 | } 87 | 88 | public void setContact(Contact contact) { 89 | this.contact = contact; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return "Person{" + 95 | "id=" + id + 96 | ", name='" + name + '\'' + 97 | ", age=" + age + 98 | ", gender=" + gender + 99 | ", address=" + address + 100 | ", contact=" + contact + 101 | '}'; 102 | } 103 | 104 | @Override 105 | public boolean equals(Object o) { 106 | if (o == null || getClass() != o.getClass()) return false; 107 | 108 | Person person = (Person) o; 109 | return Objects.equals(id, person.id); 110 | } 111 | 112 | @Override 113 | public int hashCode() { 114 | return Objects.hashCode(id); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/model/Person2.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.model; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties(prefix = "person") 6 | public record Person2(String name, int age) { } 7 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/model/Tip.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.model; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.GeneratedValue; 5 | import jakarta.persistence.GenerationType; 6 | import jakarta.persistence.Id; 7 | 8 | 9 | @Entity 10 | public class Tip { 11 | @Id 12 | @GeneratedValue(strategy = GenerationType.IDENTITY) 13 | private Long id; 14 | private String title; 15 | private String description; 16 | 17 | public Tip() { 18 | } 19 | 20 | public Tip(Long id, String title, String description) { 21 | this.id = id; 22 | this.title = title; 23 | this.description = description; 24 | } 25 | 26 | public Long getId() { 27 | return id; 28 | } 29 | 30 | public void setId(Long id) { 31 | this.id = id; 32 | } 33 | 34 | public String getTitle() { 35 | return title; 36 | } 37 | 38 | public void setTitle(String title) { 39 | this.title = title; 40 | } 41 | 42 | public String getDescription() { 43 | return description; 44 | } 45 | 46 | public void setDescription(String description) { 47 | this.description = description; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "Tip{" + 53 | "id=" + id + 54 | ", title='" + title + '\'' + 55 | ", description='" + description + '\'' + 56 | '}'; 57 | } 58 | 59 | @Override 60 | public boolean equals(Object o) { 61 | if (o == null || getClass() != o.getClass()) return false; 62 | 63 | Tip tip = (Tip) o; 64 | return id.equals(tip.id); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return id.hashCode(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/repository/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import pl.piomin.samples.springboot.tips.data.model.Person; 5 | 6 | import java.util.List; 7 | 8 | public interface PersonRepository extends CrudRepository { 9 | 10 | List findByAddressCity(String city); 11 | List findByAgeGreaterThan(int age); 12 | 13 | private void y() { 14 | 15 | } 16 | 17 | default void x() { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/data/repository/TipRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.data.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import pl.piomin.samples.springboot.tips.data.model.Tip; 5 | 6 | public interface TipRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/exception/handler/TipNotFoundHandler.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.exception.handler; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ControllerAdvice; 5 | import org.springframework.web.bind.annotation.ExceptionHandler; 6 | import org.springframework.web.bind.annotation.ResponseStatus; 7 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 8 | 9 | import java.util.NoSuchElementException; 10 | 11 | @ControllerAdvice 12 | public class TipNotFoundHandler { 13 | 14 | @ResponseStatus(HttpStatus.NO_CONTENT) 15 | @ExceptionHandler(NoSuchElementException.class) 16 | public void handleNotFound() { 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/listener/ApplicationStartupListener.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.listener; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.context.event.ApplicationReadyEvent; 5 | import org.springframework.context.ApplicationListener; 6 | import org.springframework.context.annotation.Profile; 7 | import org.springframework.stereotype.Component; 8 | import pl.piomin.samples.springboot.tips.data.model.Tip; 9 | import pl.piomin.samples.springboot.tips.data.repository.TipRepository; 10 | 11 | @Profile("demo") 12 | @Component 13 | public class ApplicationStartupListener implements ApplicationListener { 14 | 15 | @Autowired 16 | private TipRepository repository; 17 | 18 | public void onApplicationEvent(final ApplicationReadyEvent event) { 19 | repository.save(new Tip(null, "Test1", "Desc1")); 20 | repository.save(new Tip(null, "Test2", "Desc2")); 21 | repository.save(new Tip(null, "Test3", "Desc3")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/service/PersonService.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | import pl.piomin.samples.springboot.tips.data.model.Person; 5 | import pl.piomin.samples.springboot.tips.data.model.Tip; 6 | import pl.piomin.samples.springboot.tips.data.repository.PersonRepository; 7 | import pl.piomin.samples.springboot.tips.data.repository.TipRepository; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class PersonService { 13 | 14 | private PersonRepository repository; 15 | 16 | public PersonService(PersonRepository repository) { 17 | this.repository = repository; 18 | } 19 | 20 | public List findAll() { 21 | return (List) repository.findAll(); 22 | } 23 | 24 | public Person addPerson(Person person) { 25 | return repository.save(person); 26 | } 27 | 28 | public List addPersons(List person) { 29 | return (List) repository.saveAll(person); 30 | } 31 | 32 | public Person findById(Long id) { 33 | return repository.findById(id).orElseThrow(); 34 | } 35 | 36 | public List findByCity(String city) { 37 | return repository.findByAddressCity(city); 38 | } 39 | 40 | public List findAllGreaterThanAge(int age) { 41 | return repository.findByAgeGreaterThan(age); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/pl/piomin/samples/springboot/tips/service/TipService.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | import pl.piomin.samples.springboot.tips.data.model.Tip; 5 | import pl.piomin.samples.springboot.tips.data.repository.TipRepository; 6 | 7 | import java.util.List; 8 | 9 | @Service 10 | public class TipService { 11 | 12 | private TipRepository repository; 13 | 14 | public TipService(TipRepository repository) { 15 | this.repository = repository; 16 | } 17 | 18 | public List findAll() { 19 | return (List) repository.findAll(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/additional.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: tips-x 3 | version: 1.1 -------------------------------------------------------------------------------- /src/main/resources/app.properties: -------------------------------------------------------------------------------- 1 | property1=App specific property1 2 | 3 | person.name = Piotr Minkowski 4 | person.age = 18 5 | 6 | spring.liquibase.change-log = classpath:db/changeLog.sql -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | app: 2 | name: tips 3 | version: 1.0 4 | 5 | management: 6 | endpoints: 7 | web: 8 | exposure: 9 | include: "*" 10 | 11 | spring.liquibase.change-log: classpath:db/changeLog.sql 12 | 13 | spring: 14 | config: 15 | additional-location: additional2.yml 16 | location: additional2.yml 17 | main: 18 | lazy-initialization: true -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into tip(id, title, description) values (100, 'Test1', 'Desc1'); 2 | insert into tip(id, title, description) values (200, 'Test2', 'Desc2'); 3 | insert into tip(id, title, description) values (300, 'Test3', 'Desc3'); 4 | insert into person(name, age) values ('Test1', 10); 5 | insert into person(name, age) values ('Test2', 20); 6 | insert into person(name, age) values ('Test3', 30); 7 | insert into person(name, age) values ('Test4', 40); -------------------------------------------------------------------------------- /src/main/resources/db/changeLog.sql: -------------------------------------------------------------------------------- 1 | --liquibase formatted sql 2 | 3 | --changeset piomin:1 4 | create table tip ( 5 | id int auto_increment, 6 | title varchar(255), 7 | description varchar(255) 8 | ); 9 | create table person ( 10 | id serial primary key, 11 | name varchar(255), 12 | age int, 13 | gender varchar(20), 14 | country varchar(50), 15 | city varchar(50), 16 | street varchar(50), 17 | house_number int, 18 | flat_number int, 19 | email varchar(50), 20 | phone_number varchar(20) 21 | ); -------------------------------------------------------------------------------- /src/main/resources/global.properties: -------------------------------------------------------------------------------- 1 | property1=Global property1 2 | property2=Global property2 -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/AppTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.web.server.LocalServerPort; 7 | 8 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 9 | public class AppTest { 10 | 11 | @LocalServerPort 12 | private int port; 13 | 14 | @Test 15 | void test() { 16 | Assertions.assertTrue(port > 0); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/PersonsControllerTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import net.datafaker.Faker; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.boot.test.web.client.TestRestTemplate; 9 | import pl.piomin.samples.springboot.tips.data.model.Address; 10 | import pl.piomin.samples.springboot.tips.data.model.Contact; 11 | import pl.piomin.samples.springboot.tips.data.model.Gender; 12 | import pl.piomin.samples.springboot.tips.data.model.Person; 13 | 14 | import java.util.Locale; 15 | import java.util.Random; 16 | 17 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 18 | public class PersonsControllerTests { 19 | 20 | @Autowired 21 | private TestRestTemplate restTemplate; 22 | 23 | @Test 24 | void add() { 25 | Faker faker = new Faker(Locale.of("pl"), new Random(0)); 26 | Contact contact = new Contact(); 27 | contact.setEmail(faker.internet().emailAddress()); 28 | contact.setPhoneNumber(faker.phoneNumber().cellPhoneInternational()); 29 | Address address = new Address(); 30 | address.setCity(faker.address().city()); 31 | address.setCountry(faker.address().country()); 32 | address.setStreet(faker.address().streetName()); 33 | int number = Integer.parseInt(faker.address().streetAddressNumber()); 34 | address.setHouseNumber(number); 35 | number = Integer.parseInt(faker.address().buildingNumber()); 36 | address.setFlatNumber(number); 37 | Person person = new Person(); 38 | person.setName(faker.name().fullName()); 39 | person.setContact(contact); 40 | person.setAddress(address); 41 | person.setGender(Gender.valueOf(faker.gender().binaryTypes().toUpperCase())); 42 | person.setAge(faker.number().numberBetween(18, 65)); 43 | System.out.println(person); 44 | 45 | person = restTemplate.postForObject("/persons", person, Person.class); 46 | Assertions.assertNotNull(person); 47 | Assertions.assertNotNull(person.getId()); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/PersonsControllerViaRestClientTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.boot.test.context.TestConfiguration; 7 | import org.springframework.boot.test.web.server.LocalServerPort; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.web.client.RestClient; 10 | import pl.piomin.samples.springboot.tips.data.model.Person; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 15 | public class PersonsControllerViaRestClientTests { 16 | 17 | @TestConfiguration 18 | public static class RestClientConfiguration { 19 | @LocalServerPort 20 | private Integer port; 21 | 22 | @Bean 23 | public RestClient restClient(RestClient.Builder builder) { 24 | return builder.baseUrl("http://localhost:" + port).build(); 25 | } 26 | } 27 | 28 | @Autowired 29 | RestClient client; 30 | 31 | @Test 32 | void shouldGetPerson() { 33 | Person person = client.get() 34 | .uri("/persons/{id}", 1L) 35 | .header("X-Version", "v1") 36 | .retrieve() 37 | .body(Person.class); 38 | assertNotNull(person); 39 | assertNotNull(person.getId()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/PersonsServiceTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import org.instancio.Instancio; 4 | import org.instancio.Select; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 11 | import org.springframework.context.annotation.Import; 12 | import pl.piomin.samples.springboot.tips.data.model.Address; 13 | import pl.piomin.samples.springboot.tips.data.model.Contact; 14 | import pl.piomin.samples.springboot.tips.data.model.Person; 15 | import pl.piomin.samples.springboot.tips.service.PersonService; 16 | import pl.piomin.samples.springboot.tips.service.TipService; 17 | 18 | import java.util.List; 19 | 20 | @DataJpaTest(showSql = false) 21 | @Import({TipService.class, PersonService.class}) 22 | public class PersonsServiceTest { 23 | 24 | private final static Logger LOG = LoggerFactory.getLogger(PersonsServiceTest.class); 25 | 26 | @Autowired 27 | private PersonService personService; 28 | 29 | @Test 30 | void testFindAll() { 31 | List persons = personService.findAll(); 32 | Assertions.assertEquals(4, persons.size()); 33 | } 34 | 35 | @Test 36 | void addAndGet() { 37 | Person person = Instancio.of(Person.class) 38 | .ignore(Select.field(Person::getId)) 39 | .generate(Select.field(Contact::getPhoneNumber), gen -> gen.text().pattern("+#d#d #d#d#d #d#d#d #d#d#d")) 40 | .generate(Select.field(Contact::getEmail), gen -> gen.text().pattern("#c@#c.com")) 41 | .create(); 42 | person = personService.addPerson(person); 43 | Assertions.assertNotNull(person.getId()); 44 | person = personService.findById(person.getId()); 45 | Assertions.assertNotNull(person); 46 | Assertions.assertNotNull(person.getAddress()); 47 | } 48 | 49 | @Test 50 | void addListAndGet() { 51 | final int numberOfObjects = 5; 52 | final String city = "Warsaw"; 53 | List persons = Instancio.ofList(Person.class) 54 | .size(numberOfObjects) 55 | .ignore(Select.field(Person::getId)) 56 | .generate(Select.field(Contact::getPhoneNumber), gen -> gen.text().pattern("+#d#d #d#d#d #d#d#d #d#d#d")) 57 | .generate(Select.field(Contact::getEmail), gen -> gen.text().pattern("#c@#c.com")) 58 | .set(Select.field(Address::getCity), city) 59 | .create(); 60 | personService.addPersons(persons); 61 | persons = personService.findByCity(city); 62 | Assertions.assertEquals(numberOfObjects, persons.size()); 63 | } 64 | 65 | @Test 66 | void addGeneratorAndGet() { 67 | List persons = Instancio.ofList(Person.class) 68 | .size(100) 69 | .ignore(Select.field(Person::getId)) 70 | .generate(Select.field(Person::getAge), gen -> gen.ints().range(18, 65)) 71 | .generate(Select.field(Contact::getPhoneNumber), gen -> gen.text().pattern("+#d#d #d#d#d #d#d#d #d#d#d")) 72 | .generate(Select.field(Contact::getEmail), gen -> gen.text().pattern("#c@#c.com")) 73 | .create(); 74 | personService.addPersons(persons); 75 | persons = personService.findAllGreaterThanAge(40); 76 | Assertions.assertTrue(persons.size() > 0); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/TipsAppTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.springboot.tips.data.model.Person2; 9 | 10 | @SpringBootTest(properties = { 11 | "spring.config.location=classpath:/global.properties,classpath:/app.properties" 12 | }) 13 | public class TipsAppTest { 14 | 15 | @Value("${property1}") 16 | private String property1; 17 | @Value("${property2}") 18 | private String property2; 19 | @Autowired 20 | private Person2 person2; 21 | 22 | @Test 23 | void testProperties() { 24 | Assertions.assertEquals("App specific property1", property1); 25 | Assertions.assertEquals("Global property2", property2); 26 | } 27 | 28 | @Test 29 | void testConfigurationProperties() { 30 | Assertions.assertEquals("Piotr Minkowski", person2.name()); 31 | Assertions.assertEquals(18, person2.age()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/TipsControllerTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 7 | import org.springframework.context.annotation.Import; 8 | import pl.piomin.samples.springboot.tips.data.model.Tip; 9 | import pl.piomin.samples.springboot.tips.service.PersonService; 10 | import pl.piomin.samples.springboot.tips.service.TipService; 11 | 12 | import java.util.List; 13 | 14 | @DataJpaTest(showSql = false) 15 | @Import({TipService.class, PersonService.class}) 16 | public class TipsControllerTest { 17 | 18 | @Autowired 19 | private TipService tipService; 20 | 21 | @Test 22 | void testFindAll() { 23 | List tips = tipService.findAll(); 24 | Assertions.assertEquals(3, tips.size()); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/pl/piomin/samples/springboot/tips/TipsRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.springboot.tips; 2 | 3 | import org.junit.jupiter.api.*; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.transaction.annotation.Transactional; 7 | import pl.piomin.samples.springboot.tips.data.model.Tip; 8 | import pl.piomin.samples.springboot.tips.data.repository.TipRepository; 9 | 10 | import java.util.List; 11 | 12 | @SpringBootTest 13 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 14 | @Transactional 15 | public class TipsRepositoryTest { 16 | 17 | @Autowired 18 | private TipRepository tipRepository; 19 | 20 | @Test 21 | @Order(1) 22 | public void testAdd() { 23 | Tip tip = tipRepository.save(new Tip(null, "Tip1", "Desc1")); 24 | Assertions.assertNotNull(tip); 25 | } 26 | 27 | @Test 28 | @Order(2) 29 | public void testFindAll() { 30 | Iterable tips = tipRepository.findAll(); 31 | Assertions.assertEquals(3, ((List) tips).size()); 32 | } 33 | } 34 | --------------------------------------------------------------------------------