Skip to content

Instantly share code, notes, and snippets.

@alexanderankin
Last active September 15, 2023 18:49
Show Gist options
  • Save alexanderankin/cb70d8b8afc19040376db7729a9d613d to your computer and use it in GitHub Desktop.
Save alexanderankin/cb70d8b8afc19040376db7729a9d613d to your computer and use it in GitHub Desktop.
single file application
plugins {
id 'java'
}
repositories.mavenCentral()
dependencies {
implementation platform('org.springframework.boot:spring-boot-dependencies:3.1.3')
annotationProcessor platform('org.springframework.boot:spring-boot-dependencies:3.1.3')
annotationProcessor 'org.projectlombok:lombok'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.withType(Test).configureEach { useJUnitPlatform() }
package org.example.SingleFileApplication;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import javax.sql.DataSource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.springframework.http.HttpStatus.NOT_FOUND;
@SpringBootApplication
class SingleFileApplication {
public static void main(String[] args) {
System.setProperty("spring.datasource.url",
"jdbc:h2:mem:single;IGNORECASE=TRUE;MODE=MySQL;");
SpringApplication.run(SingleFileApplication.class, args);
}
private static <T> T orNotFound(T byId) {
return Optional.ofNullable(byId)
.orElseThrow(() -> new ResponseStatusException(NOT_FOUND));
}
private static void orNotFound(int updated) {
if (updated == 0)
throw new ResponseStatusException(NOT_FOUND);
}
@Autowired
void setupDb(DataSource dataSource) {
DatabasePopulatorUtils.execute(connection -> {
var m = new BeanPropertyRowMapper<>(JdbcTable.class);
var tables = new ArrayList<JdbcTable>();
var resultSet = connection.getMetaData().getTables(null, null, null, new String[]{"TABLE"});
while (resultSet.next()) {
tables.add(m.mapRow(resultSet, 0));
}
var tableNames = tables.stream()
.filter(j -> "single".equalsIgnoreCase(j.getTableCat()))
.map(JdbcTable::getTableName)
.collect(Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)));
if (tableNames.add("apple")) {
connection.createStatement().execute("""
create table apple
(
-- id IDENTITY NOT NULL PRIMARY KEY,
id int unsigned NOT NULL PRIMARY KEY auto_increment,
color VARCHAR(255) NOT NULL,
weight_lbs decimal(4, 2),
variety VARCHAR(50)
);
""");
}
if (tableNames.add("tree")) {
connection.createStatement().execute("""
create table tree
(
id int unsigned NOT NULL PRIMARY KEY auto_increment,
name varchar(255) not null unique,
description varchar(255)
);
""");
connection.createStatement().execute("""
alter table apple add column tree_id int unsigned;
""");
connection.createStatement().execute("""
alter table apple add foreign key (tree_id) references tree(id);
""");
}
}, dataSource);
}
@Data
static class JdbcTable {
String tableName;
@SuppressWarnings("SpellCheckingInspection")
String tableSchem;
String tableCat;
}
@Data
@Accessors(chain = true)
static class Apple {
@Id
Long id;
String color;
BigDecimal weightLbs;
String variety;
Long treeId;
}
@Data
@Accessors(chain = true)
static class Tree {
@Id
Long id;
String name;
String description;
}
@RequiredArgsConstructor
@RestController
@RequestMapping("/apples")
static class AppleController {
final JdbcTemplate jdbcTemplate;
final JdbcAggregateOperations aggregateOperations;
@GetMapping
List<Apple> all(@RequestParam Optional<Integer> page,
@RequestParam Optional<Integer> size) {
return aggregateOperations.findAll(Apple.class,
PageRequest.of(page.orElse(0), size.orElse(10))).getContent();
}
@PostMapping
Apple create(@RequestBody Apple apple) {
return aggregateOperations.insert(apple);
}
@GetMapping("/{id}")
Apple one(@PathVariable Long id) {
return orNotFound(aggregateOperations.findById(id, Apple.class));
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
void delete(@PathVariable Long id) {
orNotFound(jdbcTemplate.update("delete from apple where id = ?", id));
}
// customer just wants to scan a barcode and associate the two
@PutMapping("/{id}/tree/{treeId}")
@ResponseStatus(HttpStatus.OK)
void associateToTree(@PathVariable Long id, @PathVariable Long treeId) {
orNotFound(jdbcTemplate.update("update apple set tree_id = ? where id = ?", treeId, id));
}
@DeleteMapping("/{id}/tree/{treeId}")
@ResponseStatus(HttpStatus.OK)
void disassociateFromTree(@PathVariable Long id, @PathVariable Long treeId) {
orNotFound(jdbcTemplate.update("update apple set tree_id = null where id = ?", treeId, id));
}
}
@RequiredArgsConstructor
@RestController
@RequestMapping("/trees")
static class TreeController {
final JdbcTemplate jdbcTemplate;
final JdbcAggregateOperations aggregateOperations;
@GetMapping
List<Tree> all(@RequestParam Optional<Integer> page,
@RequestParam Optional<Integer> size) {
return aggregateOperations.findAll(Tree.class,
PageRequest.of(page.orElse(0), size.orElse(10))).getContent();
}
@PostMapping
Tree create(@RequestBody Tree apple) {
return aggregateOperations.insert(apple);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
void delete(@PathVariable Long id) {
orNotFound(jdbcTemplate.update("delete from tree where id = ?", id));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment