多数据源配置H2 Mysql

这篇具有很好参考价值的文章主要介绍了多数据源配置H2 Mysql。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

需求背景

最近有一需求,原本项目中由于某些原因使用嵌入式数据库H2,鉴于嵌入式数据库可靠性以及不方便管理等因素,需要将数据库迁移到Mysql。

环境说明

SpringBoot:3.0.2
JDK:17
H2:2.1.214
spring-boot-starter-data-jpa:3.0.2

Mysql:8.0.32

实现过程

配置调整

原配置

pom.xml

<!-- 省略其他依赖... -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
    <scope>runtime</scope>
</dependency>

dev.yml

spring:
  datasource:
    url: jdbc:h2:file:./data/cloak-ab
    driver-class-name: org.h2.Driver
    username: root
    password: root789456
  jpa:
    database: h2
    hibernate:
      ddl-auto: update
    show-sql: true
  h2:
    console:
      path: /h2-console
      enabled: true
      settings:
        web-allow-others: true
        trace: true

修改配置

修改后pom.xml

<!-- 省略其他依赖... -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.32</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
    <scope>runtime</scope>
</dependency>

修改后dev.yml

spring:
  datasource:
    url: jdbc:mysql://192.168.0.80:3306/cloak_ab?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root789456
  jpa:
  	# 此处有修改
    database: mysql
    hibernate:
      ddl-auto: update
    show-sql: true
  h2:
    console:
      path: /h2-console
      enabled: true
      settings:
        web-allow-others: true
        trace: true

主要修改点有三处:

①添加Mysql连接依赖
②数据库的连接地址和数据库驱动
③jpa使用的数据库

代码调整

新增DatasourceConfig配置类

配置Mysql主数据源,H2次数据源
DatasourceConfig.java

import lombok.Data;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DatasourceConfig {

    @Data
    @Configuration
    @ConfigurationProperties(prefix="spring.datasource")
    public static class MasterDatasourceProperties {
        private String url;
        private String driverClassName;
        private String username;
        private String password;
    }

    @Bean
    @Primary
    public DataSource dataSource(MasterDatasourceProperties masterDatasourceProperties) {
        return DataSourceBuilder.create()
                .driverClassName(masterDatasourceProperties.getDriverClassName())
                .url(masterDatasourceProperties.getUrl())
                .username(masterDatasourceProperties.getUsername())
                .password(masterDatasourceProperties.getPassword())
                .build();
    }

    @Primary
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create()
                .url("jdbc:h2:file:./data/cloak-ab")
                .driverClassName("org.h2.Driver")
                .username("root")
                .password("root789456")
                .build();
    }

    @Bean(name = "secondaryJdbcTemplate")
    public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
        return new JdbcTemplate(secondaryDataSource);
    }

}

Mysql主数据源添加@Primary注解,jpa使用的默认是主数据源,如此一来jpa操作的就是Mysql数据库了。

使用secondaryJdbcTemplate

	@Autowired
    private AppParamDao appParamDao;
	@Autowired
    @Qualifier("secondaryJdbcTemplate")
    protected JdbcTemplate secondaryJdbcTemplate;

	@Override
    public void h2ToMysql() {
        String sql = "select * from app_param";
        List<AppParam> appParams = secondaryJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AppParam.class));
        appParamDao.saveAllAndFlush(appParams);
    }

此时secondaryJdbcTemplate操作的就是H2数据库,而appParamDao操作的就是Mysql数据库

AppParamDao.java

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;
import java.util.Set;

@Repository
public interface AppParamDao extends CrudRepository<AppParam, Long>, JpaRepository<AppParam, Long> {
}

遇到的坑

坑1:在实体类中属性名与数据库字段名不一致

不知道为什么,总能碰到一些“阴间操作”,实体类中的属性名称与数据库中的字段名不一样,导致在使用secondaryJdbcTemplate查询的时候,实体类的属性值为null…如此一来,在执行数据插入的时候,这些字段值就为null

appParamDao.saveAllAndFlush(appParams);

如下,数据库字段名为:param_value,但是实体类属性名是:value

@EntityListeners(AuditingEntityListener.class)
@Schema(description = "App配置参数信息")
@Table( indexes = {
		@Index(name = "idx_app_param_bundle_id", columnList = "bundle_id")
})
@Entity(name = "app_param")
@Data
public class AppParam implements Serializable {
	@Serial
	private static final long serialVersionUID = 8398910406652563675L;
	// ... 省略其他属性
	/**
	* 参数值
	*/
	@Schema(description = "参数值")
	@Column(name = "param_value")
	private String value;

