Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
id 'org.springframework.boot' version '3.3.4'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id("io.freefair.lombok") version "8.10.2"
}

targetCompatibility = '22'
Expand All @@ -14,6 +15,10 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation('org.springframework.boot:spring-boot-starter-test')

implementation("org.springframework.boot:spring-boot-starter-jooq")
implementation('org.flywaydb:flyway-core')
runtimeOnly('com.h2database:h2')
}

test {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/wilkins/showcase/Book.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.wilkins.showcase;

public record Book(String id, String name, String author) {
}
31 changes: 31 additions & 0 deletions src/main/java/com/wilkins/showcase/controller/BookController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.wilkins.showcase.controller;

import com.wilkins.showcase.service.BookService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.coyote.http11.Constants;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import static org.springframework.http.ResponseEntity.ok;

@RestController
@RequestMapping("/books")
@Slf4j
@RequiredArgsConstructor
public class BookController {

private final BookService bookService;

@GetMapping
ResponseEntity<StreamingResponseBody> streamBooks() {
log.info("streaming books");
return ok()
.header(HttpHeaders.CONTENT_ENCODING, Constants.CHUNKED)
.body(bookService::streamBooks);
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/wilkins/showcase/controller/JsonBook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wilkins.showcase.controller;

import com.wilkins.showcase.Book;

public record JsonBook(String id, String name, String author) {
public static JsonBook from(Book book) {
return new JsonBook(book.id(), book.name(), book.author());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wilkins.showcase.service;

import com.wilkins.showcase.Book;

import java.util.stream.Stream;

public interface BookRepository {
Stream<Book> findAll();
}
7 changes: 7 additions & 0 deletions src/main/java/com/wilkins/showcase/service/BookService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wilkins.showcase.service;

import java.io.OutputStream;

public interface BookService {
void streamBooks(OutputStream outputStream);
}
37 changes: 37 additions & 0 deletions src/main/java/com/wilkins/showcase/service/DefaultBookService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.wilkins.showcase.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wilkins.showcase.controller.JsonBook;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.OutputStream;

@RequiredArgsConstructor
@Slf4j
@Service
public class DefaultBookService implements BookService {
private final ObjectMapper objectMapper = new ObjectMapper();
private final BookRepository bookRepository;

@Override
public void streamBooks(OutputStream outputStream) {
try (var books = bookRepository.findAll()) {
books
.map(JsonBook::from)
.forEach(book -> {
try {
log.info("getting book {}", book);
Thread.sleep(2000);
log.info("writing book {}", book.name());
outputStream.write(objectMapper.writeValueAsBytes(book));
outputStream.flush();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/wilkins/showcase/service/JooqBookRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.wilkins.showcase.service;

import com.wilkins.showcase.Book;
import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Table;
import org.jooq.impl.DSL;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Stream;

import static org.jooq.impl.DSL.field;

@Service
public class JooqBookRepository implements BookRepository {

private final DSLContext dsl;

public JooqBookRepository(DataSource dataSource) throws SQLException {
this.dsl = DSL.using(dataSource.getConnection());
}

@Override
public Stream<Book> findAll() {
return dsl.select(bookFields)
.from(bookTable)
.fetchStreamInto(Book.class);
}

private static final Table<Record> bookTable = DSL.table("book");
private static final Field<String> externalId = field("book.external_id", String.class);
private static final Field<String> title = field("book.title", String.class);
private static final Field<String> author = field("book.author", String.class);
private static final List<Field<String>> fields = List.of(externalId, title, author);
private static final Field[] bookFields = fields.toArray(new Field[0]);
}
11 changes: 10 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
server:
port: 8080
servlet:
application-display-name: showcase
application-display-name: showcase

spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:showcase
username: showcase
password: Passw0rd!
28 changes: 28 additions & 0 deletions src/main/resources/db/migration/V0__create_book.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
create table book
(
id bigint not null auto_increment,
external_id varchar(120) not null,
title varchar(120),
author varchar(120),
constraint book_pk primary key (id),
constraint book_external_id unique (external_id)
);

insert into book(external_id, title, author)
values ('hobbit_01', 'The Hobbit', 'JRR Tolkien');
insert into book(external_id, title, author)
values ('lord_01', 'Fellowship of the Ring', 'JRR Tolkien');
insert into book(external_id, title, author)
values ('lord_02', 'The Two Towers', 'JRR Tolkien');
insert into book(external_id, title, author)
values ('lord_03', 'Return of the King', 'JRR Tolkien');
insert into book(external_id, title, author)
values ('madding_01', 'Far from the Madding Crowd', 'Thomas Hardy');
insert into book(external_id, title, author)
values ('merchant_01', 'The Merchant of Venice', 'William Shakespear');
insert into book(external_id, title, author)
values ('hamlet_01', 'Hamlet', 'William Shakespear');
insert into book(external_id, title, author)
values ('romeo_01', 'Romeo and Juliet', 'William Shakespear');
insert into book(external_id, title, author)
values ('macbeth_01', 'Macbeth', 'William Shakespear');