Spring boot 随笔 1 DatasourceInitializer

news2024/11/25 20:18:39

0. 为啥感觉升级了 win11 之后,电脑像是刚买回来的,很快

这篇加餐完全是一个意外:时隔两年半,再看 Springboot-quartz-starter 集成实现的时候,不知道为啥我的h2 在应用启动的时候,不能自动创建quartz相关的schema。后面看了 springboot 的文档,据说是可以做到的,AI也是这么说的。

没办法,只能看 QuartzAutoConfiguration 源码了。于是乎,就有了这么个好活

没办法,就当是一个支线任务了

1. AbstractScriptDatabaseInitializer

请添加图片描述

下面是熟悉的,阉割后的 源码

package org.springframework.boot.sql.init;

/**
 * Base class for an {@link InitializingBean} that performs SQL database initialization
 * using schema (DDL) and data (DML) scripts.
 *
 * @author Andy Wilkinson
 * @since 2.5.0
 */
public abstract class AbstractScriptDatabaseInitializer implements ResourceLoaderAware, InitializingBean {

	// 构造入参配置
	private final DatabaseInitializationSettings settings;
	private volatile ResourceLoader resourceLoader;

	@Override
	public void afterPropertiesSet() throws Exception {
		// 初始化后,就执行逻辑了
		initializeDatabase();
	}

	/**
	 * Initializes the database by applying schema and data scripts.
	 * @return {@code true} if one or more scripts were applied to the database, otherwise
	 * {@code false}
	 */
	public boolean initializeDatabase() {
		ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
		// 先后执行 schema, data 的脚本
		boolean initialized = applySchemaScripts(locationResolver);
		return applyDataScripts(locationResolver) || initialized;
	}

	// 真正执行脚本前,会走这个判断,决定是否要执行脚本
	private boolean isEnabled() {
		if (this.settings.getMode() == DatabaseInitializationMode.NEVER) {
			return false;
		}
		return this.settings.getMode() == DatabaseInitializationMode.ALWAYS || isEmbeddedDatabase();
	}

	/**
	 * Returns whether the database that is to be initialized is embedded.
	 * @return {@code true} if the database is embedded, otherwise {@code false}
	 * @since 2.5.1
	 */
	protected boolean isEmbeddedDatabase() {
		throw new IllegalStateException(
				"Database initialization mode is '" + this.settings.getMode() + "' and database type is unknown");
	}

	private boolean applySchemaScripts(ScriptLocationResolver locationResolver) {
		return applyScripts(this.settings.getSchemaLocations(), "schema", locationResolver);
	}

	private boolean applyDataScripts(ScriptLocationResolver locationResolver) {
		return applyScripts(this.settings.getDataLocations(), "data", locationResolver);
	}

	private boolean applyScripts(List<String> locations, String type, ScriptLocationResolver locationResolver) {
		List<Resource> scripts = getScripts(locations, type, locationResolver);
		if (!scripts.isEmpty() && isEnabled()) {
			runScripts(scripts);
			return true;
		}
		return false;
	}

	// 根据配置的 路径的字符串 -> spring.Resource 类型
	private List<Resource> getScripts(List<String> locations, String type, ScriptLocationResolver locationResolver) {
		if (CollectionUtils.isEmpty(locations)) {
			return Collections.emptyList();
		}
		List<Resource> resources = new ArrayList<>();
		for (String location : locations) {
			for (Resource resource : doGetResources(location, locationResolver)) {
				if (resource.exists()) {
					resources.add(resource);
				}
			}
		}
		return resources;
	}

	private List<Resource> doGetResources(String location, ScriptLocationResolver locationResolver) {
		return locationResolver.resolve(location);
	}

	private void runScripts(List<Resource> resources) {
		runScripts(resources, this.settings.isContinueOnError(), this.settings.getSeparator(),
				this.settings.getEncoding());
	}

	protected abstract void runScripts(List<Resource> resources, boolean continueOnError, String separator,
			Charset encoding);

