Spring Boot實現第一次啟動時自動初始化數據庫
在現在的后端開發(fā)中,只要是運用聯(lián)系型數據庫,信任SSM架構(Spring Boot + MyBatis)已經成為首選。 不過在咱們第一次運轉或許布置項目的時分,一般要先手動銜接數據庫,履行一個SQL文件以創(chuàng)立數據庫以及數據庫表格完結數據庫的初始化作業(yè),這樣咱們的SSM應用程序才能夠正常作業(yè)。 這樣也對實際布置或許是容器化造成了一些麻煩,必須先手動初始化數據庫再發(fā)動應用程序。 那能不能讓咱們的SSM應用程序第一次發(fā)動時,主動地幫咱們履行SQL文件以完結數據庫初始化作業(yè)呢? 這樣事實上是沒問題的,今日就以Spring Boot + MyBatis為例,運用MySQL作為數據庫,完結上述的數據庫初始化功用。
1,全體思路
咱們能夠編寫一個裝備類,在一個標示了@PostConstruct
注解的辦法中編寫初始化數據庫的邏輯,這樣應用程序發(fā)動時,就會履行該辦法協(xié)助咱們完結數據庫的初始化作業(yè)。 那么這個初始化數據庫的邏輯大概是什么呢?能夠總結為如下過程:
- 首要測驗銜接用戶裝備的地址,若銜接拋出反常闡明地址中指定的數據庫不存在,需求創(chuàng)立數據庫并初始化數據,不然就不需求初始化,直接退出初始化邏輯
- 若要履行初始化,首要從頭拼裝用戶裝備的銜接地址,使得本次銜接不再是銜接至詳細的數據庫,并履行
create database
句子完結數據庫創(chuàng)立 - 創(chuàng)立完結數據庫后,再次運用用戶裝備的銜接地址,這時數據庫創(chuàng)立完結就能夠成功銜接上了!這時再履行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
反常作為判別應用程序是否是第一次布置發(fā)動的條件。 好的,已然數據庫不存在,咱們就要創(chuàng)立數據庫,但是上述地址銜接不上啊!怎樣創(chuàng)立呢? 正是由于上述地址中指定了要銜接的詳細數據庫,而數據庫又不存在,才會銜接失利,那能不能銜接時不指定數據庫,僅僅是銜接到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/
并主張銜接即可,這便是上述說的第二步。 第二步完結了數據庫的創(chuàng)立,第三步便是完結表格創(chuàng)立了!表格創(chuàng)立就寫在SQL文件里即可,由于數據庫創(chuàng)立好了,咱們在第三步中又能夠從頭運用用戶給的裝備地址jdbc:mysql://127.0.0.1:3306/init_demo
再次銜接并履行SQL文件完結初始化了! 上述過程中,咱們將運用JDBC自帶的接口完結數據庫銜接等等,而不是運用MyBatis的SqlSessionFactory
,由于咱們第二步需求改動銜接地址。 下面,咱們就來完結一下。
2,詳細完結
首要是在本地或許其它地方樹立好MySQL服務器,這兒就不再贅述怎樣去樹立MySQL了。 我這兒在本地樹立了MySQL服務器,下面經過Spring Boot進行銜接。
(1) 創(chuàng)立應用程序并裝備
首要創(chuàng)立一個Spring Boot應用程序,并集成好MySQL驅動和MyBatis支持,我這兒的依靠如下:
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<!-- MySQL銜接支持 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Hutool有用工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
<!-- Lombok注解 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Spring Boot測試 -->
<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
格局的裝備文件。
(2) 編寫裝備類完結數據庫的檢測和初始化邏輯
這兒先給出這個裝備類的代碼:
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;
/**
* 用于第一次發(fā)動時,初始化數據庫的裝備類
*/
@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;
/**
* 檢測當前銜接的庫是否存在(銜接URL中的數據庫)
*
* @return 當前銜接的庫是否存在
*/
private boolean currentDatabaseExists() {
// 測驗以裝備文件中的URL樹立銜接
try {
Connection connection = DriverManager.getConnection(url, username, password);
connection.close();
} catch (SQLException e) {
// 若銜接拋出反常則闡明銜接URL中指定數據庫不存在
return false;
}
// 正常情況下闡明銜接URL中數據庫存在
return true;
}
/**
* 履行SQL腳本
*
* @param path SQL腳本文件的路徑
* @param isClasspath SQL腳本路徑是否是classpath路徑
* @param connection 數據庫銜接目標,經過這個銜接履行腳本
*/
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));
// 創(chuàng)立SQL腳本履行器目標
ScriptRunner scriptRunner = new ScriptRunner(connection);
// 運用SQL腳本履行器目標履行腳本
scriptRunner.runScript(sqlFileStreamReader);
// 最后關閉文件讀取器
sqlFileStreamReader.close();
} catch (Exception e) {
log.error("讀取文件或許履行腳本失利!");
e.printStackTrace();
}
}
/**
* 履行SQL腳本以創(chuàng)立數據庫
*/
private void createDatabase() {
try {
// 修正銜接句子,從頭樹立銜接
// 從頭樹立的銜接不再銜接到指定庫,而是直接銜接到整個MySQL
// 運用URI類解析并拆解銜接地址,從頭拼裝
URI databaseURI = new URI(url.replace("jdbc:", ""));
// 得到銜接地址中的數據庫平臺名(例如mysql)
String databasePlatform = databaseURI.getScheme();
// 得到銜接地址和端口
String hostAndPort = databaseURI.getAuthority();
// 得到銜接地址中的庫名
String databaseName = databaseURI.getPath().substring(1);
// 拼裝新的銜接URL,不銜接至指定庫
String newURL = "jdbc:" + databasePlatform + "://" + hostAndPort + "/";
// 從頭樹立銜接
Connection connection = DriverManager.getConnection(newURL, username, password);
Statement statement = connection.createStatement();
// 履行SQL句子創(chuàng)立數據庫
statement.execute("create database if not exists `" + databaseName + "`");
// 關閉會話和銜接
statement.close();
connection.close();
log.info("創(chuàng)立數據庫完結!");
} catch (URISyntaxException e) {
log.error("數據庫銜接URL格局過錯!");
throw new RuntimeException(e);
} catch (SQLException e) {
log.error("銜接失利!");
throw new RuntimeException(e);
}
}
/**
* 該辦法用于檢測數據庫是否需求初始化,假如是則履行SQL腳本進行初始化操作
*/
@PostConstruct
private void initDatabase() {
log.info("開端查看數據庫是否需求初始化...");
// 檢測當前銜接數據庫是否存在
if (currentDatabaseExists()) {
log.info("數據庫存在,不需求初始化!");
return;
}
log.warn("數據庫不存在!預備履行初始化過程...");
// 先創(chuàng)立數據庫
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句子完結數據庫的創(chuàng)立,咱們運用Java的URI
類解析用戶裝備的銜接地址,便于咱們拆分然后拼裝銜接地址,并獲取用戶要運用的數據庫名,對其進行創(chuàng)立,這兒的代碼完結了上述初始化邏輯中的第二步 - 上述
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`;
-- 創(chuàng)立表格
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
的庫,發(fā)動程序試試:
可見成功地完結了數據庫的檢測、初始化作業(yè),也可見ScriptRunner
在履行SQL的時分會在操控臺輸出履行的句子。 現在再從頭發(fā)動一下程序試試:
可見第2次發(fā)動時,名為init_demo
的數據庫已經存在了,這時就不需求履行初始化邏輯了!
(3) 假如有的Bean初始化時需求拜訪數據庫
假定現在有一個類,在初始化為Bean的時分需求拜訪數據庫,例如:
// 省略package和import
/**
* 發(fā)動時需求查詢數據庫的Beans
*/
@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的Bean
// 這樣能夠使得databaseInitialize這個Bean(咱們的數據庫查看類)先被初始化,并履行完結數據庫初始化后再初始化本類,以順利拜訪數據庫
@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
庫,再次發(fā)動應用程序:
可見在初始化數據庫后,又成功地在發(fā)動時拜訪了數據庫。文章來源:http://www.zghlxwxcb.cn/news/detail-772820.html
3,總結
本文以Spring Boot + Mybatis為例,運用MySQL數據庫,完結了SSM應用程序第一次發(fā)動時主動檢測并完結數據庫初始化的功用,理論上上述方式適用于一切的聯(lián)系型數據庫,咱們稍作修正即可。文章來源地址http://www.zghlxwxcb.cn/news/detail-772820.html
到了這里,關于Spring Boot實現第一次啟動時自動初始化數據庫的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!