您可以为每个线程创建一个不同的 Hibernate 内存数据库(用于测试)吗?



我一直在实现Hibernate和JPA接口,用于由映射支持的测试。 这种数据结构构成了一个非常简单的"数据库":

// All my database classes implement this (Kotlin) interface.
// "Idd" means "having an ID" as in "ID'd"
interface Idd {
val id: Long
}
// This simple data structure works as an in-memory database.
//
// The Class is the name of the mapped entity class (which extends Idd)
//
// The inner map uses the ID as the key and the actual objects as
// the value.
//
// ID's are assigned by looking up the Class in the outer map
// and adding one to the size of the inner map (in a synchronized block).
map: SortedMap<Class<Idd>,
MutableMap<Long, Idd>>

我包装了Hibernate的EntityManager,以便测试可以传递新映射,而不是连接到真正的数据库。 我为每个测试传递一个单独的映射,并让测试设置它所需的任何数据,以便在不同线程中同时运行的测试彼此完全独立。

这很好用(这对我的测试来说是一个突破(,但是相关的Hibernate/JPA类有很多方法,并且在Hibernate版本之间发生了很大的变化。

我听说Hibernate附带了一个内存数据库。 这可以与每个线程的单独数据库一起使用进行测试吗? 一个例子会很棒!

附言我使用排序/树状图,因为我可以按可靠的顺序打印出来进行调试。

使用 H2 内存数据库

我在我的问题中尝试了地图的地图想法进行测试,但我被诱惑实现了越来越多的Hibernate接口,这些接口随着每个新的Hibernate版本而改变。 而且,它变得非常复杂。 最终,我删除了所有这些,改用了H2内存数据库。

您需要将依赖项添加到构建中(仅限测试范围(:

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>

考虑对所有测试使用相同的数据库

我的项目在我的机器上,大约需要 2 秒钟来创建 H2 数据库,让 Hibernate 滚动,并在第一次测试之前启动整个事情。 如果您可以帮助它,您不想在每个测试中添加等待! 最好只初始化一次 H2 数据库,并为每个测试使用不同的根节点,这样数据库中的任何内容都不会相交。

我的数据库中的所有内容都按具有独特name字段的公司隔离。 用户有公司等。 所以在每次测试开始时,我都会说,co = newCo("nameUniqueToThisTest")然后new User(co, ...)等等。 这样,来自 2 个测试的数据永远不会覆盖。 如果是这样,那么您的数据孤岛就不能很好地工作,这是一个更大的问题,很好发现!

以下是确保唯一性的 newCo(( 方法:

private val coNames: MutableSet<String> = mutableSetOf()
@JvmStatic
fun newCo(nm: String): Company {
synchronized(coNames) {
val lcName: String = nm.toLowerCase()
if (coNames.contains(lcName)) {
throw IllegalArgumentException("Can't have two companies with the same name (case insensitive): $nm")
}
coNames.add(lcName)
}
val co = Company(nm)
save(co) // assigns ID
return co
}

如果数据库未分离,则可能需要为每个测试创建一个新数据库。 如果是这样,您将不需要延迟初始化,因为您将为每个测试使用不同的 init。 在这种情况下,请确保更改每个测试的myDb数据库名称,以便它们不会在每次运行时以不同的顺序覆盖彼此的数据。

我的惰性初始化解决方案

我的解决方案在 Kotlin 中使用了一个延迟初始化的线程安全变量来确保 Hibernate 和数据库以及数据库创建仅在第一次测试之前发生一次,并由所有后续测试共享(请参阅上面关于唯一性的注释(。 在Java中,你可以使用LazyRef或其他一些线程安全的一次性初始化来代替Kotlin的by lazy

private val initialized: Boolean by lazy {
// TODO: Initialize Logging Here!
val factory: EntityManagerFactory = try {
val props = mapOf(
// Tell Hibernate to use H2, in memory, the myDb database.
"hibernate.connection.url" to "jdbc:h2:mem:myDb",
"hibernate.connection.useUnicode" to "true",
"hibernate.connection.characterEncoding" to "UTF-8",
"hibernate.hbm2ddl.auto" to "validate",
// c3p0.max_size is the only option needed to enable c3p0
"hibernate.c3p0.max_size" to "60",
"hibernate.c3p0.timeout" to "599",
"hibernate.bytecode.use_reflection_optimizer" to "false",
"hibernate.current_session_context_class" to "thread",
"hibernate.format_sql" to "true",
"hibernate.max_fetch_depth" to "3",
// Without the drop, had more schema initialization issues.
"javax.persistence.schema-generation.database.action" to "drop-and-create",
// New for MySQL 8?
"javax.persistence.sharedCache.mode" to "NONE",
"javax.persistence.validation.mode" to "NONE")
val tempFactory: EntityManagerFactory = Persistence.createEntityManagerFactory(
"my.app.persistence",
props)
println("H2 EntityManager Initialized")
// Returns the tempFactory just created.
// The try-block evaluates in Kotlin.
tempFactory
} catch (ex:Throwable) {
// Make sure you log the exception, as it might be swallowed
println("ERROR: Initial JPA entityManagerFactory creation failed:n$ex")
println("Caused by:n" + ex.cause)
throw ex
}
entityManagerFactory.set(factory)
commit()
// Return true as the value for the `initialized` variable.
true
}
@JvmStatic
fun testDbInMemory() {
if (!initialized) {
throw IllegalStateException("Couldn't initialize Hibernate")
}
}

然后在每个 Junit(kotlin 测试(测试类文件的顶部,使用我放置的数据库:

@BeforeTest
fun before() {
TestGlobals.testDbInMemory()
}
@AfterTest
fun after() { HibernateUtil.commit() }

BeforeTest是为了确保 db 初始化。AfterTest是为了确保任何未提交的事务在(或接近(与它们相关的测试中爆炸。 它使调试变得更加容易。

最新更新