Giter Club home page Giter Club logo

sb3's Introduction

Spring Boot 3 and Beyond

This is the demo code for my talk, Spring Boot 3 and Beyond.

Agenda

  • Spring Initializr
    • web,postgresql,docker-compose,jdbc,devtools, actuator,graalvm
  • Open the project in IntelliJ
    • Docker Composer
  • Run the application
    • Docker Compose
    • Tomcat 10.1 & Servlet API 6.0 (Jakarta EE 10)
  • Model
    • Post Package
    • Post Record
    • Validation
  • Database Abstractions (Picking the correct one for your project)
    • Connect to a database
    • No tables (yet)
    • schema.sql / application.properties
    • JdbcTemplate
      • create new run
      • find all runs
      • find one run
      • PostController -> findAll()
    • Jdbc Client
      • convert to Jdbc Client
    • Spring Data JDBC
      • convert to Spring Data JDBC
  • Rest Client
    • Rest Template
    • Rest Client
    • Http Interfaces
  • Upgrade
  • Production
    • Virtual Threads
    • JAR
    • Container
    • Native Image

Spring Initializr

Open the project in IntelliJ

Examine the following files:

  • docker-compose.yml

Run the Application

Take notice that we are using Tomcat 10.1 / Servlet 6.0 (Jakarta EE 10)

2024-02-13T13:54:45.913-05:00  INFO 97480 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.18]

Also take notice that the Docker Compose file is getting picked up by convention and the database container is being started.

## Database Abstractions

Picking the correct abstraction level for the project.

### Jdbc Template

