回文集目录:JHipster一知半解
老实说,Database配置内容相当多的,包括数据库选择,ORM工具选择,数据库连接池的选择等。JHipster主要使用了Hibernate系列的JPA架构,从最大程度满足了主流的数据库(甚至可以是非关系型数据库)。这也契合微服务的理念,要求ORM模型尽量简单,尽量把服务(业务功能)进行拆分,而不是把相关内容糅合在一起。换句话说,虽然JHipster看上去高大上,毕竟还是提供了一个框架(毛呸房),还是要求进行详尽的功能划分(细装修)才能真正实现微服务的架构。
下面按照比较通用的mysql数据库的配置,进行分析
Pom.xml
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
com.h2database
h2
com.zaxxer
HikariCP
org.hibernate
hibernate-jcache
${hibernate.version}
org.hibernate
hibernate-envers
org.hibernate
hibernate-validator
org.liquibase
liquibase-core
其中spring-boot-starter-data-jpa也是一个复合pom,里面依赖spring-data-jpa、hibernate-core、spring-boot-starter-jdbc(包含tomcat-jdbc和spring-jdbc)
从依赖关系可以猜出,默认使用的连接池是tomcat-jdbc,具体的代码
- 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。
- 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模板。
- 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,
ObjectProvider 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 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中
有特别
org.liquibase.ext
liquibase-hibernate5
${liquibase-hibernate5.version}
org.liquibase.ext
liquibase-hibernate5
${liquibase-hibernate5.version}
这样才能根据DB与实体Entity的不同,生成ChangeLog.
JHipster-v4-minibook -P125
JHipster’s development guide recommends the following workflow:
- Modify your JPA entity (add a field, a relationship, etc.).
- Run mvn compile liquibase:diff.
- A new changelog is created in your src/main/resources/config/liquibase/changelog directory.
- 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的开发指引推荐遵循下面几个步骤:
- 修改你的JPA实体(新增一个域,一个连接等等)(使用yo jhipster:entity,而非手工修改entity的源码)
- 执行mvn命令 mvn compile liquibase:diff
- 在src/main/resources/config/liquibase/changelog目录里面就会有一个新的changelog文件.
- 检查这个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)