Рубрики
Без рубрики

Встроенный сервер MySQL Spring

В этой статье описывается метод создания процесса mysqld, управляемого приложением Spring Boot con… С тегами java, spring, mysql.

В этой статье описывается метод создания mysql Процесс , управляемый приложением Spring Boot, зависит от определения свойства application.properties , ${mysqld.home} . Если свойство определено, соответствующий компонент @Configuration с помощью invoke mysqld с опцией --initialize-insecure для создания базы данных, а затем создания и управления mysqld |/Процесс в течение срока службы приложения Spring Boot, включая плавное завершение работы при завершении работы приложения.

Предоставляется полный javadoc .

Теория работы

Конфигурация Mysqld представляет собой @ConditionalOnProperty аннотацию для ${mysqld.home} ; если свойство определено, Процесс @Bean создается под управлением сервера MySQL. Если ${mysqld.datadir} не существует, mysqld вызывается с параметром --initialize-insecure для первого создания базы данных. Определен метод @PreDestroy для уничтожения mysqld Процесс при завершении работы приложения.

@Configuration
@ConditionalOnProperty(name = "mysqld.home", havingValue = "")
@NoArgsConstructor @ToString @Log4j2
public class MysqldConfiguration {
    @Value("${mysqld.home}")
    private File home;

    @Value("${mysqld.defaults.file:${mysqld.home}/my.cnf}")
    private File defaults;

    @Value("${mysqld.datadir:${mysqld.home}/data}")
    private File datadir;

    @Value("${mysqld.port}")
    private Integer port;

    @Value("${mysqld.socket:${mysqld.home}/socket}")
    private File socket;

    @Value("${logging.path}/mysqld.log")
    private File console;

    private volatile Process mysqld = null;

    ...

    @Bean
    public Process mysqld() throws IOException {
        if (mysqld == null) {
            synchronized (this) {
                if (mysqld == null) {
                    Files.createDirectories(home.toPath());
                    Files.createDirectories(datadir.toPath().getParent());
                    Files.createDirectories(console.toPath().getParent());

                    String defaultsArg = "--no-defaults";

                    if (defaults.exists()) {
                        defaultsArg = + defaults.getAbsolutePath();
                    }

                    String datadirArg = + datadir.getAbsolutePath();
                    String socketArg = + socket.getAbsolutePath();
                    String portArg = + port;

                    if (! datadir.exists()) {
                        try {
                            new ProcessBuilder("mysqld", defaultsArg, datadirArg, "--initialize-insecure")
                                .directory(home)
                                .inheritIO()
                                .redirectOutput(Redirect.to(console))
                                .redirectErrorStream(true)
                                .start()
                                .waitFor();
                        } catch (InterruptedException exception) {
                        }
                    }

                    if (datadir.exists()) {
                        socket.delete();

                        mysqld =
                            new ProcessBuilder("mysqld", defaultsArg, datadirArg, socketArg, portArg)
                            .directory(home)
                            .inheritIO()
                            .redirectOutput(Redirect.appendTo(console))
                            .redirectErrorStream(true)
                            .start();

                        while (! socket.exists()) {
                            try {
                                mysqld.waitFor(15, SECONDS);
                            } catch (InterruptedException exception) {
                            }

                            if (mysqld.isAlive()) {
                                continue;
                            } else {
                                throw new IllegalStateException("mysqld not started");
                            }
                        }
                    } else {
                        throw new IllegalStateException("mysqld datadir does not exist");
                    }
                }
            }
        }

        return mysqld;
    }

    @PreDestroy
    public void destroy() {
        if (mysqld != null) {
            try {
                for (int i = 0; i < 8; i+= 1) {
                    if (mysqld.isAlive()) {
                        mysqld.destroy();
                        mysqld.waitFor(15, SECONDS);
                    } else {
                        break;
                    }
                }
            } catch (InterruptedException exception) {
            }

            try {
                if (mysqld.isAlive()) {
                    mysqld.destroyForcibly().waitFor(60, SECONDS);
                }
            } catch (InterruptedException exception) {
            }
        }
    }
}

Сервер mysqld настроен с параметром --socket=${mysqld.socket} с целью уведомления приложения Spring Boot о запуске сервера: В то время как MySQL Connector/J не поддерживает доменные сокеты UNIX, приведенный выше код ожидает, пока сервер mysqld создаст сокет, чтобы убедиться, что сервер запущен, прежде чем продолжить. Компонент Mysqld будет просто отслеживать, что Процесс все еще активен. Этот @Component зависит от mysqld @Bean который, в свою очередь, зависит от свойства ${mysqld.home} .

@Component
@ConditionalOnBean(name = { "mysqld" })
@NoArgsConstructor @ToString @Log4j2
public class MysqldComponent {
    @Autowired private Process mysqld;

    @Scheduled(fixedRate = 15 * 1000)
    public void run() {
        if (mysqld != null) {
            if (mysqld.isAlive()) {
                try {
                    mysqld.waitFor(15, SECONDS);
                } catch (InterruptedException exception) {
                }
            } else {
                throw new IllegalStateException("mysqld is not running");
            }
        }
    }
}

В соответствии с указаниями Справочного руководства по пружинным ботинкам , Компонент EntityManagerFactory предоставляется для указания mysqld Process требуется JPA.

@Component
@ConditionalOnProperty(name = "mysqld.home", havingValue = "")
@ToString @Log4j2
public class EntityManagerFactoryComponent extends EntityManagerFactoryDependsOnPostProcessor {
    @Autowired private Process mysqld;

    public EntityManagerFactoryComponent() { super("mysqld"); }
}

Приложение может интегрировать эту функциональность, аннотируя некоторый компонент (предположительно, тот, который зависит от Репозитория или JpaRepository ) с помощью:

@Component
@ComponentScan(basePackageClasses = { ball.spring.mysqld.MysqldComponent.class })
public class SomeComponent {
    ...
}

Сценарий оболочки

Следующий сценарий оболочки может быть помещен в каталог ${mysql.home} , чтобы удобно запускать сервер MySQL из оболочки.

#!/bin/bash

PRG="$0"

while [ -h "$PRG" ]; do
    ls=$(ls -ld "$PRG")
    link=$(expr "$ls" : '.*-> \(.*\)$')
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=$(dirname "$PRG")"/$link"
    fi
done

cd $(dirname "$PRG")

MYCNF=$(pwd)/my.cnf
DATADIR=$(pwd)/data
SOCKET=$(pwd)/socket

if [ ! -f "${MYCNF}" ]; then
    cat > "${MYCNF}" <

Резюме

Описанный здесь метод может быть использован с другими приложениями баз данных (например, PostgreSQL).

Оригинал: “https://dev.to/allenball/spring-embedded-mysql-server-2j14”