	private static class ScriptLocationResolver {
		private final ResourcePatternResolver resourcePatternResolver;
		private List<Resource> resolve(String location) throws IOException {
			// ...
		}
	}

}

再看几个它的实现类,加载上配置类,基本上,可以知道它的使用方法了

2. 吾のDemo

始于测试类

package org.pajamas.spring.boot;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.pajamas.example.starter.core.entity.AlbumEntity;
import org.pajamas.example.starter.core.repo.AlbumRepo;
import org.pajamas.example.test.AbstractApplicationTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;

import java.util.List;

/**
 * @author william
 * @since 2024/5/30
 */
@DisplayName("what the interesting component")
@TestPropertySource(properties = {
        "spring.application.name=service-example-test",
        // 屏蔽 liquibase 的干扰 
        "spring.liquibase.enabled=false"
})
@Import(ExampleDatabaseInitializer.class)
public class DatabaseInitializerTest extends AbstractApplicationTest {
	// 其实就,一个 jpa 实体类的 repository
    @Autowired
    AlbumRepo repo;

    // @Disabled
    @DisplayName("execute DDL, DML automatically, as App startup")
    @Test
    public void t0() throws Exception {
    	// 预期的结果:启动启动时,自动创建表,并插入一条记录
        List<AlbumEntity> all = this.repo.findAll();
        printErr(all);
    }
}

既然是测试,就走简单的方式,注册这个bean

package org.pajamas.spring.boot;

import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;

import java.util.Collections;

import javax.sql.DataSource;

/**
 * @author william
 * @since 2024/5/30
 */
public class ExampleDatabaseInitializer extends SqlDataSourceScriptDatabaseInitializer {
    public ExampleDatabaseInitializer(DataSource dataSource) {
        super(dataSource, getProperty());
    }

    private static SqlInitializationProperties getProperty() {
        SqlInitializationProperties properties = new SqlInitializationProperties();
        properties.setSchemaLocations(Collections.singletonList("classpath:sql/schema.sql"));
        properties.setDataLocations(Collections.singletonList("classpath:sql/data.sql"));
        properties.setMode(DatabaseInitializationMode.ALWAYS);
        properties.setContinueOnError(false);
        return properties;
    }
}

schema.sql

CREATE TABLE IF NOT EXISTS `t_album`
(
    `id`             bigint NOT NULL AUTO_INCREMENT,
    `album_name`     varchar(32)                                                  DEFAULT NULL COMMENT 'album name',
    `album_year`     int                                                          DEFAULT NULL COMMENT 'album publish year',
    `create_date`    timestamp NULL DEFAULT NULL,
    `create_user_id` bigint                                                       DEFAULT NULL,
    `update_date`    timestamp NULL DEFAULT NULL,
    `update_user_id` bigint                                                       DEFAULT NULL,
    `ver`            int    NOT NULL                                              DEFAULT '0',
    `del`            bigint NOT NULL                                              DEFAULT '0',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uni_album_id_del` (`id`,`del`)
) COMMENT='album table';

CREATE TABLE IF NOT EXISTS `t_artist`
(
    `id`             bigint NOT NULL AUTO_INCREMENT,
    `artist_name`    varchar(32)                                                  DEFAULT NULL COMMENT 'artist name',
    `artist_from`    varchar(32)                                                  DEFAULT NULL COMMENT 'shorten of country name',
    `create_date`    timestamp NULL DEFAULT NULL,
    `create_user_id` bigint                                                       DEFAULT NULL,
    `update_date`    timestamp NULL DEFAULT NULL,
    `update_user_id` bigint                                                       DEFAULT NULL,
    `ver`            int    NOT NULL                                              DEFAULT '0',
    `del`            bigint NOT NULL                                              DEFAULT '0',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uni_artist_id_del` (`id`,`del`)
) COMMENT='artist table';

data.sql

insert into
    `t_album`
