JHipster一知半解- 3.4Database相关配置

回文集目录:JHipster一知半解

老实说,Database配置内容相当多的,包括数据库选择,ORM工具选择,数据库连接池的选择等。JHipster主要使用了Hibernate系列的JPA架构,从最大程度满足了主流的数据库(甚至可以是非关系型数据库)。这也契合微服务的理念,要求ORM模型尽量简单,尽量把服务(业务功能)进行拆分,而不是把相关内容糅合在一起。换句话说,虽然JHipster看上去高大上,毕竟还是提供了一个框架(毛呸房),还是要求进行详尽的功能划分(细装修)才能真正实现微服务的架构。

下面按照比较通用的mysql数据库的配置,进行分析

Pom.xml

<!--最为核心的spring-boot-data-jpa-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 数据库连接器,这里根据选择的数据库类型不同-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 默认引用的内存式数据库,开发测试用 -->
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
<!-- 关系型数据库使用的连接池 -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>
<!-- Hibernate引用和liqubase -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jcache</artifactId>
    <version>${hibernate.version}</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

其中spring-boot-starter-data-jpa也是一个复合pom,里面依赖spring-data-jpa、hibernate-core、spring-boot-starter-jdbc(包含tomcat-jdbc和spring-jdbc)
从依赖关系可以猜出,默认使用的连接池是tomcat-jdbc,具体的代码

  1. DataSource解析流程

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration

    /**
     * Tomcat Pool DataSource configuration.
     */
    @ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)
    static class Tomcat extends DataSourceConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.tomcat")
        public org.apache.tomcat.jdbc.pool.DataSource dataSource(
                DataSourceProperties properties) {
            org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
                    properties, org.apache.tomcat.jdbc.pool.DataSource.class);
            DatabaseDriver databaseDriver = DatabaseDriver
                    .fromJdbcUrl(properties.determineUrl());
            String validationQuery = databaseDriver.getValidationQuery();
            if (validationQuery != null) {
                dataSource.setTestOnBorrow(true);
                dataSource.setValidationQuery(validationQuery);
            }
            return dataSource;
        }

    }

    /**
     * Hikari DataSource configuration.
     */
    @ConditionalOnClass(HikariDataSource.class)
    @ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
    static class Hikari extends DataSourceConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.hikari")
        public HikariDataSource dataSource(DataSourceProperties properties) {
            return createDataSource(properties, HikariDataSource.class);
        }

    }

这里可以清晰地看到,如果没有配置,那么只需要引入tomcat.jdbc的DataSource.class(spring-boot-starter-jdbc里面默认引入依赖的),那么就会自动使用Tomcat这个数据源。
对于JHipster,由于已经添加HikariCP的依赖,且application-××.yml都配置了

spring.datasource.type: com.zaxxer.hikari.HikariDataSource

因而,会调用HikariDataSource的createDataSource(prod的配置里面,还激活了通过spring.datasource.hikari配置额外的DataSourceProperties)。这样,系统就有了dataSource的Bean。

  1. JdbcTemplate解析流程

org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration

    @Bean
    @Primary
    @ConditionalOnMissingBean(NamedParameterJdbcOperations.class)
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
        return new NamedParameterJdbcTemplate(this.dataSource);
    }

这里会自动注入刚刚生成的HikariDataSource,生成默认的JdbcTemplate(实际是一个NamedParameterJdbcTemplate),这样在程序任何地方,就可以通过@Autowired JdbcTemplate jdbcTemplate,获得spring提供的默认JDBC模板。

  1. JPA解析流程

org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration

类注解
@Configuration
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class })
@Conditional(HibernateEntityManagerCondition.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })

可以看出,只需要有spring-orm的LocalContainerEntityManagerFactoryBean和Hibernate的EntityManager。就能在配置DataSourceAutoConfiguration之后,自动配置HibernateJpaAutoConfiguration

构造函数
    public HibernateJpaAutoConfiguration(DataSource dataSource,
            JpaProperties jpaProperties,
            ObjectProvider<JtaTransactionManager> jtaTransactionManager,
            ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        super(dataSource, jpaProperties, jtaTransactionManager,
                transactionManagerCustomizers);
    }

自动配置HibernateJpaAutoConfiguration本身并没有去实例化spring的bean,而是在构造函数,调用了父类的构造函数,实例化父亲JpaBaseConfiguration

org.springframework.boot.autoconfigure.orm.jpaJpaBaseConfiguration

首先它是implements BeanFactoryAware,因而有一个ConfigurableListableBeanFactory变量,保存当前的Bean工厂

@Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
    }

其次,里面又众多的@Bean声明JPA需要的Bean,这里,我们关注一下LocalContainerEntityManagerFactoryBean

@Bean
    @Primary
    @ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class,
            EntityManagerFactory.class })
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            EntityManagerFactoryBuilder factoryBuilder) {
        Map<String, Object> vendorProperties = getVendorProperties();
        customizeVendorProperties(vendorProperties);
        return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan())
                .properties(vendorProperties).jta(isJta()).build();
    }

