Spring嵌入式MySQL服务器

Spring Embedded MySQL Server

Introduction

This article describes a method to create a mysqld Process managed by the Spring Boot Application conditioned on the definition of an application.properties property, ${mysqld.home}. If the property is defined, the corresponding bean @Configuration with invoke mysqld with the --initialize-insecure option to create the database and then create and manage the mysqld Process for the life of the Spring Boot application including graceful shutdown at application shutdown.

Complete javadoc is provided.

Theory of Operation

The MysqldConfiguration is @ConditionalOnProperty for ${mysqld.home}; if the property is defined, the Process @Bean is created running the MySQL server. If the ${mysqld.datadir} does not exist, mysqld is invoked with the --initialize-insecure option to create the database first. A @PreDestroy method is defined to destroy the mysqld Process at application shutdown.

@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-file=" + defaults.getAbsolutePath();
                    }

                    String datadirArg = "--datadir=" + datadir.getAbsolutePath();
                    String socketArg = "--socket=" + socket.getAbsolutePath();
                    String portArg = "--port=" + 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) {
            }
        }
    }
}

The mysqld server is configured with the --socket=${mysqld.socket} option for the purpose of notifying the Spring Boot Application that the server has started: While the MySQL Connector/J does not support UNIX domain sockets, the above code waits for the mysqld server to create the socket to be sure the server is running before continuing. The MysqldComponent will simply monitor that the Process is still alive. This @Component is dependent on the mysqld @Bean which in turn is dependent on the ${mysqld.home} property.

@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");
            }
        }
    }
}

Per the direction of the Spring Boot Reference Guide, EntityManagerFactoryComponent is provided to indicate the mysqld Process is required by JPA.

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

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

An application may integrate this functionality by annotating some component (presumably one that depends on a Repository or JpaRepository) with:

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

Shell Script

以下shell脚本可能会放入$ {mysqld.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}" <<EOF
[mysqld]
general_log = ON
log_output = TABLE
EOF
fi

DEFAULTS_OPT=--no-defaults
DATADIR_OPT=--datadir="${DATADIR}"

if [ -f "${MYCNF}" ]; then
    DEFAULTS_OPT=--defaults-file="${MYCNF}"
fi

if [ ! -d "${DATADIR}" ]; then
    mysqld "${DEFAULTS_OPT}" "${DATADIR_OPT}" --initialize-insecure
fi

exec mysqld "${DEFAULTS_OPT}" "${DATADIR_OPT}" --socket="${SOCKET}"

Summary

此处描述的技术可以与其他数据库应用程序(例如PostgreSQL)一起使用。

from: https://dev.to//allenball/spring-embedded-mysql-server-2j14

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值