• springboot 项目启动检查创建数据库,执行脚本,以及初始化数据源


    背景: 因为项目需要部署到项目现场,新的电脑环境虽然安装了MySQL,但是部署人员不一定会执行数据库脚本。每次通过Navicat等软件也比较麻烦。所以需要在项目启动前检查是否存在数据库,如果不存在就创建数据库并且执行数据库脚本。如果存在就跳过。

    项目配置文件application.yml

    1. spring:
    2. datasource:
    3. url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
    4. username: XXX
    5. password: XXX
    6. driver-class-name: com.mysql.cj.jdbc.Driver
    7. platform: mysql
    8. schema: classpath:/sql/schema-mysql.sql
    9. #data: classpath:/sql/data-mysql.sql
    10. initialization-mode: always
    11. continue-on-error: true
    12. sql-script-encoding: utf-8
    13. separator: ;
    14. main:
    15. url: jdbc:mysql://127.0.0.1:3306/XXX?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
    16. username: XXX
    17. password: XXX
    18. driver-class-name: com.mysql.cj.jdbc.Driver
    19. type: com.alibaba.druid.pool.DruidDataSource
    20. continue-on-error: true
    21. initial-size: 5
    22. min-idle: 5
    23. max-active: 200
    24. max-wait: 60000
    25. break-after-acquire-failure: false
    26. connection-error-retry-attempts: 3
    27. filters: stat,wall,slf4j

    检查数据库执行类(检查是否存在指定的数据库–不存在则创建数据库-否则跳过)

    1. package xxx.xxx.xxx;
    2. import org.slf4j.Logger;
    3. import org.slf4j.LoggerFactory;
    4. import org.springframework.beans.factory.annotation.Value;
    5. import org.springframework.context.annotation.Configuration;
    6. import javax.annotation.PostConstruct;
    7. import java.sql.*;
    8. @Configuration
    9. public class DatabaseInitConfig {
    10. private static final Logger LOG = LoggerFactory.getLogger(DatabaseInitConfig.class);
    11. private static final String SCHEMA_NAME = "schema_name";
    12. private static final String DATABASE_NAME = "xxx";
    13. /**
    14. * com.mysql.cj.jdbc.Driver
    15. */
    16. @Value("${spring.datasource.driver-class-name}")
    17. private String driver;
    18. /**
    19. * jdbc_url
    20. */
    21. @Value("${spring.datasource.url}")
    22. private String url;
    23. /**
    24. * 账号名称
    25. */
    26. @Value("${spring.datasource.username}")
    27. private String username;
    28. /**
    29. * 账号密码
    30. */
    31. @Value("${spring.datasource.password}")
    32. private String password;
    33. /**
    34. * 需要创建的数据名称
    35. */
    36. @PostConstruct
    37. public void init() {
    38. try {
    39. Class.forName(driver);
    40. } catch (ClassNotFoundException e) {
    41. LOG.error("JDBC URL解析错误", e);
    42. }
    43. try (
    44. Connection connection = DriverManager.getConnection(url, username, password);
    45. Statement statement = connection.createStatement()) {
    46. String sal = "select schema_name from information_schema.schemata where schema_name = " + "'" + DATABASE_NAME + "'";
    47. //查询返回的结果集
    48. ResultSet resultSet = statement.executeQuery(sal);
    49. if (!resultSet.next()) {
    50. //查不到数据库,执行数据库初始化脚本
    51. LOG.warn("查不到数据库({})", DATABASE_NAME);
    52. String createDb = "CREATE DATABASE IF NOT EXISTS " + DATABASE_NAME
    53. + " DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci ";
    54. connection.setAutoCommit(false);
    55. statement.execute(createDb);
    56. connection.commit();
    57. LOG.info("创建数据库({})成功", DATABASE_NAME);
    58. } else {
    59. String databaseName = resultSet.getString(SCHEMA_NAME);
    60. LOG.warn("已经存在数据库({})", databaseName);
    61. }
    62. if (resultSet.isClosed()) {
    63. resultSet.close();
    64. }
    65. } catch (SQLException e) {
    66. LOG.error("启动项目检查数据库是否创建报错", e);
    67. }
    68. }
    69. }

    @Configuration 知识点
    用于定义配置类,可替换XML配置文件,被注解的类内部包含一个或多个@Bean注解方法。可以被AnnotationConfigApplicationContext或者AnnotationConfigWebApplicationContext 进行扫描。用于构建bean定义以及初始化Spring容器。

    @PostConstruct说明
    被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次,类似于Serclet的inti()方法。被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。

    @PreConstruct说明
    被@PreConstruct修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreConstruct修饰的方法会在destroy()方法之后运行,在Servlet被彻底卸载之前

    执行脚本可以通过yml配置实现

    spring:
      datasource:
        url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&tinyInt1isBit=false&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
        username: XXX
        password: XXX
        driver-class-name: com.mysql.cj.jdbc.Driver
        platform: mysql
        schema: classpath:/sql/schema-mysql.sql
        data: classpath:/sql/data-mysql.sql
        initialization-mode: always

    配置数据源类

    控制数据源初始化在检查数据库完成之后

    @DependsOn 控制bean初始化顺序
    可能有些场景中,bean A 间接依赖 bean B。如Bean B应该需要更新一些全局缓存,可能通过单例模式实现且没有在spring容器注册,bean A需要使用该缓存;因此,如果bean B没有准备好,bean A无法访问。
    另一个场景中,bean A是事件发布者(或JMS发布者),bean B (或一些) 负责监听这些事件,典型的如观察者模式。我们不想B 错过任何事件,那么B需要首先被初始化。
    简言之,有很多场景需要bean B应该被先于bean A被初始化,从而避免各种负面影响。我们可以在bean A上使用@DependsOn注解,告诉容器bean B应该先被初始化。下面通过示例来说明。

    1. package xxx.xxx;
    2. import com.alibaba.druid.pool.DruidDataSource;
    3. import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
    4. import org.slf4j.Logger;
    5. import org.slf4j.LoggerFactory;
    6. import org.springframework.aop.support.DefaultPointcutAdvisor;
    7. import org.springframework.aop.support.JdkRegexpMethodPointcut;
    8. import org.springframework.beans.factory.annotation.Value;
    9. import org.springframework.context.annotation.Bean;
    10. import org.springframework.context.annotation.Configuration;
    11. import org.springframework.context.annotation.DependsOn;
    12. import org.springframework.context.annotation.Scope;
    13. import javax.sql.DataSource;
    14. import java.sql.SQLException;
    15. @Configuration
    16. public class DataSourceConfig {
    17. private static final Logger LOG = LoggerFactory.getLogger(DataSourceConfig.class);
    18. /**
    19. * spring监控,druid的拦截器
    20. *
    21. * @return
    22. */
    23. @Bean
    24. public DruidStatInterceptor druidStatInterceptor() {
    25. return new DruidStatInterceptor();
    26. }
    27. @Bean
    28. @Scope("prototype")
    29. public JdkRegexpMethodPointcut druidStatPointcut() {
    30. JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
    31. pointcut.setPatterns("com.xx.xxxx.*.service.*.service.*", "com.xxxx.xxxx.*.service.*.mapper.*");
    32. return pointcut;
    33. }
    34. /**
    35. * aop配置
    36. *
    37. * @param druidStatInterceptor
    38. * @param druidStatPointcut
    39. * @return
    40. */
    41. @Bean
    42. public DefaultPointcutAdvisor druidStatAdvisor(DruidStatInterceptor druidStatInterceptor, JdkRegexpMethodPointcut druidStatPointcut) {
    43. DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
    44. advisor.setAdvice(druidStatInterceptor);
    45. advisor.setPointcut(druidStatPointcut);
    46. return advisor;
    47. }
    48. private DruidDataSource dataSource;
    49. @Bean
    50. @DependsOn("databaseInitConfig")
    51. public DataSource init(
    52. @Value("${spring.datasource.main.driver-class-name}")
    53. String driverClassName,
    54. @Value("${spring.datasource.main.username}")
    55. String username,
    56. @Value("${spring.datasource.main.password}")
    57. String password,
    58. @Value("${spring.datasource.main.url}")
    59. String url,
    60. @Value("${spring.datasource.main.initial-size}")
    61. Integer initialSize,
    62. @Value("${spring.datasource.main.min-idle}")
    63. Integer minIdle,
    64. @Value("${spring.datasource.main.max-active}")
    65. Integer maxActive,
    66. @Value("${spring.datasource.main.max-wait}")
    67. Integer maxWait,
    68. @Value("${spring.datasource.main.filters}")
    69. String filters
    70. ) {
    71. try {
    72. if (dataSource != null) {
    73. return dataSource;
    74. }
    75. DruidDataSource druidDataSource = new DruidDataSource();
    76. druidDataSource.setDriverClassName(driverClassName);
    77. druidDataSource.setUsername(username);
    78. druidDataSource.setPassword(password);
    79. druidDataSource.setUrl(url);
    80. druidDataSource.setInitialSize(initialSize);
    81. druidDataSource.setMinIdle(minIdle);
    82. druidDataSource.setMaxActive(maxActive);
    83. druidDataSource.setMaxWait(maxWait);
    84. druidDataSource.setFilters(filters);
    85. return druidDataSource;
    86. } catch (SQLException e) {
    87. LOG.error("初始化数据源出错", e);
    88. }
    89. return null;
    90. }
    91. }

     

  • 相关阅读:
    阿加犀AI应用案例征集活动 持续进行中!
    记录因为端口号使用6000,造成浏览器GET请求无响应
    IO系列(十) -TCP 滑动窗口原理解析
    Java(十二)---认识异常
    世强硬创获昕感科技授权代理,SiC MOSFET实现超低导通电阻
    互联网架构演进方向
    用async函数和await解决回调函数地狱
    浮动布局再续前缘
    导入自己的jacoco exec文件到IDEA并进行展示
    大数据从入门到精通(超详细版)之BI工具的安装
  • 原文地址:https://blog.csdn.net/zzchances/article/details/127687645