Добрый день!
Давайте сегодня модифицируем наше приложение из прошлой статьи и добавим в него поддержку взаимодействия с базой данных, расположенной в докер-контейнере.
Начнём с написания кода.
Добавим следующие зависимости
compile 'org.springframework.boot:spring-boot-starter-data-jpa:2.1.0.RELEASE'
compile 'org.projectlombok:lombok:1.18.4'
compile 'org.postgresql:postgresql:42.2.5'
Создаём application.properties
spring.jpa.hibernate.ddl-auto = update
spring.datasource.url=jdbc:postgresql://db_host/
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
RequestEntity.java
package com.tune_it;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class RequestEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
RequestEntityRepository.java
package com.tune_it;
import org.springframework.data.repository.CrudRepository;
public interface RequestEntityRepository extends CrudRepository<RequestEntity, Long> {
}
Модифицируем HelloController
package com.tune_it;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/hello")
public class HelloController {
@Autowired
private RequestEntityRepository entityRepository;
@GetMapping
public String hello() {
entityRepository.save(new RequestEntity());
return "Hello " + entityRepository.count() + " times\n";
}
}
Теперь займёмся нашим build.gradle
Добавим задачи создания старта и удаления контейнера с базой данных.
Для того, чтобы наши контейнеры могли общаться друг с другом, их необходимо объединить в одну сеть. Для этого создадим сеть и добавим её параметром в создание контейнеров.
def postgresHostDir = '/tmp/test_db'
def postgresDockerDir = '/var/lib/postgresql/data'
def dbContainerName = 'test_db'
def dbNetworkName = 'test_network'
task createContainer(type: DockerCreateContainer) {
dependsOn buildImage, removeContainer
targetImageId buildImage.getImageId()
containerName = "$uniqueContainerName"
portBindings = ['8080:8080']
network = "$dbNetworkName"
networkAliases = ["web_host"]
}
task createDbContainer(type: DockerCreateContainer) {
targetImageId "postgres:11.0"
containerName = "$dbContainerName"
volumes = ["$postgresHostDir:$postgresDockerDir"]
network = "$dbNetworkName"
networkAliases = ["db_host"]
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task startDbContainer(type: DockerStartContainer) {
targetContainerId("$dbContainerName")
}
task stopDbContainer(type: DockerStopContainer) {
targetContainerId("$dbContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeDbContainer(type: DockerRemoveContainer) {
dependsOn stopDbContainer
targetContainerId("$dbContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
networkAliases = ["db_host"]
- параметр, отвечающий за имя хоста с нашей БД, именно его мы указывали в application.properties
Итоговый build.gradle будет выглядеть следующим образом
plugins {
id 'java'
id 'application'
id 'org.springframework.boot' version '2.1.0.RELEASE'
id 'com.bmuschko.docker-remote-api' version '4.8.0'
}
import com.bmuschko.gradle.docker.tasks.container.DockerCreateContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStartContainer
import com.bmuschko.gradle.docker.tasks.container.DockerRemoveContainer
import com.bmuschko.gradle.docker.tasks.container.DockerStopContainer
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import com.bmuschko.gradle.docker.tasks.image.DockerRemoveImage
import com.bmuschko.gradle.docker.tasks.image.Dockerfile
import com.bmuschko.gradle.docker.tasks.network.DockerCreateNetwork
def dockerBuildDir = 'build/docker/'
def imageVersion = '1.0'
def uniqueContainerName = 'test_docker_plugin'
def postgresHostDir = '/tmp/test_db'
def postgresDockerDir = '/var/lib/postgresql/data'
def dbContainerName = 'test_db'
def dbNetworkName = 'test_network'
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-data-jpa:2.1.0.RELEASE'
compile 'org.projectlombok:lombok:1.18.4'
compile 'org.postgresql:postgresql:42.2.5'
}
application {
mainClassName = 'com.tune_it.App'
}
jar {
manifest {
attributes 'Main-Class': 'com.tune_it.App'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
task createDockerfile(type: Dockerfile) {
destFile = project.file("$dockerBuildDir/Dockerfile")
from 'openjdk:8-jre-alpine'
copyFile jar.archiveName, '/app/test_service.jar'
entryPoint 'java'
defaultCommand '-jar', '/app/test_service.jar'
exposePort 8080
runCommand 'apk --update --no-cache add curl'
instruction 'HEALTHCHECK CMD curl -f http://localhost:8080/hello || exit 1'
}
task syncJar(type: Copy) {
dependsOn assemble
from jar.archivePath
into dockerBuildDir
}
task stopContainer(type: DockerStopContainer) {
targetContainerId("$uniqueContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeContainer(type: DockerRemoveContainer) {
dependsOn stopContainer
targetContainerId("$uniqueContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeImage(type: DockerRemoveImage) {
dependsOn removeContainer
targetImageId("adpashnin/gradle_docker_plugin:$imageVersion")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task buildImage(type: DockerBuildImage) {
dependsOn createDockerfile, syncJar
inputDir = project.file(dockerBuildDir)
tags = ["adpashnin/gradle_docker_plugin:$imageVersion"]
}
task createContainer(type: DockerCreateContainer) {
dependsOn buildImage, removeContainer
targetImageId buildImage.getImageId()
containerName = "$uniqueContainerName"
portBindings = ['8080:8080']
network = "$dbNetworkName"
networkAliases = ["web_host"]
}
task startContainer(type: DockerStartContainer) {
dependsOn createContainer
targetContainerId("$uniqueContainerName")
}
task createNetwork(type: DockerCreateNetwork) {
networkId = "$dbNetworkName"
}
task createDbContainer(type: DockerCreateContainer) {
targetImageId "postgres:11.0"
containerName = "$dbContainerName"
volumes = ["$postgresHostDir:$postgresDockerDir"]
network = "$dbNetworkName"
networkAliases = ["db_host"]
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task startDbContainer(type: DockerStartContainer) {
targetContainerId("$dbContainerName")
}
task stopDbContainer(type: DockerStopContainer) {
targetContainerId("$dbContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
task removeDbContainer(type: DockerRemoveContainer) {
dependsOn stopDbContainer
targetContainerId("$dbContainerName")
onError { exc ->
if (exc.message!=null && !exc.message.contains('NotModifiedException')) {
throw new RuntimeException(exc)
}
}
}
Протестируем
$ gradle createNetwork
$ gradle createDbContainer
$ gradle startDbContainer
$ gradle startContainer
$ curl localhost:8080/hello
Hello 1 timesadpashnin
$ curl localhost:8080/hello
Hello 2 times
$ curl localhost:8080/hello
Hello 3 times
Успех! И теперь оно работает:D
В случае, если необходимо как-то кастомизировать базу данных, можно повторить для неё процедуру создания собственного образа на основе образа базы данных.
На это всё, спасибо за внимание