	/**
	 * 创建者
	 */
	@CreatedBy
	@Schema(description = "创建者")
	private String createBy;

	/**
	 * 创建日期
	 */
	@CreatedDate
	@Schema(description = "创建日期")
	@Column(updatable = false)
	private LocalDateTime createDate;

	/**
	 * 更新者
	 */
	@LastModifiedBy
	@Schema(description = "更新者")
	private String updateBy;

	/**
	 * 更新日期
	 */
	@LastModifiedDate
	@Schema(description = "更新日期")
	private LocalDateTime updateDate;

}
解决办法1:将实体类中属性名改成与数据库字段名一致

例如:

@EntityListeners(AuditingEntityListener.class)
@Schema(description = "App配置参数信息")
@Table( indexes = {
		@Index(name = "idx_app_param_bundle_id", columnList = "bundle_id")
})
@Entity(name = "app_param")
@Data
public class AppParam implements Serializable {
	@Serial
	private static final long serialVersionUID = 8398910406652563675L;
	/**
	* 参数值
	*/
	@Schema(description = "参数值")
	@Column(name = "param_value")
	private String paramValue;
}

该解决方式缺点:
①可能会导致修改的代码比较多,因为有其他使用到该字段的地方都要同时修改
②倘若某些地方使用了诸如BeanUtils的Bean拷贝工具,会导致copy的类中属性值为空,容易引发隐藏bug

解决办法2:实体类中添加与数据库字段名一致的属性

例如:

@EntityListeners(AuditingEntityListener.class)
@Schema(description = "App配置参数信息")
@Table( indexes = {
		@Index(name = "idx_app_param_bundle_id", columnList = "bundle_id")
})
@Entity(name = "app_param")
@Data
public class AppParam implements Serializable {
	@Serial
	private static final long serialVersionUID = 8398910406652563675L;
	/**
	* 参数值
	*/
	@Schema(description = "参数值")
	@Column(name = "param_value")
	private String value;

	@Transient
	@Deprecated
	private String paramValue;

	/**
	 * 创建者
	 */
	@CreatedBy
	@Schema(description = "创建者")
	private String createBy;

	/**
	 * 创建日期
	 */
	@CreatedDate
	@Schema(description = "创建日期")
	@Column(updatable = false)
	private LocalDateTime createDate;

	/**
	 * 更新者
	 */
	@LastModifiedBy
	@Schema(description = "更新者")
	private String updateBy;

	/**
	 * 更新日期
	 */
	@LastModifiedDate
	@Schema(description = "更新日期")
	private LocalDateTime updateDate;
}

必须添加@Transient注解,否则启动时jpa会报错,大概意思就是有多个属性引用相同列

Caused by: org.hibernate.DuplicateMappingException: Table [app_param] contains physical column name [param_value] referred to by multiple logical column names: [param_value], [paramValue]
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl$TableColumnNameBinding.bindPhysicalToLogical(InFlightMetadataCollectorImpl.java:1055)
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl$TableColumnNameBinding.addBinding(InFlightMetadataCollectorImpl.java:1024)
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.addColumnNameBinding(InFlightMetadataCollectorImpl.java:1094)
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.addColumnNameBinding(InFlightMetadataCollectorImpl.java:1075)
	at org.hibernate.cfg.AnnotatedColumn.addColumnBinding(AnnotatedColumn.java:473)
	at org.hibernate.cfg.AnnotatedColumn.linkWithValue(AnnotatedColumn.java:436)
	at org.hibernate.cfg.annotations.BasicValueBinder.linkWithValue(BasicValueBinder.java:1134)
	at org.hibernate.cfg.annotations.BasicValueBinder.make(BasicValueBinder.java:1109)
	at org.hibernate.cfg.annotations.PropertyBinder.makePropertyAndValue(PropertyBinder.java:202)
	at org.hibernate.cfg.annotations.PropertyBinder.makePropertyValueAndBind(PropertyBinder.java:229)
	at org.hibernate.cfg.AnnotationBinder.bindBasic(AnnotationBinder.java:1455)
	at org.hibernate.cfg.AnnotationBinder.buildProperty(AnnotationBinder.java:1251)
	at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1112)
	at org.hibernate.cfg.annotations.EntityBinder.processIdPropertiesIfNotAlready(EntityBinder.java:935)
	at org.hibernate.cfg.annotations.EntityBinder.bindEntityClass(EntityBinder.java:273)
	at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:556)
	at org.hibernate.boot.model.source.internal.annotations.AnnotationMetadataSourceProcessorImpl.processEntityHierarchies(AnnotationMetadataSourceProcessorImpl.java:216)
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess$1.processEntityHierarchies(MetadataBuildingProcess.java:247)
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:290)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1350)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1421)
	at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:66)
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:376)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
	at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:352)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1797)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1747)
	... 126 common frames omitted

