Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building service with Java 8 and Spring Boot

Building service with Java 8 and Spring Boot

A short introduction to Java 8 and Spring Boot, also covering topics like JPA, Spring Data and Project Lombok.

Torsten Heinrich

January 19, 2018
Tweet

More Decks by Torsten Heinrich

Other Decks in Technology

Transcript

  1. 3 Agenda 1. Java 8 2. Spring Boot 3. JPA

    (Java Persistence API) and Spring Data 4. Project Lombok 5. Learnings 6. Q&A
  2. 5 Java 8 • Java 8 is around since 2014

    • The current version is 8u152 • No more public updates after September 2018 • Introduced a lot of new features, some originally planned for Java 7 • Interface default and static methods • Lambda expressions • Stream API • DateTime API • forEach
  3. 6 Interface default and static methods • Before Java 8,

    only methods declarations were possible in interfaces • Introducing a new method in an interface forced all implementing classes to create an implementation • Default and static method solve this problem • Declared by using the default keyword
  4. 7 Interface default and static methods public interface Installable {

    default Boolean canInstall() { return true; } } public interface Runnable { default Boolean canRun() { return true; } } public class App implements Installable, Runnable { } public class Service implements Installable, Runnable { @Override public Boolean canInstall() { return false; } }
  5. 8 Lambda expressions • Represent a one method interface using

    a expression • They replace anonymous inner classes • Lambda expressions consist of three parts: • An argument list, e.g. (int x, int y) • An arrow token, e.g. -> • A body, e.g. x * y • The list of arguments can be empty • Method expressions are lambda expressions that refer to a method by name
  6. 9 Lambda expressions List<App> apps = new ArrayList<>(); apps.add(new App(1));

    apps.add(new App(3)); apps.add(new App(2)); apps.add(new App(5)); apps.add(new App(4)); // sorts the apps ascending by release apps.sort((App a1, App a2) -> a1.release().compareTo(a2.release())); apps.sort(Comparator.comparing(App::release));
  7. 10 Lambda expressions public class App extends AbstractPersistentActor { @Override

    public Receive createReceive() { return receiveBuilder() .match(AppCommand.CreateApp.class, c -> this.when(c)) .match(AppCommand.InstallApp.class, this::when) .build(); } private void when(AppCommand.CreateApp command) { } private void when(AppCommand.InstallApp command) { } }
  8. 11 Stream API • Streams allow you to process data

    in a declarative way • A stream doesn’t have a fixed size, it can produce elements on demand • Can be found in the java.util.stream package • Simplifies multithreading using the parallelStream() method to run operations in parallel • Allow intermediate operations (iterate, filter, map, match) and terminal operations (reduce, collect) • It’s possible to create streams from pretty much everything (Arrays, Collections)
  9. 12 Stream API List<App> apps = new ArrayList<>(); apps.add(new App("one",

    1)); apps.add(new App("three", 3)); apps.add(new App("two", 2)); apps.add(new App("five", 5)); apps.add(new App("four", 4)); List<String> names = apps .stream() .filter(a -> 1 == a.release() % 2) .sorted(Comparator.comparing(App::release).reversed()) .map(App::name) .collect(Collectors.toList());
  10. 13 DateTime API • Introduced with JSR 310, provide better

    support for Date and Time handling • Immutability to guarantee thread-safety • Following a DDD approach • Classes for Date and Time are strictly separated and the API is much more concise • Support for different calendaring system that differ from ISO-8601
  11. 14 DateTime API // local means no time zone information

    val now = LocalDateTime.now(); val today = LocalDate.now(); val yesterday1 = LocalDate.parse("2018-01-18"); val yesterday2 = LocalDate.now().minus(Period.ofDays(1)); val tomorrow = LocalDate.now().plus(Period.ofDays(1)); val newYork = ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("America/New_York")); val day = Duration.between(today, yesterday1);
  12. 15 forEach List<App> apps = new ArrayList<>(); apps.add(new App(1)); apps.add(new

    App(3)); apps.add(new App(2)); apps.add(new App(5)); apps.add(new App(4)); for (App app : apps) { System.out.println(app.release()); } apps.forEach(a -> System.out.println(a.release()));
  13. 17 Spring Boot • Takes an opinionated view of building

    production-ready Spring applications. Spring Boot favours convention over configuration and is designed to get you up and running as quickly as possible. • The project is around since 2014 • The current version is 1.5.9 • Convention over configuration approach • Suitable for REST-based micro services and rapid prototyping • Annotations, annotations, annotations to remove the XML configuration and automate whenever possible
  14. 18 Spring Boot @SpringBootApplication public class Application { public static

    void main(String[] args) { SpringApplication.run(Application.class, args); } }
  15. 19 Configuration • With spring-boot-starter, all configuration can be put

    in src/main/resources • It’s possible to use .properties, .conf or .yaml files • Standard XML configuration, like Log4J, is parsed as well • Profile-specific configuration can also be defined in application-{profile}.properties • The current profile can be set in various ways: • Using spring.profiles.active=dev,psql in application.properties • As a JVM system parameter using -Dspring.profiles.active=dev,psql • As a environment variable using EXPORT spring_profiles_active=dev • Configuration can be profile-specific by using the @Profile annotation
  16. 20 Configuration spring: datasource: url: "jdbc:postgresql://${DB_HOST}:${DB_PORT}/${APPLICATION_NAME}_${BRANCH_NAME}" username: "${DB_USER}" password: "${DB_PASSWORD}"

    driver-class-name: "org.postgresql.Driver" jpa: hibernate: ddl-auto: validate server: port: ${APP_PORT} app: name: “App API" version: 1.0
  17. 21 Dependency Injection • Uses Spring Beans for all managed

    objects inside the IoC container • Use @Configuration and @Bean to create or overwrite global components • Use @Component (or @Controller, @Repository, @Service etc.) for application components • When using custom folder structures, make sure to use @ComponentScan • To inject objects into other components, use the @Autowired annotation • Works for one constructor, fields or setter methods
  18. 22 Dependency Injection @Configuration @ComponentScan(basePackages = {"com.trivago.stdio"}) public class AppConfiguration

    { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); return mapper; } } @Repository public interface AppRepository extends CrudRepository<App, UUID> { } public class AppService { private final AppRepository appRepository; public AppService(@Autowired AppRepository appRepository) { this.appRepository = appRepository; } }
  19. 23 Controller • Enabled by annotating a class with @Controller

    or @RestController • @Controller should when returning (HTML) views • @RestController is @Controller plus @ResponseBody • Should be used when returning (JSON) resources • Can be annotated with @RequestMapping to define controller-wide parameters • Can be annotated with @Validated to enable validation for all request parameters and bodies that are constrained
  20. 24 Controller @RestController @RequestMapping(path = "/apps") @Validated public class AppController

    { private final AppRepository appRepository; public AppController(AppRepository appRepository) { this.appRepository = appRepository; } }
  21. 25 Requests • @RequestMapping maps requests to controller actions •

    Defines the HTTP method, parameters, headers and media types required for matching an action • Use @GetMapping, @PostMapping or @DeleteMapping for better readability • Use @ResponseStatus to define the default status code for an action • Use @PathVariable to bind URI parameters • Use @RequestBody to bind POST bodies to models • Uses Jackson by default to map requests/responses from and to JSON
  22. 26 Requests @RestController @RequestMapping(path = "/apps") @Validated public class AppController

    { @GetMapping(path = "/{appId}") @ResponseStatus(HttpStatus.OK) public App getApp(@PathVariable UUID appId) { return new App(); } @PostMapping @ResponseStatus(HttpStatus.CREATED) public App createApp(@RequestBody App app) { return new App(); } @DeleteMapping(path = "/{appId}") @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteApp(@PathVariable UUID appId) { } }
  23. 27 Exceptions • The @ControllerAdvice annotation allows single, global error

    handling components • Introduced in Spring 3.2 • Define a handler method with @ExceptionHandler method that matches the exception type and returns an HTTP/JSON response • The response can be fully customised • It’s possible to map several exceptions to the same handler
  24. 28 Exceptions public class ApiErrorException extends RuntimeException { public ApiErrorException(String

    message) { super(message); } } public interface ApiErrorAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity<Problem> handleApiError( final ApiErrorException exception, final NativeWebRequest request ) { return create(Status.INTERNAL_SERVER_ERROR, exception, request); } } @ControllerAdvice public class ExceptionHandling implements ApiErrorAdviceTrait { }
  25. 30 JPA (Java Persistence API) • The Java Persistence API

    provides Java developers with an object/relational mapping facility for managing relational data in Java applications. • The specification is around since 2006 • The current version is 2.1, introduced in 2013 • It's a specification that describes the management of relational data in the Java platform • Consists of four different parts: • The API itself (javax.persistence package) • The JPQL (Java Persistence Query Language) • The Java Persistence Criteria API • Object/relational mapping metadata (lots of annotations)
  26. 31 JPA (Java Persistence API) • There are various implementations:

    • Hibernate • EclipseLink • ObjectDB • Apache OpenJPA • Spring Data makes it easier to work with different databases (SQL, NoSQL) • Spring Data JPA sits on top of Hibernate and JPA
  27. 32 Repositories • All the repositories are interfaces by default

    • Require the @EnableJpaRepositories annotation to generate proxy classes • Basic interface is Repository<T, ID extends Serializable> interface • Entity repositories should extend CrudRepository, JpaRepository etc. • Offer basic functionality: • <S extends T> S save(S entity) • T findOne(ID id) • Iterable<T> findAll() • void delete(T entity)
  28. 33 Repositories @Repository public interface AppRepository extends CrudRepository<App, UUID>, QueryByExampleExecutor<App>

    { public App findByAppId(UUID appId); public App findByAppIdAndRelease(UUID appId, Integer release); }
  29. 34 Entities • POJOs that should be persisted to a

    relational database • Specified by the @Entity annotation • They must have a primary key, annotated with @Id • Various strategies for generated keys are available (AUTO, IDENTITY, SEQUENCE, TABLE) • For UUID use @GeneratedValue(generator = “system-uuid") and @GenericGenerator(name = "system-uuid", strategy = "uuid") • Underlying tables can be annotated using @Table(name = "app")
  30. 35 Entities • Properties are mapped to columns using the

    @Column annotation • The column name can be given or is taken for the field name • Various constraints can be added, for instance unique = true/false, nullable = true/false, insertable = true/false or updatable = true/false • Relations to other entities are defined using annotations like @OneToMany, @ManyToOne or @ManyToMany • These can be refined by using @JoinTable or @JoinColumns • Operations can be cascaded using @OneToMany(cascade = {CascadeType.ALL})
  31. 36 Entities @NoArgsConstructor @Entity @Table(name = "app") public class App

    implements Serializable { @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") @Column(name = "id") private String id; @NotNull @Column(name = "name") private String name; @ManyToMany @JoinTable( name = "app2category", joinColumns = { @JoinColumn(name = "app_id", referencedColumnName = "app_id"), @JoinColumn(name = "release_number", referencedColumnName = "release_number") }, inverseJoinColumns = @JoinColumn(name = "category_id", referencedColumnName = "id")) private List<AppCategory> categories; @Column(name = "created_at", insertable = false, updatable = false) private Timestamp createdAt; }
  32. 37 Query methods • Method names starting with findBy or

    countBy are parsed for query expressions • Properties found in the method name are used as WHERE expressions • Expressions can be chained together using And or Or • Between, LessThan, GreaterThan or Distinct are also supported • Ordering the result set by using OrderBy plus Asc or Desc • Limiting the result set by using Top • Supports nested properties, e.g. findByDeveloperName(DeveloperName name)
  33. 38 Query methods @Repository public interface AppRepository extends CrudRepository<App, UUID>

    { // equals SELECT a FROM App a WHERE a.appId = ?1 ORDER BY release DESC LIMIT 1 App findTopByAppIdOrderByReleaseDesc(final UUID appId); }
  34. 39 @Query • Methods annotated with @Query execute the query

    given as a value • Uses the SpEL (Spring Expression Language) • Parameter binding is position-based by default • Uses named parameters to bind certain values • Declare manipulating queries by using @Modifying
  35. 40 @Query @Repository public interface AppRepository extends CrudRepository<App, UUID> {

    @Query("SELECT a FROM App a WHERE a.appId = ?1 AND a.release = ?2") App findRelease(final UUID appId, final Integer release); @Query("SELECT a FROM App a WHERE a.developerId = :developerId") List<App> findByDeveloper(@Param("developerId") final UUID developerId); }
  36. 41 Examples • Query-less approach to create dynamic queries •

    Uses a domain object with relevant fields being populated as a probe • Uses an example instance with a matcher to create actual queries • Doesn’t support nested or grouped properties • Only supports basic string matching (starts, ends, contains, regex) and exact matching for other types
  37. 42 Examples @Repository public interface AppRepository extends QueryByExampleExecutor<App> { }

    App probe = new App(); probe.setAppId(UUID.fromString("505f3009-fdca-4570-a067-ceabfd571a66")); probe.setRelease(12); Example<App> example = Example.of(probe); // equals SELECT a FROM App a WHERE a.appId = ?1 AND a.release = ?2 App app = appRepository.findOne(example);
  38. 43 Specifications • Write different criteria to build queries programatically

    • Can be chained together using Specifications.and(), Specifications.or() or Specifications.not() • Allow for re-use of certain criteria • Allow for building queries using the domain language
  39. 44 Specifications public class AppSpecification { public static Specification<App> isDraft()

    { return (Root<App> root, CriteriaQuery<?> query, CriteriaBuilder builder) -> builder.equal(root.get("status"), "draft"); } public static Specification<App> updatedBefore(LocalDate before) { return (Root<App> root, CriteriaQuery<?> query, CriteriaBuilder builder) -> builder.lessThan(root.get("updated"), before); } } List<App> apps = appRepository.findAll( Specifications .where(isDraft()) .and(Specifications.not(updatedBefore(LocalDate.parse("2018-01-01")))) );
  40. 46 Project Lombok • Project Lombok is a java library

    that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again. Early access to future java features such as val, and much more. • The project is around since 2009 • The current version 1.16.20 • Currently supports Java version 1.6 - 1.8, support for 1.9 is being worked on • Can be integrate using Maven, Gradle, Ant • IDE support available for IntelliJ, Eclipse, Netbeans
  41. 47 val • Stable feature • Declare local variables as

    final with type inference • Requires import lombok.val; • The variables have to be initialised • Only works for local variables and for foreach loops
  42. 48 val final List<String> tags = new ArrayList<>(); tags.add("search"); tags.add("book");

    tags.add("stay"); for (final String tag: tags) { System.out.println("The tag is: " + tag + "."); } val tags = new ArrayList<>(); tags.add("search"); tags.add("book"); tags.add("stay"); for (val tag: tags) { System.out.println("The tag is: " + tag + "."); }
  43. 49 @NoArgsConstructor • Stable feature • Can be combined with

    one another • Ignores static fields • Use @NoArgsConstructor to generate a constructor with no parameters • Can be forced with final fields being initialised with empty values (null, 0, false) • Use @RequiredArgsConstructor to generate a constructor for all final and @NotNull fields • Use @AllArgsConstructor to generate a constructor for all fields
  44. 50 @NoArgsConstructor @AllArgsConstructor @NoArgsConstructor(force = true) class HttpProblem { URI

    type; String title; } class HttpProblem { private final URI type; private final String title; public HttpProblem() { this.type = null; this.title = null; } public HttpProblem(URI type, String title) { this.type = type; this.title = title; } }
  45. 51 @Data • Stable feature • Combines @ToString, @EqualsAndHashCode, @Getter

    and @Setter and @RequiredArgsConstructor • Getters and setters are public by default • Doesn’t overwrite existing methods
  46. 52 @Data class HttpProblem { private final URI type; private

    final String title; public HttpProblem(URI type, String title) { this.type = type; this.title = title; } public URI getType() { return type; } } @Data class HttpProblem { private final URI type; private final String title; }
  47. 53 @Value • Stable feature • Immutable version of @Data

    • Every field is private and final by default • The class itself is also final • Setters are not being generated • Doesn’t overwrite existing methods
  48. 54 @Value class HttpProblem { private final URI type; private

    final String title; public HttpProblem(URI type, String title) { this.type = type; this.title = title; } public URI getType() { return type; } } @Value class HttpProblem { private final URI type; private final String title; }
  49. 55 @Builder • Stable feature • Can be placed on

    classes or methods • On a class, it’s @AllArgsConstructor and the constructor annotated with @Builder • On a method, it generates an inner static builder class, fields and setters for each parameter, a factory method for the builder and a factory method to create new instances • Fields not set during the build process will be null, 0, false
  50. 56 @Builder @Data @Builder class HttpProblem { private final URI

    type; private final String title; } HttpProblem problem = HttpProblem .builder() .type(URI.create("http://example.org")) .title("error") .build();
  51. 58 Learnings • Java is a bit old-fashioned (everything is

    a class, very explicit and verbose) • Widely adopted, good tool support • Tools specifically targeting web development (like PHP, Symfony) feel more mature • Annotations are convenient, but hard to debug • Booting up a Java application can consume a lot of resources • But once it’s up, it actually runs pretty smooth • Local development using Docker and Java are not the best friends
  52. Q&A