Browse Source

Upgrade spring boot version and optimize JDK docker compilation (#7509)

* Upgraded spring boot version and optimize JRE dockerization

* Change README

* Fix readme
AssentSoftware 3 years ago
parent
commit
f98f3895ef

+ 5 - 5
frameworks/Java/spring/README.md

@@ -2,12 +2,10 @@
 
 This is the Spring MVC portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
 
-An embedded tomcat is used for the web server, with nearly everything configured with default settings.
+An embedded undertow is used for the web server, with nearly everything configured with default settings.
 The only thing changed is Hikari can use up to (2 * cores count) connections (the default is 10).
 See [About-Pool-Sizing](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing)
 
-Tomcat use a fixed thread pool that can grow up to 200 threads.
-
 There are two implementations :
 * For postgresql access, JdbcTemplate is used. See [JdbcDbRepository](src/main/java/hello/JdbcDbRepository.java).
 * For mongoDB access, MongoTemplate is used. See [MongoDbRepository](src/main/java/hello/MongoDbRepository.java).
@@ -38,8 +36,10 @@ There are two implementations :
 
 ## Versions
 
-* [Java OpenJDK 11](http://openjdk.java.net/)
-* [Spring boot 2.1.2](https://spring.io/projects/spring-boot)
+* [OpenJDK Runtime Environment Temurin-11.0.16+8 (build 11.0.16+8)](https://adoptium.net/es/temurin/releases/?version=11)
+* [Spring boot 2.6.9](https://spring.io/projects/spring-boot)
+
+The change to use OpenJDK Temurin is inspired in [whichjdk](https://whichjdk.com/) page advice.
 
 ## Test URLs
 

+ 63 - 62
frameworks/Java/spring/pom.xml

@@ -1,71 +1,72 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                              http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
-  <modelVersion>4.0.0</modelVersion>
+	<modelVersion>4.0.0</modelVersion>
 
-  <groupId>hello</groupId>
-  <artifactId>hello-spring</artifactId>
-  <version>1.0-SNAPSHOT</version>
+	<groupId>hello</groupId>
+	<artifactId>hello-spring</artifactId>
+	<version>1.0-SNAPSHOT</version>
 
-  <parent>
-    <groupId>org.springframework.boot</groupId>
-    <artifactId>spring-boot-starter-parent</artifactId>
-    <version>2.3.1.RELEASE</version>
-  </parent>
+	<parent>
+		<groupId>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>2.6.9</version>
+	</parent>
 
-  <properties>
-    <maven.compiler.source>11</maven.compiler.source>
-    <maven.compiler.target>11</maven.compiler.target>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <postgresql.version>42.3.3</postgresql.version>
-  </properties>
+	<properties>
+		<java.version>11</java.version>
+		<postgresql.version>42.3.3</postgresql.version>
+	</properties>
 
-  <dependencies>
-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-web</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-data-jpa</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-data-mongodb</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-jdbc</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework.boot</groupId>
-      <artifactId>spring-boot-starter-mustache</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.postgresql</groupId>
-      <artifactId>postgresql</artifactId>
-      <version>${postgresql.version}</version>
-    </dependency>
-  </dependencies>
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-tomcat</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-undertow</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-jpa</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-mongodb</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-mustache</artifactId>
+		</dependency>
 
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.springframework.boot</groupId>
-        <artifactId>spring-boot-maven-plugin</artifactId>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.8.1</version>
-        <configuration>
-          <debug>false</debug>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
+		<dependency>
+			<groupId>org.postgresql</groupId>
+			<artifactId>postgresql</artifactId>
+		</dependency>
+	</dependencies>
 
-</project>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<debug>false</debug>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>

+ 23 - 2
frameworks/Java/spring/spring-jpa.dockerfile

@@ -1,13 +1,34 @@
+FROM eclipse-temurin:11 as jre-build
+
+# Create a custom Java runtime
+RUN $JAVA_HOME/bin/jlink \
+         --add-modules ALL-MODULE-PATH \
+         --strip-debug \
+         --no-man-pages \
+         --no-header-files \
+         --compress=2 \
+         --output /javaruntime
+
 FROM maven:3.6.1-jdk-11-slim as maven
+ENV JAVA_HOME=/opt/java/openjdk
+ENV PATH "${JAVA_HOME}/bin:${PATH}"
+COPY --from=jre-build /javaruntime $JAVA_HOME
+
+RUN mvn -version
 WORKDIR /spring
 COPY src src
 COPY pom.xml pom.xml
 RUN mvn package -q
 
-FROM openjdk:11.0.3-jdk-slim
+FROM debian:buster-slim
+ENV JAVA_HOME=/opt/java/openjdk
+ENV PATH "${JAVA_HOME}/bin:${PATH}"
+COPY --from=jre-build /javaruntime $JAVA_HOME
+
+RUN java -version
 WORKDIR /spring
 COPY --from=maven /spring/target/hello-spring-1.0-SNAPSHOT.jar app.jar
 
 EXPOSE 8080
 
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jpa"]
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseG1GC", "-XX:+DisableExplicitGC", "-XX:+UseStringDeduplication", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jpa"]

+ 23 - 2
frameworks/Java/spring/spring-mongo.dockerfile

@@ -1,13 +1,34 @@
+FROM eclipse-temurin:11 as jre-build
+
+# Create a custom Java runtime
+RUN $JAVA_HOME/bin/jlink \
+         --add-modules ALL-MODULE-PATH \
+         --strip-debug \
+         --no-man-pages \
+         --no-header-files \
+         --compress=2 \
+         --output /javaruntime
+
 FROM maven:3.6.1-jdk-11-slim as maven
+ENV JAVA_HOME=/opt/java/openjdk
+ENV PATH "${JAVA_HOME}/bin:${PATH}"
+COPY --from=jre-build /javaruntime $JAVA_HOME
+
+RUN mvn -version
 WORKDIR /spring
 COPY src src
 COPY pom.xml pom.xml
 RUN mvn package -q
 
-FROM openjdk:11.0.3-jdk-slim
+FROM debian:buster-slim
+ENV JAVA_HOME=/opt/java/openjdk
+ENV PATH "${JAVA_HOME}/bin:${PATH}"
+COPY --from=jre-build /javaruntime $JAVA_HOME
+
+RUN java -version
 WORKDIR /spring
 COPY --from=maven /spring/target/hello-spring-1.0-SNAPSHOT.jar app.jar
 
 EXPOSE 8080
 
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=mongo"]
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseG1GC", "-XX:+DisableExplicitGC", "-XX:+UseStringDeduplication", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=mongo"]

+ 23 - 2
frameworks/Java/spring/spring.dockerfile

@@ -1,13 +1,34 @@
+FROM eclipse-temurin:11 as jre-build
+
+# Create a custom Java runtime
+RUN $JAVA_HOME/bin/jlink \
+         --add-modules ALL-MODULE-PATH \
+         --strip-debug \
+         --no-man-pages \
+         --no-header-files \
+         --compress=2 \
+         --output /javaruntime
+
 FROM maven:3.6.1-jdk-11-slim as maven
+ENV JAVA_HOME=/opt/java/openjdk
+ENV PATH "${JAVA_HOME}/bin:${PATH}"
+COPY --from=jre-build /javaruntime $JAVA_HOME
+
+RUN mvn -version
 WORKDIR /spring
 COPY src src
 COPY pom.xml pom.xml
 RUN mvn package -q
 
-FROM openjdk:11.0.3-jdk-slim
+FROM debian:buster-slim
+ENV JAVA_HOME=/opt/java/openjdk
+ENV PATH "${JAVA_HOME}/bin:${PATH}"
+COPY --from=jre-build /javaruntime $JAVA_HOME
+
+RUN java -version
 WORKDIR /spring
 COPY --from=maven /spring/target/hello-spring-1.0-SNAPSHOT.jar app.jar
 
 EXPOSE 8080
 
-CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jdbc"]
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseG1GC", "-XX:+DisableExplicitGC", "-XX:+UseStringDeduplication", "-Dlogging.level.root=OFF", "-jar", "app.jar", "--spring.profiles.active=jdbc"]

+ 14 - 12
frameworks/Java/spring/src/main/java/hello/App.java

@@ -1,6 +1,7 @@
 package hello;
 
-import com.zaxxer.hikari.HikariDataSource;
+import javax.sql.DataSource;
+
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@@ -8,21 +9,22 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Profile;
 
-import javax.sql.DataSource;
+import com.zaxxer.hikari.HikariDataSource;
 
 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
 public class App {
 
-    public static void main(String[] args) {
-        SpringApplication.run(App.class, args);
-    }
+	public static void main(String[] args) {
+		SpringApplication.run(App.class, args);
+	}
 
-    @Bean
-    @Profile({"jdbc", "jpa"})
-    public DataSource datasource(DataSourceProperties dataSourceProperties) {
-        HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
-        dataSource.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
+	@Bean
+	@Profile({ "jdbc", "jpa" })
+	public DataSource datasource(DataSourceProperties dataSourceProperties) {
+		HikariDataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class)
+				.build();
+		dataSource.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
 
-        return dataSource;
-    }
+		return dataSource;
+	}
 }

+ 106 - 111
frameworks/Java/spring/src/main/java/hello/controller/HelloController.java

@@ -3,124 +3,119 @@ package hello.controller;
 import static java.util.Comparator.comparing;
 
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.IntStream;
 
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
 import hello.model.Fortune;
 import hello.model.World;
 import hello.repository.DbRepository;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.ModelAttribute;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.ResponseBody;
 
-@Controller
+@RestController
 public final class HelloController {
 
-  @Autowired
-  private DbRepository dbRepository;
-
-  @RequestMapping("/plaintext")
-  @ResponseBody
-  String plaintext() {
-    return "Hello, World!";
-  }
-
-  @RequestMapping("/json")
-  @ResponseBody
-  Message json() {
-    return new Message("Hello, World!");
-  }
-
-  @RequestMapping("/db")
-  @ResponseBody
-  World db() {
-    return dbRepository.getWorld(randomWorldNumber());
-  }
-
-  @RequestMapping("/queries")
-  @ResponseBody
-  World[] queries(@RequestParam String queries) {
-    return randomWorldNumbers()
-        .mapToObj(dbRepository::getWorld)
-        .limit(parseQueryCount(queries))
-        .toArray(World[]::new);
-  }
-
-  @RequestMapping("/updates")
-  @ResponseBody
-  World[] updates(@RequestParam String queries) {
-    return randomWorldNumbers()
-        .mapToObj(dbRepository::getWorld)
-        .map(world -> {
-          // Ensure that the new random number is not equal to the old one.
-          // That would cause the JPA-based implementation to avoid sending the
-          // UPDATE query to the database, which would violate the test
-          // requirements.
-          int newRandomNumber;
-          do {
-            newRandomNumber = randomWorldNumber();
-          } while (newRandomNumber == world.randomnumber);
-          return dbRepository.updateWorld(world, newRandomNumber);
-        })
-        .limit(parseQueryCount(queries))
-        .toArray(World[]::new);
-  }
-
-  @RequestMapping("/fortunes")
-  @ModelAttribute("fortunes")
-  List<Fortune> fortunes() {
-    var fortunes = dbRepository.fortunes();
-
-    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
-    fortunes.sort(comparing(fortune -> fortune.message));
-    return fortunes;
-  }
-
-  private static final int MIN_WORLD_NUMBER = 1;
-  private static final int MAX_WORLD_NUMBER_PLUS_ONE = 10_001;
-
-  private static int randomWorldNumber() {
-    return ThreadLocalRandom
-        .current()
-        .nextInt(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE);
-  }
-
-  private static IntStream randomWorldNumbers() {
-    return ThreadLocalRandom
-        .current()
-        .ints(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE)
-        // distinct() allows us to avoid using Hibernate's first-level cache in
-        // the JPA-based implementation.  Using a cache like that would bypass
-        // querying the database, which would violate the test requirements.
-        .distinct();
-  }
-
-  private static int parseQueryCount(String textValue) {
-    if (textValue == null) {
-      return 1;
-    }
-    int parsedValue;
-    try {
-      parsedValue = Integer.parseInt(textValue);
-    } catch (NumberFormatException e) {
-      return 1;
-    }
-    return Math.min(500, Math.max(1, parsedValue));
-  }
-
-  static class Message {
-    private final String message;
-
-    public Message(String message) {
-      this.message = message;
-    }
-
-    public String getMessage() {
-      return message;
-    }
-  }
+	private DbRepository dbRepository;
+
+	public HelloController(DbRepository dbRepository) {
+		this.dbRepository = dbRepository;
+	}
+
+	@GetMapping(value = "/plaintext", produces = MediaType.TEXT_PLAIN_VALUE)
+	String plaintext() {
+		return "Hello, World!";
+	}
+
+	@GetMapping("/json")
+	Message json() {
+		return new Message("Hello, World!");
+	}
+
+	@GetMapping("/db")
+	World db() {
+		return dbRepository.getWorld(randomWorldNumber());
+	}
+
+	@GetMapping("/queries")
+	World[] queries(@RequestParam(required = false) String queries) {
+		return randomWorldNumbers().mapToObj(dbRepository::getWorld).limit(parseQueryCount(queries))
+				.toArray(World[]::new);
+	}
+
+	@GetMapping("/updates")
+	World[] updates(@RequestParam(required = false) String queries) {
+		return randomWorldNumbers().mapToObj(dbRepository::getWorld).map(world -> {
+			// Ensure that the new random number is not equal to the old one.
+			// That would cause the JPA-based implementation to avoid sending the
+			// UPDATE query to the database, which would violate the test
+			// requirements.
+
+			// Locally the records doesn't exist, maybe in the yours is ok but we need to
+			// make this check
+			if (world == null) {
+				return null;
+			}
+
+			int newRandomNumber;
+			do {
+				newRandomNumber = randomWorldNumber();
+			} while (newRandomNumber == world.randomnumber);
+
+			return dbRepository.updateWorld(world, newRandomNumber);
+		}).limit(parseQueryCount(queries)).toArray(World[]::new);
+	}
+
+	@GetMapping("/fortunes")
+	@ModelAttribute("fortunes")
+	List<Fortune> fortunes() {
+		var fortunes = dbRepository.fortunes();
+
+		fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+		fortunes.sort(comparing(fortune -> fortune.message));
+		return fortunes;
+	}
+
+	private static final int MIN_WORLD_NUMBER = 1;
+	private static final int MAX_WORLD_NUMBER_PLUS_ONE = 10_001;
+
+	private static int randomWorldNumber() {
+		return ThreadLocalRandom.current().nextInt(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE);
+	}
+
+	private static IntStream randomWorldNumbers() {
+		return ThreadLocalRandom.current().ints(MIN_WORLD_NUMBER, MAX_WORLD_NUMBER_PLUS_ONE)
+				// distinct() allows us to avoid using Hibernate's first-level cache in
+				// the JPA-based implementation. Using a cache like that would bypass
+				// querying the database, which would violate the test requirements.
+				.distinct();
+	}
+
+	private static int parseQueryCount(String textValue) {
+		if (textValue == null) {
+			return 1;
+		}
+		int parsedValue;
+		try {
+			parsedValue = Integer.parseInt(textValue);
+		} catch (NumberFormatException e) {
+			return 1;
+		}
+		return Math.min(500, Math.max(1, parsedValue));
+	}
+
+	static class Message {
+		private final String message;
+
+		public Message(String message) {
+			this.message = message;
+		}
+
+		public String getMessage() {
+			return message;
+		}
+	}
 }

+ 4 - 3
frameworks/Java/spring/src/main/java/hello/jpa/FortuneRepository.java

@@ -1,11 +1,12 @@
 package hello.jpa;
 
-import hello.model.Fortune;
 import org.springframework.context.annotation.Profile;
 import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
+
+import hello.model.Fortune;
 
-@Component
+@Repository
 @Profile("jpa")
 public interface FortuneRepository extends JpaRepository<Fortune, Integer> {
 }

+ 29 - 31
frameworks/Java/spring/src/main/java/hello/jpa/JpaDbRepository.java

@@ -1,40 +1,38 @@
 package hello.jpa;
 
+import java.util.List;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+
 import hello.model.Fortune;
 import hello.model.World;
 import hello.repository.DbRepository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Profile;
-import org.springframework.stereotype.Component;
 
-import java.util.List;
-
-@Component
+@Service
 @Profile("jpa")
 public class JpaDbRepository implements DbRepository {
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private final WorldRepository worldRepository;
-    private final FortuneRepository fortuneRepository;
-
-    public JpaDbRepository(WorldRepository worldRepository, FortuneRepository fortuneRepository) {
-        this.worldRepository = worldRepository;
-        this.fortuneRepository = fortuneRepository;
-    }
-
-    @Override
-    public World getWorld(int id) {
-        return worldRepository.findById(id).orElse(null);
-    }
-
-    @Override
-    public World updateWorld(World world, int randomNumber) {
-        world.randomnumber = randomNumber;
-        return worldRepository.save(world);
-    }
-
-    @Override
-    public List<Fortune> fortunes() {
-        return fortuneRepository.findAll();
-    }
+	private final WorldRepository worldRepository;
+	private final FortuneRepository fortuneRepository;
+
+	public JpaDbRepository(WorldRepository worldRepository, FortuneRepository fortuneRepository) {
+		this.worldRepository = worldRepository;
+		this.fortuneRepository = fortuneRepository;
+	}
+
+	@Override
+	public World getWorld(int id) {
+		return worldRepository.findById(id).orElse(null);
+	}
+
+	@Override
+	public World updateWorld(World world, int randomNumber) {
+		world.randomnumber = randomNumber;
+		return worldRepository.save(world);
+	}
+
+	@Override
+	public List<Fortune> fortunes() {
+		return fortuneRepository.findAll();
+	}
 }

+ 4 - 3
frameworks/Java/spring/src/main/java/hello/jpa/WorldRepository.java

@@ -1,11 +1,12 @@
 package hello.jpa;
 
-import hello.model.World;
 import org.springframework.context.annotation.Profile;
 import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
+
+import hello.model.World;
 
-@Component
+@Repository
 @Profile("jpa")
 public interface WorldRepository extends JpaRepository<World, Integer> {
 }

+ 19 - 15
frameworks/Java/spring/src/main/java/hello/model/Fortune.java

@@ -1,29 +1,33 @@
 package hello.model;
 
 import javax.persistence.Entity;
+
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
 
 @Document
 @Entity
 public final class Fortune {
-  @Id
-  @javax.persistence.Id
-  public int id;
-  public String message;
+	@Id
+	@javax.persistence.Id
+	public int id;
+	@Field("message")
+	public String message;
 
-  protected Fortune() {}
+	protected Fortune() {
+	}
 
-  public Fortune(int id, String message) {
-    this.id = id;
-    this.message = message;
-  }
+	public Fortune(int id, String message) {
+		this.id = id;
+		this.message = message;
+	}
 
-  public int getId() {
-    return id;
-  }
+	public int getId() {
+		return id;
+	}
 
-  public String getMessage() {
-    return message;
-  }
+	public String getMessage() {
+		return message;
+	}
 }

+ 12 - 10
frameworks/Java/spring/src/main/java/hello/model/World.java

@@ -1,6 +1,7 @@
 package hello.model;
 
 import javax.persistence.Entity;
+
 import org.springframework.data.annotation.Id;
 import org.springframework.data.mongodb.core.mapping.Document;
 import org.springframework.data.mongodb.core.mapping.Field;
@@ -9,16 +10,17 @@ import org.springframework.data.mongodb.core.mapping.Field;
 @Entity
 public final class World {
 
-  @Id
-  @javax.persistence.Id
-  public int id;
-  @Field("randomNumber")
-  public int randomnumber;
+	@Id
+	@javax.persistence.Id
+	public int id;
+	@Field("randomNumber")
+	public int randomnumber;
 
-  protected World() {}
+	protected World() {
+	}
 
-  public World(int id, int randomnumber) {
-    this.id = id;
-    this.randomnumber = randomnumber;
-  }
+	public World(int id, int randomnumber) {
+		this.id = id;
+		this.randomnumber = randomnumber;
+	}
 }

+ 5 - 5
frameworks/Java/spring/src/main/java/hello/repository/DbRepository.java

@@ -1,14 +1,14 @@
 package hello.repository;
 
+import java.util.List;
+
 import hello.model.Fortune;
 import hello.model.World;
 
-import java.util.List;
-
 public interface DbRepository {
-    World getWorld(int id);
+	World getWorld(int id);
 
-    World updateWorld(World world, int randomNumber);
+	World updateWorld(World world, int randomNumber);
 
-    List<Fortune> fortunes();
+	List<Fortune> fortunes();
 }

+ 41 - 41
frameworks/Java/spring/src/main/java/hello/repository/JdbcDbRepository.java

@@ -1,52 +1,52 @@
 package hello.repository;
 
-import hello.model.Fortune;
-import hello.model.World;
+import java.util.List;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.annotation.Profile;
+import org.springframework.dao.EmptyResultDataAccessException;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
 
-import java.util.List;
+import hello.model.Fortune;
+import hello.model.World;
 
-@Component
+@Repository
 @Profile("jdbc")
 public class JdbcDbRepository implements DbRepository {
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private final JdbcTemplate jdbcTemplate;
-
-    public JdbcDbRepository(JdbcTemplate jdbcTemplate) {
-        this.jdbcTemplate = jdbcTemplate;
-    }
-
-    @Override
-    public World getWorld(int id) {
-        log.debug("getWorld({})", id);
-        return jdbcTemplate.queryForObject(
-                "SELECT * FROM world WHERE id = ?",
-                (rs, rn) -> new World(rs.getInt("id"), rs.getInt("randomnumber")),
-                id);
-    }
-
-    private World updateWorld(World world) {
-        jdbcTemplate.update(
-                "UPDATE world SET randomnumber = ? WHERE id = ?",
-                world.randomnumber,
-                world.id);
-        return world;
-    }
-
-    @Override
-    public World updateWorld(World world, int randomNumber) {
-        world.randomnumber = randomNumber;
-        return updateWorld(world);
-    }
-
-    @Override
-    public List<Fortune> fortunes() {
-        return jdbcTemplate.query(
-                "SELECT * FROM fortune",
-                (rs, rn) -> new Fortune(rs.getInt("id"), rs.getString("message")));
-    }
+	private final Logger log = LoggerFactory.getLogger(getClass());
+	private final JdbcTemplate jdbcTemplate;
+
+	public JdbcDbRepository(JdbcTemplate jdbcTemplate) {
+		this.jdbcTemplate = jdbcTemplate;
+	}
+
+	@Override
+	public World getWorld(int id) {
+		log.debug("getWorld({})", id);
+		try {
+			return jdbcTemplate.queryForObject("SELECT * FROM world WHERE id = ?",
+					(rs, rn) -> new World(rs.getInt("id"), rs.getInt("randomnumber")), id);
+		} catch (EmptyResultDataAccessException e) {
+			return null;
+		}
+	}
+
+	private World updateWorld(World world) {
+		jdbcTemplate.update("UPDATE world SET randomnumber = ? WHERE id = ?", world.randomnumber, world.id);
+		return world;
+	}
+
+	@Override
+	public World updateWorld(World world, int randomNumber) {
+		world.randomnumber = randomNumber;
+		return updateWorld(world);
+	}
+
+	@Override
+	public List<Fortune> fortunes() {
+		return jdbcTemplate.query("SELECT * FROM fortune",
+				(rs, rn) -> new Fortune(rs.getInt("id"), rs.getString("message")));
+	}
 }

+ 29 - 30
frameworks/Java/spring/src/main/java/hello/repository/MongoDbRepository.java

@@ -1,41 +1,40 @@
 package hello.repository;
 
-import hello.model.Fortune;
-import hello.model.World;
+import java.util.List;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.annotation.Profile;
 import org.springframework.data.mongodb.core.MongoTemplate;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
 
-import java.util.List;
+import hello.model.Fortune;
+import hello.model.World;
 
-@Component
+@Repository
 @Profile("mongo")
 public class MongoDbRepository implements DbRepository {
-    private final Logger log = LoggerFactory.getLogger(getClass());
-    private final MongoTemplate mongoTemplate;
-
-    public MongoDbRepository(MongoTemplate mongoTemplate) {
-        this.mongoTemplate = mongoTemplate;
-    }
-
-
-    @Override
-    public World getWorld(int id) {
-        log.debug("getWorld({})", id);
-        return mongoTemplate.findById(id, World.class);
-    }
-
-
-    @Override
-    public World updateWorld(World world, int randomNumber) {
-        world.randomnumber = randomNumber;
-        return mongoTemplate.save(world);
-    }
-
-    @Override
-    public List<Fortune> fortunes() {
-        return mongoTemplate.findAll(Fortune.class);
-    }
+	private final Logger log = LoggerFactory.getLogger(getClass());
+	private final MongoTemplate mongoTemplate;
+
+	public MongoDbRepository(MongoTemplate mongoTemplate) {
+		this.mongoTemplate = mongoTemplate;
+	}
+
+	@Override
+	public World getWorld(int id) {
+		log.debug("getWorld({})", id);
+		return mongoTemplate.findById(id, World.class);
+	}
+
+	@Override
+	public World updateWorld(World world, int randomNumber) {
+		world.randomnumber = randomNumber;
+		return mongoTemplate.save(world);
+	}
+
+	@Override
+	public List<Fortune> fortunes() {
+		return mongoTemplate.findAll(Fortune.class);
+	}
 }

+ 10 - 3
frameworks/Java/spring/src/main/resources/application.yml

@@ -1,6 +1,8 @@
 ---
 spring:
-  profiles: jdbc,jpa
+  config:
+    activate:
+      on-profile: jdbc,jpa
   datasource:
     url: jdbc:postgresql://${database.host}:${database.port}/${database.name}
     username: ${database.username}
@@ -15,13 +17,18 @@ database:
 
 ---
 spring:
-  profiles: jpa
+  config:
+    activate:
+      on-profile: jpa
   jpa:
     database-platform: org.hibernate.dialect.PostgreSQLDialect
+    open-in-view: false
 
 ---
 spring:
-  profiles: mongo
+  config:
+    activate:
+      on-profile: mongo
 
 spring.data.mongodb:
   host: tfb-database