用transient关键字标记的成员变量不参与序列化过程
将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的位置

然后再插入数据时手动将paramValue的值set给value

@Override
public void h2ToMysql() {
	String sql = "select * from app_param";
	List<AppParam> appParams = secondaryJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AppParam.class));
	for (AppParam appParam : appParams) {
		appParam.setValue(appParam.getParamValue());
	}
	appParamDao.saveAllAndFlush(appParams);
}

坑2:与jap审计功能冲突

如上,可以看到 AppParam 实体类上有添加注解:

@EntityListeners(AuditingEntityListener.class)

该注解通常与:@CreatedBy、@CreatedDate、@LastModifiedBy、@LastModifiedDate注解一起使用,即在插入数据时自动设置创建时间、创建者;在更新数据时,自动设置更新时间、更新者,可以减少很多重复代码。

当然,使用审计功能,要在启动类上添加启用审计功能的注解:@EnableJpaAuditing和实现默认的AuditorAware

@EnableJpaAuditing
@SpringBootApplication
public class CloakAbApplication {
	public static void main(String[] args) {
		SpringApplication.run(CloakAbApplication.class, args);
	}
}

DefaultAuditorAware .java

@Configuration
public class DefaultAuditorAware implements AuditorAware<String> {

    @Override
    public Optional<String> getCurrentAuditor() {
        SysUser sysUser = SecurityUtils.getSysUser();
        return Optional.ofNullable(sysUser).map(SysUser::getUserNo);
    }
}

此类的作用是告诉jpa获取当前用户的方式,这里使用的SecurityUtils是结合了Spring Security的工具类。各位看官可以自行实现

问题:在执行数据插入代码时,审计功能自动设置创建者、创建时间;但此时这种效果并不是我们想看见的…因为旧数据已经存在的值,我们不想它发生变化

appParamDao.saveAllAndFlush(appParams);
解决办法:自定义CustomAuditingEntityListener

CustomAuditingEntityListener.java

import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Configurable
public class CustomAuditingEntityListener {

	// 标识是否需要jpa的审计
    public static Boolean custom = false;

    @Autowired
    private AuditingEntityListener auditingEntityListener;

    @PrePersist
    public void touchForCreate(Object target) {
        if (!custom) {
            auditingEntityListener.touchForCreate(target);
        }
    }

    @PreUpdate
    public void touchForUpdate(Object target) {
        if (!custom) {
            auditingEntityListener.touchForUpdate(target);
        }
    }
}

实体类AppParam.java
将AuditingEntityListener换成CustomAuditingEntityListener

@EntityListeners(CustomAuditingEntityListener.class)
@Schema(description = "App配置参数信息")
@Table( indexes = {
		@Index(name = "idx_app_param_bundle_id", columnList = "bundle_id")
})
@Entity(name = "app_param")
@Data
public class AppParam implements Serializable {
	@Serial
	private static final long serialVersionUID = 8398910406652563675L;

	/**
	 * 参数值
	 */
	@Schema(description = "参数值")
	@Column(name = "param_value")
	private String value;

	@Transient
	@Deprecated
	private String paramValue;

	/**
	 * 创建者
	 */
	@CreatedBy
	@Schema(description = "创建者")
	private String createBy;

	/**
	 * 创建日期
	 */
	@CreatedDate
	@Schema(description = "创建日期")
	@Column(updatable = false)
	private LocalDateTime createDate;

	/**
	 * 更新者
	 */
	@LastModifiedBy
	@Schema(description = "更新者")
	private String updateBy;

	/**
	 * 更新日期
	 */
	@LastModifiedDate
	@Schema(description = "更新日期")
	private LocalDateTime updateDate;
}

此时只要在执行插入数据之前设置CustomAuditingEntityListener中custom属性的值就可以了

appParamDao.saveAllAndFlush(appParams);

代码如下:文章来源地址https://www.toymoban.com/news/detail-804162.html

@Override
public void h2ToMysql() {
	String sql = "select * from app_param";
	List<AppParam> appParams = secondaryJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AppParam.class));
	for (AppParam appParam : appParams) {
	    appParam.setValue(appParam.getParamValue());
	}
	CustomAuditingEntityListener.custom = true;
	appParamDao.saveAllAndFlush(appParams);
	CustomAuditingEntityListener.custom = false;
}

