Java单元测试实践-00.目录(9万多字文档+700多测试示例)
1. 使用JPA自动创建数据库表
以上在单元测试中使用H2数据库的方法,需要在连接数据库时指定数据库建表语句,建表语句需要从数据库服务器获取,由于不同的数据库建表语句存在一些差别,可能并不能直接在H2数据库中使用(如前文所述MySQL建表语句中H2数据库中存在部分不支持),不是最佳的选择。
可以在单元测试中使用JPA,利用JPA根据Entity自动创建数据库表的功能,屏蔽不同数据库的建语句的差异,简化处理H2数据库建表语句的操作。
1.1. JPA相关
1.1.1. 生成JPA Entity
JPA相关资料整理及根据数据库表生成JPA Entity的Java组件增强版使用说明,可参考 https://github.com/Adrninistrator/jpa-entity-generator-enhance ,在开源项目的基础上进行了优化。
1.1.2. EntityManager
参考ejb-3_0-fr-spec-persistence.pdf,“3.1 EntityManager”。
EntityManager实例与持久性上下文关联。持久性上下文是一组Entity实例,对于任何持久性Entity标识,都有一个唯一的Entity实例。持久性上下文对Entity实例及其生命周期进行管理。EntityManager接口定义了用于与持久性上下文进行交互的方法。EntityManager API用于创建和删除持久Entity实例,通过主键查找Entity,以及查询Entity。
1.1.3. EntityManagerFactory
参考ejb-3_0-fr-spec-persistence.pdf,“5.4 The EntityManagerFactory Interface”。
应用程序使用EntityManagerFactory接口来获取应用程序管理的EntityManager。当应用程序使用完EntityManagerFactory后,在应用程序关闭时,应关闭EntityManagerFactory。一旦EntityManagerFactory关闭,则其所有EntityManager都被视为处于关闭状态。
1.1.4. JPA自动建表参数配置
Hibernate实现了JPA规范。参考 https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#schema-generation 。
Hibernate允许从Entity映射生成数据库。
schema自动生成的功能对于测试非常有用,但不建议在生产环境中使用。
从Entity映射生成模式的过程称为HBM2DDL。
参考 https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#configurations-hbmddl 。
hibernate.hbm2ddl.auto参数用于设置为在SessionFactory生命周期中自动执行SchemaManagementTool操作。支持的参数值如下所示:
参数值 | 说明 |
none | 不执行任何操作(默认值) |
create-only | 创建数据库 |
drop | 删除数据库 |
create | 先删除数据库,再创建数据库 |
create-drop | 在SessionFactory创建时删除schema并重新创建,当SessionFactory关闭时删除schema |
validate | 验证数据库schema |
update | 更新数据库schema |
经测试,设置hibernate.hbm2ddl.auto=update,在应用启动时,若数据库表不存在时会创建;对于已存在的数据库表,缺失的字段会增加,多余的字段不会删除。
1.1.5. 自动建表时打印SQL语句
参考 https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#_sql_statement_logging 。
当hibernate.show_sql参数值为true时,会在控制台打印所有的SQL语句,该参数默认值为false。
当hibernate.format_sql参数值为true时,会在日志和控制台中以更直观的方式打印SQL。
1.1.6. hibernate依赖组件
参考 https://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html_single/#d0e215 。
兼容JPA 2.0的Hibernate EntityManager建立在Hibernate core和Hibernate Annotations之上。从3.5版开始,将所有必需的模块捆绑在一个Hibernate发行版中:
模块 | 说明 |
Hibernate Core | 原生Hibernate API和核心引擎 |
Hibernate Annotations | 基于注释的映射 |
Hibernate EntityManager | JPA 2.0 API和livecycle语义实现 |
可以添加对org.hibernate:hibernate-entitymanager的依赖,所有必须的依赖例如hibernate-core与hibernate-annotations会被传递地拉取。
org.hibernate:hibernate-entitymanager新版(5.4.11.Final及之后)已被移动至org.hibernate:hibernate-core,可使用org.hibernate:hibernate-core:5.4.12.Final。
由于test模块中的Entity使用了Lombok,因此还需要添加依赖“testAnnotationProcessor ‘org.projectlombok:lombok:1.18.12’”。
1.1.7. Spring JPA配置
1.1.7.1. Spring JPA
参考 https://docs.spring.io/spring-framework/docs/4.3.26.RELEASE/spring-framework-reference/htmlsingle/#orm-jpa 。
Spring JPA可以使用org.springframework.orm.jpa包,对JPA提供了全面的支持,通过与Hibernate或JDO集成的方式。
Spring JPA提供了三种设置JPA EntityManagerFactory的方式,应用程序会使用其获取EntityManager。包括LocalEntityManagerFactoryBean、从JNDI获取EntityManagerFactory、LocalContainerEntityManagerFactoryBean。
1.1.7.2. JpaVendorAdapter
参考 https://docs.spring.io/spring-framework/docs/4.3.26.RELEASE/spring-framework-reference/htmlsingle/#orm-jpa-dialect 。
JpaDialect实现可以启用Spring支持的一些高级功能,通常以特定于供应商的方式。
JpaVendorAdapter是主要用于Spring的全功能LocalContainerEntityManagerFactoryBean设置的更广泛的提供程序适配器装置。JpaVendorAdapter将JpaDialect的功能与其他特定于提供程序的默认值结合在一起。指定HibernateJpaVendorAdapter或EclipseLinkJpaVendorAdapter分别是为Hibernate或EclipseLink自动配置EntityManagerFactory设置的最便捷方法。
参考 https://docs.spring.io/spring-framework/docs/4.3.26.RELEASE/javadoc-api/org/springframework/orm/jpa/JpaVendorAdapter.html 。
JpaVendorAdapter是SPI接口,允许将特定于供应商的行为插入Spring的EntityManagerFactory创建者中。
AbstractJpaVendorAdapter类实现了JpaVendorAdapter接口,AbstractJpaVendorAdapter类的子类包括OpenJpaVendorAdapter、HibernateJpaVendorAdapter、EclipseLinkJpaVendorAdapter。
1.1.7.3. LocalContainerEntityManagerFactoryBean
参考 https://docs.spring.io/spring-framework/docs/4.3.26.RELEASE/spring-framework-reference/htmlsingle/#orm-jpa-setup-lcemfb 。
使用LocalContainerEntityManagerFactoryBean可在基于Spring的应用程序环境中提供完整的JPA功能。包括Web容器(例如Tomcat)以及独立应用程序和集成测试。
LocalContainerEntityManagerFactoryBean可以完全控制EntityManagerFactory的配置,适用于需要细粒度自定义的环境。LocalContainerEntityManagerFactoryBean创建了一个PersistenceUnitInfo实例,基于persistence.xml文件、dataSourceLookup策略和指定的loadTimeWeaver。可以使用JNDI之外的自定义数据源并控制编织过程。
LocalContainerEntityManagerFactoryBean是最强大的JPA设置选项,可以在应用程序中进行灵活的本地配置。支持链接到已存在的JDBC数据源的链接,同时支持本地和全局事务等功能。
参考 https://docs.spring.io/spring-framework/docs/4.3.26.RELEASE/javadoc-api/org/springframework/orm/jpa/LocalContainerEntityManagerFactoryBean.html ,LocalContainerEntityManagerFactoryBean包括以下属性:
- dataSource
dataSource参数用于指定JPA持久性提供程序用于访问数据库的JDBC数据源。这种方法传入一个Spring管理的DataSource,是将JDBC配置保留在persistence.xml中的一种替代方法。
在JPA的角度,这里传递的数据源将用作传递给持久性提供程序的PersistenceUnitInfo的“ nonJtaDataSource”,并覆盖persistence.xml中的数据源配置(如果有)。
注意:仅在未指定外部PersistenceUnitManager的情况下适用以上指定数据源的方法。
- jpaVendorAdapter
通过jpaVendorAdapter参数可以为所需的JPA提供程序指定JpaVendorAdapter实现(如果有)。会给定的提供程序初始化适当的默认值。
- packagesToScan
packagesToScan参数设置对classpath中的Entity类使用基于Spring的扫描,而不是使用JPA对jar文件中带有persistence.xml中的标记的标准扫描。当使用基于Spring的扫描时,不需要persistence.xml,所需要做的就是指定要搜索的基本包。
packagesToScan参数默认为无。指定包以在classpath中搜索对Entity类的自动检测。类似于Spring的组件扫描功能(ClassPathBeanDefinitionScanner)。
注意:仅在未指定外部PersistenceUnitManager的情况下适用以上指定packagesToScan参数的方法。
- jpaPropertyMap
指定Map形式的JPA属性,传给Persistence.createEntityManagerFactory()方法。
可在XML的bean定义中通过“map”或“props”元素填充。
jpaPropertyMap属性的设置方法包括setJpaPropertyMap()与setJpaProperties()。
可使用jpaProperties属性通过setJpaProperties()方法设置JPA属性,效果相同。setJpaProperties()方法支持在XML的bean定义中通过“props”元素填充,或通过"value"元素指定字符串格式数据填充,由PropertiesEditor解析。
1.2. JPA自动建表总结
在项目中引入Hibernate JPA实现,可以应用启动时通过Entity类自动创建对应的数据库表,可以屏蔽不同数据库的建表语句的细节差异。JPA仅用于自动创建数据库表,不通过JPA执行数据库操作。Entity类生成可以使用Java完成。
在项目中需要引入依赖javax.persistence:javax.persistence-api与org.hibernate:hibernate-core,以及lombok。
在Spring XML文件中需要定义LocalContainerEntityManagerFactoryBean对应的bean,将dataSource属性指定项目中使用的数据源,jpaVendorAdapter属性使用HibernateJpaVendorAdapter,packagesToScan属性指定Entity类所在的包,jpaProperties属性指定JPA属性,hibernate.hbm2ddl.auto应为update,以自动建表。
1.3. JPA自动建表配置
可参考示例项目中的JPA自动建表配置。
1.3.1. 添加依赖
在Gradle脚本中,添加了以下依赖:
"javax.persistence:javax.persistence-api:2.2",
"org.hibernate:hibernate-core:5.4.12.Final"
testAnnotationProcessor 'org.projectlombok:lombok:1.18.12'
1.3.2. Spring XML配置
参考示例项目src\test\resources\springJpa\springJpa.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- Spring JPA配置 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 配置JPA使用的数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置JPA提供程序适配器,使用Hibernate -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
</property>
<!-- 配置JPA Entity所在的包 -->
<property name="packagesToScan">
<array>
<value>adrninistrator.test_jpa.entity</value>
<value>adrninistrator.test_jpa.entity2</value>
</array>
</property>
<!-- 配置JPA属性 -->
<property name="jpaProperties">
<value>
# 自动创建或更新数据库表
hibernate.hbm2ddl.auto=update
# 打印SQL语句
hibernate.show_sql=true
hibernate.format_sql=true
</value>
</property>
</bean>
</beans>
当使用JPA自动建表时,会在Spring主配置文件applicationContext.xml中添加“<import resource=“springJpa/springJpa.xml”/>”。
LocalContainerEntityManagerFactoryBean的packagesToScan属性,支持指定单个或多个Entity类所在包,其他形式的设置如下所示:
<property name="packagesToScan" value="adrninistrator.test_jpa.entity"/>
<property name="packagesToScan">
<value>adrninistrator.test_jpa.entity, adrninistrator.test_jpa.entity2</value>
</property>
JPA属性支持通过jpaProperties、jpaPropertyMap属性进行其他形式的设置,如下所示:
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.hbm2ddl.auto" value="update"/>
<entry key="hibernate.show_sql" value="true"/>
<entry key="hibernate.format_sql" value="true"/>
</map>
</property>
1.3.3. H2数据库连接参数
当使用JPA自动建表时,在H2数据库的连接参数中,不再需要通过INIT参数指定创建数据库表的SQL脚本信息。
1.3.3.1. 使用H2嵌入(本地)模式
当使用H2数据库嵌入(本地)模式与JPA自动建表时,可参考示例项目unit_test_config.groovy文件中的use_h2_file_jpa元素。
在XML文件中指定的数据库连接URL参数示例如下:
jdbc:h2:file:./build/h2db;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;INIT=CREATE SCHEMA IF NOT EXISTS TEST\;SET SCHEMA TEST"
以上创建并设置SCHEMA是为了使生成的H2数据库文件能够使用数据库工具打开。
在properties文件中指定时,反斜杠需要转义为两个反斜杠“\\”;在Groovy配置文件中指定时,反斜杠需要转义为四个反斜杠“\\\\”。
1.3.3.2. 使用H2内存数据库模式
当使用H2内存数据库模式与JPA自动建表时,可参考示例项目unit_test_config.groovy文件中的use_h2_mem_jpa元素,数据库连接URL参数示例如下:
jdbc:h2:mem:;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE
1.4. JPA自动建表的时间类型
使用MySQL数据库表生成Entity类时,时间类型字段在Entity类中对应的类型使用java.sql.Timestamp。
使用JPA自动建表时,Entity类中的Timestamp类型的字段,在MySQL中创建的数据库表字段类型为datetime,不包含小数秒精度(即使MySQL原始数据库中对应字段包含);在H2中创建的数据库表字段类型为timestamp。
参考 https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html ,MySQL的时间类型的小数秒精度默认值为0(与标准SQL的默认值6不同),即时间数据的最小精度为秒。需要注意可能导致时间数据精度与预期不相符,导致更新时返回的受影响行数改变。
参考 http://h2database.com/html/datatypes.html#timestamp_type , H2的timestamp类型的小数秒精度默认值为6,即时间数据的最小精度比秒更小。
1.5. 指定JPA自动建表的字段定义
参考 https://docs.jboss.org/hibernate/jpa/2.2/api/javax/persistence/Column.html#columnDefinition-- 。
Entity类中的@Column注解的columnDefinition属性,可以指定生成列的DDL时使用的SQL片段。
通过Entity类中的@Column注解的columnDefinition属性,可以指定创建数据库表时的对应字段的类型、非空属性、默认值、注释等字段定义,会覆盖Entity类对应字段的类型,及@Column中的length、nullable、precision、scale等属性对于字段的设置。
可参考示例项目TestTableOther类,在JPA自动建表时,创建的数据库表的字段定义与columnDefinition属性一致。
Entity类中的@Column注解的columnDefinition属性示例如下:
@Column(name = "\"flag\"", columnDefinition = "varchar(32) default 'test' COMMENT 'flag'")
private String flag;
columnDefinition属性可以准确指定数据库字段的定义,但不同数据库的语句细节可能存在差异,因此在根据数据库表生成JPA Entity类时,未使用columnDefinition属性。数据库字段的默认值只能通过columnDefinition属性指定,当需要使用数据库字段的默认值时,需要单独处理。