(
    `album_name`,
    `album_year`,
    `create_user_id`,
    `update_user_id`
)
values
(
    'Boomerang',
    2023,
    1023,
    1023
);

3. 话说回来:为甚么,我的h2没有自动创建quartz的schema

这是springboot.Quartz的实现
在这里插入图片描述

接下来,源码启动…

package org.springframework.boot.jdbc.init;

/**
 * {@link InitializingBean} that performs {@link DataSource} initialization using schema
 * (DDL) and data (DML) scripts.
 *
 * @author Andy Wilkinson
 * @since 2.5.0
 */
public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer {
	@Override
	protected boolean isEmbeddedDatabase() {
		try {
			// step into ..
			return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
		}
		catch (Exception ex) {
			logger.debug("Could not determine if datasource is embedded", ex);
			return false;
		}
	}
}

----------

	// org.springframework.boot.jdbc.EmbeddedDatabaseConnection
	/**
	 * Convenience method to determine if a given data source represents an embedded
	 * database type.
	 * @param dataSource the data source to interrogate
	 * @return true if the data source is one of the embedded types
	 */
	public static boolean isEmbedded(DataSource dataSource) {
		try {
		
			return new JdbcTemplate(dataSource)
				// step into ...
				.execute(new IsEmbedded());
		}
		catch (DataAccessException ex) {
			// Could not connect, which means it's not embedded
			return false;
		}
	}

----------

	// org.springframework.boot.jdbc.EmbeddedDatabaseConnection.IsEmbedded
	@Override
	public Boolean doInConnection(Connection connection) throws SQLException, DataAccessException {
		DatabaseMetaData metaData = connection.getMetaData();
		String productName = metaData.getDatabaseProductName();
		if (productName == null) {
			return false;
		}
		productName = productName.toUpperCase(Locale.ENGLISH);
		// step into ...
		EmbeddedDatabaseConnection[] candidates = EmbeddedDatabaseConnection.values();
		for (EmbeddedDatabaseConnection candidate : candidates) {
			if (candidate != NONE && productName.contains(candidate.getType().name())) {
				// 根据jdbc.url判断是不是一个 嵌入式数据库
				String url = metaData.getURL();
				return (url == null || candidate.isEmbeddedUrl(url));
			}
		}
		return false;
	}

------------

public enum EmbeddedDatabaseConnection {

	// H2 判断是否为嵌入式数据的依据
	/**
	 * H2 Database Connection.
	 */
	H2(EmbeddedDatabaseType.H2, DatabaseDriver.H2.getDriverClassName(),
			"jdbc:h2:mem:%s;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", (url) -> url.contains(":h2:mem")),

}

破案:我的h2 使用默认的 file(xxx.mv.db) 存储,默认配置下(DatabaseInitializationMode.EMBEDDED), 只有内存(嵌入式)的数据库会开启这个特性。

  • 要么配置 DatabaseInitializationMode.ALWAYS
  • 要么使用内存数据库

Anyway, h2支持好多种连接方式,新版本h2, 默认的file模式,采用mv的storeEngine 支持MVCC。所以说,对于quartz这种依赖行锁的要求,也是支持的。

4. 话又说回去… 这个东西对项目的意义是什么

  • 可以试下这个:如果你有一个连接数据库的测试环境,或者你的程序很简单,又或者 有特殊的xp(内存数据库)
  • 专门数据库的版本控制工具:你的程序比较复杂,或者 本身就需要数据库的版本控制工具(如 Liquibase),运行在严肃的生产环境

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1717920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剖析【C++】——类和对象(下篇)——超详解——小白篇

目录 1.再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1.3 explicit 关键字 2. Static成员 2.1 概念 2.2 特性 3. 友元 3.1 友元函数 3.2 友元类 3.3总结&#xff1a; 4. 内部类 1.概念 2.特性 示例代码&#xff1a; 代码分析 3.总结 5.再次理解类和对象 …