这里调用了getPackagesToScan()扫描了应用程序下的所有类,进行EntityManagerFactory的初始化。(貌似DatabaseConfiguration的@EnableJpaRepositories("io.github.jhipster.sample.repository")并没有作用。

至此,Jhipster完成解析数据源,数据库连接池,ORM工具的配置,这些几乎都是spring-boot-data-jpa“自动”完成的,我们所要做的只是在yml配置文件中配置对于的信息(JHipster也很贴心的给了配置模板,我们只需要填空即可)。

DatabaseConfiguration

由于大部分的配置几乎是“自动”完成的,几乎没有什么额外的数据库配置

类注解
@Configuration
@EnableJpaRepositories("io.github.jhipster.sample.repository")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement

这里@EnableJpaRepositories和@EnableTransactionManagement,好习惯的标准代码,在专门地方配置相关的代码

如果是开发模式,还初始化了一个H2数据库的TcpServer,方便通过WEB浏览器查看数据库内容。

@Bean
    public SpringLiquibase liquibase(@Qualifier("taskExecutor") TaskExecutor taskExecutor,
            DataSource dataSource, LiquibaseProperties liquibaseProperties) {

        // Use liquibase.integration.spring.SpringLiquibase if you don't want Liquibase to start asynchronously
        SpringLiquibase liquibase = new AsyncSpringLiquibase(taskExecutor, env);
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog("classpath:config/liquibase/master.xml");
        liquibase.setContexts(liquibaseProperties.getContexts());
        liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
        liquibase.setDropFirst(liquibaseProperties.isDropFirst());
        if (env.acceptsProfiles(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE)) {
            liquibase.setShouldRun(false);
        } else {
            liquibase.setShouldRun(liquibaseProperties.isEnabled());
            log.debug("Configuring Liquibase");
        }
        return liquibase;
    }

通过SpringLiquibase进行数据库的同步,并注入taskExecutor使得该过程能在独立的线程异步进行。其中AsyncSpringLiquibase位于io.github.jhipster.config.liquibase,Jhipster给SpringLiquibase赋予当处于SPRING_PROFILE_DEVELOPMENT或SPRING_PROFILE_HEROKU环境是,能够用taskExecutor异步执行。
注意SPRING_PROFILE_NO_LIQUIBASE或者yml配置了liquibase.enabled: false也可以关闭这个过程。

liquibase

数据库版本控制以及升级维护,是一件繁琐且容易出错的工作。JHipster选用的Liquibase进行数据库版本升级的"自动化“。
具体原理就是在维护一个数据库版本和变更的xml,每次启动时进行版本比较,如果现在版本低,就调用xml中sql进行升级。
因而最理想的流程是这样的:
1.使用jhipster entity 生成活修改具体的实体Bean
2.由jhi-cli修改domain中实体对象,并生成.jhipster目录下对应的json文件,以及resources\config\liquibase中数据库信息
3.项目启动时,Liquibase自动识别变动信息,进行数据库的变动。

这种一个地方修改,即可全局生效理念固然不错,实际情况也有可能已经做了数据的sql更新,此时如果需要发布版本,流程就有点不同了。
1.手工修改domain中实体对象,在数据库用sql修改db。
2.调用mvn liquibase:generateChangeLog 对比数据库,对变更的数据,生成为应用的SQL
3.执行mvn liquibase:update 执行changeLog更新数据库
当然也可以完全关闭liquibase,手工执行数据库版本控制以及升级维护。

特别注意的是在pom.xml的iquibase-maven-plugin中
有特别

<dependency>
    <groupId>org.liquibase.ext</groupId>
    <artifactId>liquibase-hibernate5</artifactId>
    <version>${liquibase-hibernate5.version}</version>
</dependency>
<dependency>
    <groupId>org.liquibase.ext</groupId>
    <artifactId>liquibase-hibernate5</artifactId>
    <version>${liquibase-hibernate5.version}</version>
</dependency>

这样才能根据DB与实体Entity的不同,生成ChangeLog.

JHipster-v4-minibook -P125

JHipster’s development guide recommends the following workflow:

  1. Modify your JPA entity (add a field, a relationship, etc.).
  2. Run mvn compile liquibase:diff.
  3. A new changelog is created in your src/main/resources/config/liquibase/changelog directory.
  4. Review this changelog and add it to your src/main/resources/config/liquibase/master.xml file, so it
    is applied the next time you run your application.
    If you use Gradle, you can use the same workflow by confirming database settings in liquibase.gradle
    and running ./gradlew liquibaseDiffChangelog.

JHipster的开发指引推荐遵循下面几个步骤:

  1. 修改你的JPA实体(新增一个域,一个连接等等)(使用yo jhipster:entity,而非手工修改entity的源码)
  2. 执行mvn命令 mvn compile liquibase:diff
  3. 在src/main/resources/config/liquibase/changelog目录里面就会有一个新的changelog文件.
  4. 检查这个changelog文件,并把它"手工"添加到src/main/resources/config/liquibase/master.xml文件中,这样下次启动应用的时候,对实体对象的修改,就生效了.
    --注意,这个步骤的核心,是没有写任何的sql,去修改DB.
    如果使用的gradle,那你也将使用类似的工作流程进行数据库配置,区别是使用的是liquibase.gradle并且在第二步执行
    ./gradlew liquibaseDiffChangelog

domain和repository

这两个目录倒是没特别的,根据JHipster的建议,domain应该根据JDL自动生成的,并且.jhipster目录下有一个json文件对应。

mapstruct

实际应用中Dao实体与数据传输对象DTO对象一致或有少量属性不同。如果一致自然可以重复使用,无需额外声明。如果不同,要嘛抽象出基类在进行扩展(增加了额外的逻辑),要嘛重新定义一个DTO类(存在大量代码重复,且需同时维护2个对象)。

用Refeletion反射机制虽然也是可行的方案,但是效率还是相对较低的。mapstruct作为“编译器插件“,能够在编译的时候生成”类似“手工编写的JavaBean。

JHipster中,试验性的加入了mapstruct功能,pom.xml中是有引入依赖的,但是生成的代码中并没有使用,如果想用的,可以自行添加@Mappr进行代码简化。

资源和书籍推荐

Use Liquibase to Safely Evolve Your Database Schema(http://www.baeldung.com/liquibase-refactor-schema-of-java-app
liquibase官网(http://www.liquibase.org/)
Liquibase 筆記(http://ju.outofmemory.cn/entry/90761)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容