在现在的后端开发中,只要是运用联系型
数据库 ,信任SSM架构(Spring Boot + MyBatis)已经成为首选。
不过在咱们第一次运转或许布置项目的时分,一般要先手动衔接数据库,履行一个SQL文件以创立数据库以及数据库表格完结
数据库的初始化作业 ,这样咱们的SSM应用程序才能够正常作业。
这样也对实际布置或许是容器化造成了一些麻烦,必须先手动初始化数据库再发动应用程序。
那能不能
让咱们的SSM应用程序第一次发动时,主动地帮咱们履行SQL文件以完结数据库初始化作业呢?
这样事实上是没问题的,今日就以Spring Boot + MyBatis为例,运用MySQL作为数据库,完结上述的数据库初始化功用。
咱们能够编写一个装备类,在一个标示了
@PostConstruct
注解的办法中编写
初始化数据库的逻辑 ,这样应用程序发动时,就会履行该办法协助咱们完结数据库的初始化作业。
那么这个初始化数据库的逻辑大概是什么呢?能够总结为如下过程:
首要测验衔接用户装备的地址 ,若衔接抛出反常阐明地址中指定的数据库不存在 ,需求创立数据库并初始化数据,不然就不需求初始化,直接退出初始化逻辑
若要履行初始化,首要从头拼装用户装备的衔接地址,使得本次衔接不再是衔接至详细的数据库 ,并履行create database
句子完结数据库创立
创立完结数据库后,再次运用用户装备的衔接地址,这时数据库创立完结就能够成功衔接上了!这时再履行SQL文件初始化表格即可
上述逻辑中咱们能够会有下列的疑问:
第一步中,为什么衔接抛出反常阐明地址中指定的数据库不存在 ?
第二步中,什么是 “使得本次衔接不再是衔接至详细的数据库” ?
假定用户装备的衔接地址是
jdbc:mysql://127.0.0.1:3306/init_demo
,信任这个咱们十分熟悉了,它表明:
衔接的MySQL地址是127.0.0.1
,端口是3306
,并且衔接到该MySQL中名为init_demo
的数据库中 。
那么假如MySQL中
init_demo
的库并
不存在 ,Spring Boot还测验衔接上述地址的话,就会抛出
SQLException
反常:
所以在这儿能够将
是否抛出SQLException
反常 作为判别
应用程序是否是第一次布置发动 的条件。
好的,已然数据库不存在,咱们就要创立数据库,但是上述地址衔接不上啊!怎样创立呢?
正是由于上述地址中指定了要衔接的详细数据库,而数据库又不存在,才会衔接失利,那能不能
衔接时不指定数据库,仅仅是衔接到MySQL上 就行呢?当然能够,咱们将上述的衔接地址改成:
jdbc:mysql://127.0.0.1:3306/
,就能够衔接成功了!
不过一般SSM应用程序中,装备数据库地址都是要指定库名的,因而咱们待会在装备类编写初始化数据库逻辑时,从头拼装一下用户给的装备衔接地址即可,即把
jdbc:mysql://127.0.0.1:3306/init_demo
经过代码处理成
jdbc:mysql://127.0.0.1:3306/
并主张衔接即可,这便是上述说的第二步。
第二步完结了数据库的创立,第三步便是完结表格创立了!表格创立就写在SQL文件里即可,由于数据库创立好了,咱们在第三步中又能够从头运用用户给的装备地址
jdbc:mysql://127.0.0.1:3306/init_demo
再次衔接并履行SQL文件完结初始化了!
上述过程中,咱们将运用JDBC自带的接口完结数据库衔接等等,而不是运用MyBatis的
SqlSessionFactory
,由于咱们第二步需求改动衔接地址。
下面,咱们就来完结一下。
首要是在本地或许其它地方树立好MySQL服务器,这儿就不再赘述怎样去树立MySQL了。
我这儿在本地树立了MySQL服务器,下面经过Spring Boot进行衔接。
首要创立一个Spring Boot应用程序,并集成好MySQL驱动和MyBatis支持,我这儿的依靠如下:
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-web</artifactId >
</dependency >
<dependency >
<groupId > org.mybatis.spring.boot</groupId >
<artifactId > mybatis-spring-boot-starter</artifactId >
<version > 3.0.2</version >
</dependency >
<dependency >
<groupId > com.mysql</groupId >
<artifactId > mysql-connector-j</artifactId >
<scope > runtime</scope >
</dependency >
<dependency >
<groupId > cn.hutool</groupId >
<artifactId > hutool-all</artifactId >
<version > 5.8.16</version >
</dependency >
<dependency >
<groupId > org.projectlombok</groupId >
<artifactId > lombok</artifactId >
<scope > provided</scope >
</dependency >
<dependency >
<groupId > org.springframework.boot</groupId >
<artifactId > spring-boot-starter-test</artifactId >
<scope > test</scope >
</dependency >
然后在装备文件
application.yml
中加入下列装备:
spring:
datasource:
url: "jdbc:mysql://127.0.0.1:3306/init_demo?serverTimezone=GMT%2B8"
username: "swsk33"
password: "dev-2333"
这便是正常的数据库衔接装备,不再过多讲述。我这儿运用
yaml
格局装备文件,咱们也能够运用
properties
格局的装备文件。
这儿先给出这个装备类的代码:
package com.gitee.swsk33.sqlinitdemo.config;
import cn.hutool.core.io.resource.ClassPathResource;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.jdbc.ScriptRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
@Slf4j
@Configuration
public class DatabaseInitialize {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
private boolean currentDatabaseExists () {
try {
Connection connection = DriverManager.getConnection(url, username, password);
connection.close();
} catch (SQLException e) {
return false ;
}
return true ;
}
private void runSQLScript (String path, boolean isClasspath, Connection connection) {
try (InputStream sqlFileStream = isClasspath ? new ClassPathResource (path).getStream() : new FileInputStream (path)) {
BufferedReader sqlFileStreamReader = new BufferedReader (new InputStreamReader (sqlFileStream, StandardCharsets.UTF_8));
ScriptRunner scriptRunner = new ScriptRunner (connection);
scriptRunner.runScript(sqlFileStreamReader);
sqlFileStreamReader.close();
} catch (Exception e) {
log.error("读取文件或许履行脚本失利!" );
e.printStackTrace();
}
}
private void createDatabase () {
try {
URI databaseURI = new URI (url.replace("jdbc:" , "" ));
String databasePlatform = databaseURI.getScheme();
String hostAndPort = databaseURI.getAuthority();
String databaseName = databaseURI.getPath().substring(1 );
String newURL = "jdbc:" + databasePlatform + "://" + hostAndPort + "/" ;
Connection connection = DriverManager.getConnection(newURL, username, password);
Statement statement = connection.createStatement();
statement.execute("create database if not exists `" + databaseName + "`" );
statement.close();
connection.close();
log.info("创立数据库完结!" );
} catch (URISyntaxException e) {
log.error("数据库衔接URL格局过错!" );
throw new RuntimeException (e);
} catch (SQLException e) {
log.error("衔接失利!" );
throw new RuntimeException (e);
}
}
@PostConstruct
private void initDatabase () {
log.info("开端查看数据库是否需求初始化..." );
if (currentDatabaseExists()) {
log.info("数据库存在,不需求初始化!" );
return ;
}
log.warn("数据库不存在!预备履行初始化过程..." );
createDatabase();
try (Connection connection = DriverManager.getConnection(url, username, password)) {
runSQLScript("/create-table.sql" , true , connection);
log.info("初始化表格完结!" );
} catch (Exception e) {
log.error("初始化表格时,衔接数据库失利!" );
e.printStackTrace();
}
}
}
上述代码中,有下列要点:
咱们运用@Value
注解读取了装备文件中数据库的衔接信息,包括衔接地址、用户名和暗码
上述currentDatabaseExists
办法用于测验运用装备的地址进行衔接 ,假如抛出SQLException
反常则判别装备的地址中,指定的数据库是不存在的,这儿的代码主要是完结了上述初始化逻辑中的第一步
上述createDatabase
办法用于从头拼装用户的衔接地址,使其不再是衔接到指定数据库,然后履行SQL句子完结数据库的创立 ,咱们运用Java的URI
类解析用户装备的衔接地址,便于咱们拆分然后拼装衔接地址,并获取用户要运用的数据库名,对其进行创立,这儿的代码完结了上述初始化逻辑中的第二步
上述initDatabase
办法是会被主动履行 的,它调用了currentDatabaseExists
和createDatabase
办法,组合起来一切的过程,在其间完结了第一步和第二步后,从头运用用户装备的地址主张衔接并履行SQL脚本以初始化表 ,这个办法包括 了上述初始化逻辑中的第三步
上述runSQLScript
办法用于衔接数据库后履行SQL脚本 ,其间ScriptRunner
类是由MyBatis供给的运转SQL脚本的有用类,其结构函数需求传入JDBC的数据库衔接目标Connection
目标,然后上述我还设定了形参isClasspath
,能够让用户自定义是读取文件体系中的SQL脚本还是classpath
中的SQL脚本
上述的初始化表格脚本坐落工程目录的
src/main/resources/create-table.sql
,即
classpath
中,内容如下:
drop table if exists `user `;
create table `user `
(
`id` int unsigned auto_increment,
`username` varchar (16 ) not null ,
`password` varchar (32 ) not null ,
primary key (`id`)
) engine = InnoDB
default charset = utf8mb4;
好的,现在先保证MySQL数据库中
不存在 init_demo
的库,发动程序试试:
可见成功地完结了数据库的检测、初始化作业,也可见
ScriptRunner
在履行SQL的时分会在操控台输出履行的句子。
现在再从头发动一下程序试试:
可见第2次发动时,名为
init_demo
的数据库已经存在了,这时就
不需求 履行初始化逻辑了!
假定现在有一个类,在初始化为Bean的时分需求拜访数据库,例如:
@Slf4j
@Component
public class UserServiceDemo {
@Autowired
private UserDAO userDAO;
@PostConstruct
private void init () {
log.info("履行数据库测试拜访..." );
userDAO.add(new User (0 , "用户名" , "暗码" ));
List <User > users = userDAO.getAll();
for (User user : users) {
System .out.println(user);
}
}
}
这个类在被初始化为Bean的时分,就需求拜访数据库进行读写操作,那问题来了,假如这个类
UserServiceDemo
在上述数据库初始化类
DatabaseInitialize
之前 被初始化了怎样办呢?这会导致数据库还没有被初始化时,
UserServiceDemo
就去拜访数据库,导致初始化失利。
这时,咱们能够运用
@DependsOn
注解,这个注解能够操控
UserServiceDemo
在
DatabaseInitialize
初始化之后再进行初始化:
@Slf4j
@Component
@DependsOn ("databaseInitialize" )
public class UserServiceDemo {
}
在这儿咱们在
UserServiceDemo
上标示了注解
@DependsOn
,并传入
databaseInitialize
作为参数,
表明UserServiceDemo
这个类是依靠于名(id)为databaseInitialize
的Bean的 ,这样Spring Boot就会在
DatabaseInitialize
初始化之后再初始化
UserServiceDemo
。
标示了@Component
等等的类,默认情况下被初始化为Bean的时分,其名称是其类名的小驼峰方式,例如上述的DatabaseInitialize
类,初始化为Bean时姓名默认为databaseInitialize
,因而上述@DependsOn
注解就传入databaseInitialize
。
现在删去
init_demo
库,再次发动应用程序:
可见在初始化数据库后,又成功地在发动时拜访了数据库。
本文以Spring Boot + Mybatis为例,运用MySQL数据库,完结了SSM应用程序第一次发动时主动检测并完结数据库初始化的功用,理论上上述方式适用于一切的联系型数据库,咱们稍作修正即可。
本文仅仅是我自己供给的思路,以及部分内容也是和“机器朋友”沟通后的成果,假如咱们对此有更好的思路,欢迎在谈论区提出您的主张。
本文代码的仓库地址:传送门