到了这里,关于多数据源配置H2 Mysql的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot整合mysql、postgres、sqlserver实现多数据源配置案例

            分享一下近期处理的一个小demo,关于配置多数据源实现不同服务之间的数据推送和数据治理。第一次接触到pg库和sqlserver一头雾水,选择了JDBC+mybatis-plus的方式去链接。 1、首先要引入以下依赖 2、demo的项目结构如下 3、yml配置文件 4、配置类 5、controller、dao、service以

    2024年02月06日
    浏览(34)
  • 若依分离版——解决配置双数据源oracle,mysql分页错误问题

    1. 按照若依的手册配置双数据源mysql,oracle   2. 在service指定 数据源 @DataSource(value = DataSourceType.MASTER) 或者@DataSource(value = DataSourceType.SLAVE) 3. 发现出现使用分页的情况下报错,不使用分页时正常。 4.  最后找到解决办法,是application.yml文件的pagehelper分页配置有误,正确配置如

    2024年02月15日
    浏览(37)
  • docker安装nacos配置外部数据源mysql,解决no DataSource set 问题

    1.之前一直看的别人的nacos配置教程,都感觉不全面。同时启动时莫名会出现no datasource set问题,前两天为了看了各方面的教程,也写了 nacos docker v2.1.2启动报错数据源未设置no datasource set文章。 2.昨天nacos突然宕机了,试了各种方法,还是no datasource set;现在综合各方面的教程

    2024年02月02日
    浏览(59)
  • 【五一创作】Springboot+多环境+多数据源(MySQL+Phoenix)配置及查询(多知识点)

    实时数据展示,通常分两部分, 一部分是离线数据计算,这部分通过大数据程序计算好后,同步到MySQL中。 一部分是实时程序,这部分是Flink实时写入Phoenix表中。 这样两部分拼接好后,就是完整的实时数据部分,所以现在一个接口查询需要将MySQL和Phoenix中的表查询并合并在

    2024年02月02日
    浏览(34)
  • yml配置动态数据源(数据库@DS)与引起(If you want an embedded database (H2, HSQL or Derby))类问题

    Druid连接池的自动配置类是DruidDataSourceAutoConfigure类上有一行注解 @EnableConfigurationProperties注解的作用是:使配置文件中的配置生效并且映射到指定类的属性 DruidStatProperties:指定的前缀是spring.datasource.druid,主要设置连接池的一些参数 DataSourceProperties:指定的前缀是spring.dataso

    2024年02月10日
    浏览(33)
  • springboot + (mysql/pgsql) + jpa 多数据源(不同类数据源)

     配置文件: datasourceconfig: 数据源一: 数据源二:

    2024年02月14日
    浏览(48)
  • springboot hive mysql 多数据源切换

    本次实验重在多数据源切换 性能不在考虑其中 开发环境: hive 3.1.3 mysql 8.0.33 jdk 1.8 maven 3.9.1 idea 2023.1 springboot 2.7.11 HikariCP 连接池 实验效果:从 hive 中迁移数据到 MySQL pom.xml springboot 配置文件 application.yml pojo 类 mapper 接口 mybatis 映射配置文件 多数据配置 多数据源切换工具类

    2024年02月01日
    浏览(62)
  • SpringBoot Mybatis 多数据源 MySQL+Oracle

    在SpringBoot Mybatis 项目中,需要连接 多个数据源,连接多个数据库,需要连接一个MySQL数据库和一个Oracle数据库   spring.datasource.url数据库的JDBC URL spring.datasource.jdbc-url用来重写自定义连接池 Hikari没有url属性,但是有jdbcUrl属性,在这中情况下必须使用jdbc_url MysqlDataSourceConfig 使用

    2024年02月11日
    浏览(45)
  • springboot+mybatis实现mysql和oracle多数据源

    在实际项目中很多时候会涉及到多个数据库的访问,或者数据库读写分离的形式。 下面通过使用 Aspect+注解来实现mysql+oracle的多数据源配置(注意:事务一致性未提供) 首先要去oracle官网下载ojdbc的jar包,根据oracle的版本去下载,或者在下载的oracle的jdbc包下的lib里面有,然后

    2024年02月07日
    浏览(44)
  • SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

    本文是SpringBoot第27讲,在某些场景下,Springboot需要使用多个数据源,以及某些场景会需要多个数据源的动态切换。本文主要介绍上述场景及 SpringBoot+MyBatis实现多个数据源的方案和示例 需要了解多数据源出现的场景和对应的多数据源集成思路。 1.1、什么场景会出现多个数据源

    2024年02月09日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包