vue2转vue3初步下载pnpm遇到的问题 pnpm : 无法加载文件 D:\nodejs\pnpm.ps1

安装pnpm npm install -g pnpm pnpm -v 提示&#xff1a; 解决&#xff1a;nvm install 18.18.0 下载最稳定版本的nodejs nvm use 18.18.0 然后注意重新下载删除pnpm npm uninsta17 -g pnpm npm install -g pnpmlatest 在vscode使用pnpm报错 解决&#xff1a;管理员运行Windo…

C语言.数据结构.单链表

数据结构.单链表 1.链表的概念及结构2.单链表的实现2.1链表的打印2.2节点的申请2.3单链表的尾插2.4单链表的头插2.5单链表的尾删2.6单链表的头删2.7单链表节点的查找2.8在指定位置之前插入数据2.9在指定位置之后插入数据2.10删除pos节点2.11删除pos之后的节点2.12单链表的销毁2…

新一代最强开源UI自动化测试神器Playwright(Java版)六(断言)

Playwright是一个流行的UI自动化测试框架&#xff0c;用于编写UI自动化测试。在测试中&#xff0c;断言是一个非常重要的概念&#xff0c;用于验证测试的结果是否符合预期。Playwright提供了一些内置的断言函数&#xff0c;可以帮助测试人员编写更加简洁和可读的测试代码。本文…

为啥装了erlang,还报错erl: command not found?

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 问题背景&#xff1a; 在一台不通外网的服务器上装rabbitmq&#xff0c;然后在启动的时候&#xff0c;遇到了报错 “/usr/lib/…

走进中国照明百强西顿照明,解码数字化战略与经营增长

5月24日&#xff0c;纷享销客携领20多位企业高管走进纷享销客【数字化标杆】游学示范基地——西顿照明&#xff0c;并参访其位于惠州总部的光之家及灯巢&#xff0c;特邀广东端到端管理咨询兼纷享管理服务专家陈立云、西顿照明CIO唐勇作主题分享&#xff0c;为嘉宾带来一场解码…

【成品设计】基于STM32单片机的饮水售卖机

基于STM32单片机的饮水售卖机 所需器件&#xff1a; STM32最小系统板。RFID&#xff1a;MFRC-522用于IC卡检测。OLED屏幕&#xff1a;用于显示当前水容量、系统状态等。水泵软管&#xff1a;用于抽水。水位传感器&#xff08;3个&#xff09;&#xff1a;用于分别标定&#x…

Qt-qrencode生成二维码

Qt-qrencode开发-生成二维码&#x1f4c0; 文章目录 Qt-qrencode开发-生成二维码&#x1f4c0;[toc]1、概述&#x1f4f8;2、实现效果&#x1f4bd;3、编译qrencode&#x1f50d;4、在QT中引入编译为静态库的QRencode5、在Qt中直接使用QRencode源码6、在Qt中使用QRencode生成二…

数据可视化:解析其在现代生活中的日益重要地位

数据可视化为什么对我们的生活影响越来越大&#xff1f;这是一个值得探讨的话题。在信息化时代&#xff0c;数据无处不在&#xff0c;海量的数据不仅改变了商业模式&#xff0c;也深刻影响了我们的日常生活。数据可视化作为一种将复杂数据转化为直观图表、图形的技术&#xff0…

ubuntu22.04.3 vmware虚拟机配置共享文件夹 解决无法挂载/mnt/hgfs,血泪教训

一、背景介绍 在VMware Workstation 17 Pro上创建ubuntu22.04.3虚拟机&#xff0c;实现在ubuntu系统中共享windows的文件夹。按照网上方法试了大半&#xff0c;都没法解决&#xff0c;最终发现是vmware tools安装出现了问题&#xff0c;成功安装后&#xff0c;解决。 二、配置…

【软件测试】软件测试概念 | 测试用例 | BUG | 开发模型 | 测试模型 | 生命周期