```java
@Repository
public class PostRepository {

    private static final Logger log = LoggerFactory.getLogger(PostRepository.class);
    private final JdbcTemplate jdbcTemplate;

    public PostRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void create(Post post) {
        int update = jdbcTemplate.update(
                "INSERT INTO Post(id,user_id,title,body) VALUES(?,?,?,?)",
                post.id(), post.userId(), post.title(), post.body()
        );
        log.info("Inserted {} rows", update);
    }

    public List<Post> findAll() {
        return jdbcTemplate.query(
                "SELECT id, user_id, title, body FROM Post",
                (rs, rowNum) -> new Post(
                        rs.getInt("id"),
                        rs.getInt("user_id"),
                        rs.getString("title"),
                        rs.getString("body")
                )
        );
    }
}

Jdbc Client

@Repository
public class PostRepository {

  private static final Logger log = LoggerFactory.getLogger(PostRepository.class);
  private final JdbcClient jdbcClient;
  private final JdbcTemplate jdbcTemplate;

  public PostRepository(JdbcClient jdbcClient, JdbcTemplate jdbcTemplate) {
    this.jdbcClient = jdbcClient;
    this.jdbcTemplate = jdbcTemplate;
  }

  List<Post> findAll() {
    return jdbcClient.sql("SELECT * FROM post")
            .query(Post.class)
            .list();
  }

  public Optional<Post> findById(String id) {
    return jdbcClient.sql("SELECT id,user_id,title,body FROM post WHERE id = :id")
            .param("id", id)
            .query(Post.class)
            .optional();
  }

  public void create(Post post) {
    int update = jdbcClient.sql("INSERT INTO post (id, user_id, title, body) VALUES (?, ?, ?, ?)")
            .params(List.of(post.id(), post.userId(), post.title(), post.body()))
            .update();
    log.info("Inserted {} rows", update);
  }

  void update(Post post, String id) {
    var updated = jdbcClient.sql("UPDATE post SET user_id = ?, title = ?, body = ? where id = ?")
            .params(List.of(post.userId(),post.title(),post.body(), id))
            .update();

    log.info("Updated {} row(s)", updated);
  }

  void delete(String id) {
    var updated = jdbcClient.sql("DELETE from post where id = :id")
            .param("id", id)
            .update();

    log.info("Deleted {} row(s)", updated);
  }

  List<Post> findByUserId(Integer userId) {
    return jdbcClient.sql("SELECT * FROM post WHERE user_id = :userId")
            .param("userId", userId)
            .query(Post.class)
            .list();
  }

  public void saveAllPosts(List<Post> posts) {
    posts.forEach(this::create);
  }

  public void saveAll(List<Post> posts) {
    for (Post p : posts) {
      jdbcTemplate.update("INSERT INTO post (id,user_id,title,body) " +
                      "VALUES (?, ?, ?,?)",
              p.id(),
              p.userId(),
              p.title(),
              p.body());
    }
  }

}

Spring Data JDBC

@RestController
@RequestMapping("/posts")
class PostController {

    private final PostRepository postRepository;

    PostController(PostRepository postRepository) {
        this.postRepository = postRepository;
    }

    @GetMapping("")
    List<Post> findAll() {
        return postRepository.findAll();
    }

    @GetMapping("/user/{userId}")
    List<Post> findByUserId(@PathVariable Integer userId) {
        return postRepository.findByUserId(userId);
    }

}
public interface PostRepository extends ListCrudRepository<Post,Integer> {

    List<Post> findByUserId(Integer userId);

}
public record Post(
        @Id
        Integer id,
        Integer userId,
        @NotEmpty
        String title,
        String body,
        @Version
        Integer version
) {

}

Rest Clients

RestTemplate

@Component
public class PostClient {

    private final RestTemplate restTemplate;

    public PostClient(RestTemplateBuilder builder) {
        this.restTemplate = builder.build();
    }

    /*
     * Call JsonPlaceholderService to get a list of posts
     */
    public List<Post> findAll() {
        return restTemplate.exchange(
                "https://jsonplaceholder.typicode.com/posts",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Post>>() {
                }
        ).getBody();
    }

}

RestClient

@Component
public class PostClient {

    private final RestClient restClient;

    public PostClient(RestClient.Builder builder) {
        this.restClient = builder
                .baseUrl("https://jsonplaceholder.typicode.com/")
                .build();
    }

    public List<Post> findAll() {
        return restClient.get()
                .uri("/posts")
                .retrieve()
                .body(new ParameterizedTypeReference<>() {});
    }

}

Http Interfaces

public interface PostClient {

    @GetExchange("/posts")
    List<Post> findAll();

    @GetExchange("/posts/{id}")
    Post findById(@PathVariable Integer id);

}
@Bean
PostClient postClient() {
    RestClient restClient = RestClient.create("https://jsonplaceholder.typicode.com/");
    HttpServiceProxyFactory factory = HttpServiceProxyFactory.builderFor(RestClientAdapter.create(restClient)).build();
    return factory.createClient(PostClient.class);
}

Production

Production is the greatest place on the web! We should all want to go there, but we get it, it can be a scary place.

Virtual Threads

Virtual Threads were introduced in JDK 21 as a solution to the challenges of asynchronous programming. These lightweight threads do not block platform threads, making them highly efficient. In fact, you can spawn millions of Virtual Threads without needing to worry about thread pooling.

Enabling Virtual Threads in your Spring Boot application couldn't be easier. Open the application.properties file and set the following property.

spring.threads.virtual.enabled=true
    @GetMapping("")
    List<Post> findAll() {
        log.info("Thread: {}", Thread.currentThread());
        return postRepository.findAll();
    }

Run the application and inspect the logs to see the Virtual Threads in action.

2024-02-13T16:12:28.117-05:00  INFO 11869 --- [omcat-handler-0] dev.danvega.sb3.post.PostController      : Thread: VirtualThread[#76,tomcat-handler-0]/runnable@ForkJoinPool-1-worker-1

JAR

The spring-boot-loader modules lets Spring Boot support executable jar and war files. If you use the Maven plugin or the Gradle plugin, executable jars are automatically generated, and you generally do not need to know the details of how they work.

To create an executable JAR run the following command:

./mvnw clean package
  • clean: Deletes the target folder
  • package: Invoke Maven's package phase, which will cause verify, compile and test phases to be invoked in the correct order.

To run the executable JAR with Java run the following command:

java -jar target/sb3-0.0.1-SNAPSHOT.jar

https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html#appendix.executable-jar

Containers

In the previous section you learned how to build an Uber JAR and run it on any machine that has a JDK. What if there is no JDK?

This is where containers and specifically Docker containers can help us out. You have already seen some examples of us running a Docker Compose file to run a production grade database locally.

You can create a production version of your Spring Boot application using Dockerfiles, or by using Cloud Native Buildpacks to create optimized docker compatible container images that you can run anywhere.

https://docs.spring.io/spring-boot/docs/current/reference/html/container-images.html#container-images

As long as you have Docker desktop running you can run the following command to create an OCI image:

./mvnw spring-boot:build-image
docker image ls -a
docker run -it -p8080:8080 sb3:0.0.1-SNAPSHOT

To learn more about Packaging OCI Images check out the documentation

If you want to do "./mvnw -Pnative spring-boot:build-image" on your Apple Silicon or Raspberry Pi then this is your solution.

Native Images

Spring Boot 3.0 applications can now be converted into GraalVM native images which can provide significant memory and startup-up performance improvements. Spring Boot requires GraalVM 22.3 or later and Native Build Tools Plugin 0.9.17 or later to build native images. If you would like to learn more about GraalVM Native Image Support you can check out the reference documentation.

With the native profile active, you can invoke the native:compile goal to trigger native-image compilation:

mvn -Pnative native:compile

The result will be a native executable in the target/ directory.

Notes

  • There is a bug with Spring Data JDBC and native images that you can read about here.
@ImportRuntimeHints(MyRuntimeHints.ResourcesRegistrar.class)
@Configuration
public class MyRuntimeHints {

    static class ResourcesRegistrar implements RuntimeHintsRegistrar {

        @Override
        public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
            try {
                hints.reflection().registerType(TypeReference.of("org.springframework.data.domain.Unpaged"),
                        builder -> builder
                                .withMembers(MemberCategory.values()));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

}

Notes

  • GitHub Repo Open in Browser
  • have IntelliJ open (refactor application)
    • maybe have a start branch so that I can have all the builds ready to go

sb3's People

Contributors

danvega avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.