文章目录 一、什么是软件测试1.什么是软件测试2.软件测试和调试的区别测试人员需要的素养 二、软件测试概念1.需求1.需求的定义2.测试人员眼中的需求 2.测试用例1.测试用例概念 3.BUG 软件错误4、开发模型和测试模型1.软件的生命周期2.开发模型1.瀑布模型2.螺旋模型3.增量、迭代…

端午档新片已预热,强业绩修复的影视股为何仍在徘徊?

随着端午临近&#xff0c;假期13部新片开始定档。据猫眼专业版显示&#xff0c;截至5月29日11时&#xff0c;即将上映的这13部新片&#xff0c;预售总票房已达到2155万。 受此消息影响&#xff0c;近日影视股出现了小幅的触底震荡反弹迹象&#xff0c;其中IMAX中国(01970)反弹…

记录Win11安装打印机驱动过程

1. 首先下载打印机对应型号的驱动 可以从这里下载&#xff1a;打印机驱动,打印机驱动下载 - 打印机驱动网 2. 下载 3. 打开控制面板-->设备和打印机 找到目标打印机添加设备即可 新增打印纸张尺寸

上传图片并显示#Vue3#后端接口数据

上传图片并显示#Vue3#后端接口数据 效果&#xff1a; 代码&#xff1a; <!-- 上传图片并显示 --> <template><!-- 上传图片start --><div><el-form><el-form-item><el-uploadmultipleclass"avatar-uploader"action"…

借助AI大模型,三分钟原创一部儿童故事短视频(附完整操作步骤)

前面文章的介绍&#xff0c;我们可以通过在自己笔记本电脑上部署的Llama 3大模型生成文章、文本润色、生成摘要等。今天我们更进一步&#xff0c;在文本的基础上&#xff0c;快速制作一部儿童故事短视频&#xff0c;且可根据自己需要完全原创…… 前提&#xff1a;有AI大模型对…

禅道迁移,linux一键安装版

问题描述&#xff1a;公司需要迁移禅道到另外一台服务器&#xff0c;没迁移过&#xff0c;去官网看了之后成功迁移&#xff0c;其中遇到了很多坑,希望对你们有所帮助。 禅道版本 迁移的版本一致&#xff0c;我的版本是18.3&#xff0c;18.3下载页面 其他版本下载 先进入检…

【SAP HANA 33】前端参数多选情况下HANA如何使用IN来匹配?

场面描述: 在操作界面经常会出现某个文本框需要多选的情况,然后后台需要根据多选的值进行匹配搜索。 一般处理的情况是: 1、在Java后端动态生成SQL 2、不改变动态SQL的情况,直接当做一个正常的参数进行传递 本次方案是第二个,直接当做一个正常的字符串参数进行传递即…

USB主机模式——Android

理论 摘自&#xff1a;USB 主机和配件概览 | Connectivity | Android Developers (google.cn) Android 通过 USB 配件和 USB 主机两种模式支持各种 USB 外围设备和 Android USB 配件&#xff08;实现 Android 配件协议的硬件&#xff09;。 在 USB 主机模式下&#xff0…

迪普科技:量子安全“先行者”退场?

今年4月&#xff0c;迪普科技&#xff08;300768 SHE&#xff09;接受机构调研时被问及“公司对量子通讯所必需的安全产品在继续研究吗”时表示&#xff1a;“公司曾为量子通讯网络提供安全产品解决方案&#xff0c;从这点说与其他领域的安全产品区别不大”。换句话说&#xff…

mybatis异常:Invalid bound statement (not found): com.lm.mapper.ArticleMapper.list

现象&#xff1a; 原因&#xff1a; 无效绑定&#xff0c;应该是mybatis最常见的一个异常了&#xff0c;接口与XML文件没绑定。首先&#xff0c;mapper接口并没有实现类&#xff0c;所以框架会通过JDK动态代理代理模式获取接口的代理实现类&#xff0c;进而根据接口全限定类名…