Skip to content

Commit a24e5a5

Browse files
authored
Add Hibernate example with H2 in-memory database to idea-examples (#1367)
* Add Hibernate example with H2 in-memory database to idea-examples This commit introduces a new example demonstrating the integration of Kotlin DataFrame with Hibernate ORM and H2 in-memory database. It includes setup for Hibernate, entity mappings, and analytical operations using DataFrame. Dependencies and configuration for Hibernate and H2 are updated accordingly. * Refactor Hibernate example: improve session handling, replace field access, and enhance query projection.
1 parent 58a2d6a commit a24e5a5

File tree

6 files changed

+398
-0
lines changed

6 files changed

+398
-0
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ val modulesUsingJava11 = with(projects) {
157157
dataframeJupyter,
158158
dataframeGeoJupyter,
159159
examples.ideaExamples.titanic,
160+
examples.ideaExamples.unsupportedDataSources,
160161
tests,
161162
plugins.dataframeGradlePlugin,
162163
)

examples/idea-examples/unsupported-data-sources/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ dependencies {
2525
implementation(libs.exposed.json)
2626
implementation(libs.exposed.money)
2727

28+
// Hibernate + H2 + HikariCP (for Hibernate example)
29+
implementation(libs.hibernate.core)
30+
implementation(libs.hibernate.hikaricp)
31+
implementation(libs.hikaricp)
32+
33+
implementation(libs.h2db)
34+
implementation(libs.sl4jsimple)
35+
2836
// (kotlin) spark support
2937
implementation(libs.kotlin.spark)
3038
compileOnly(libs.spark)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.jetbrains.kotlinx.dataframe.examples.hibernate
2+
3+
import jakarta.persistence.Column
4+
import jakarta.persistence.Entity
5+
import jakarta.persistence.GeneratedValue
6+
import jakarta.persistence.GenerationType
7+
import jakarta.persistence.Id
8+
import jakarta.persistence.Table
9+
import org.jetbrains.kotlinx.dataframe.annotations.ColumnName
10+
import org.jetbrains.kotlinx.dataframe.annotations.DataSchema
11+
12+
@Entity
13+
@Table(name = "Albums")
14+
class AlbumsEntity(
15+
@Id
16+
@GeneratedValue(strategy = GenerationType.IDENTITY)
17+
@Column(name = "AlbumId")
18+
var albumId: Int? = null,
19+
20+
@Column(name = "Title", length = 160, nullable = false)
21+
var title: String = "",
22+
23+
@Column(name = "ArtistId", nullable = false)
24+
var artistId: Int = 0,
25+
)
26+
27+
@Entity
28+
@Table(name = "Artists")
29+
class ArtistsEntity(
30+
@Id
31+
@GeneratedValue(strategy = GenerationType.IDENTITY)
32+
@Column(name = "ArtistId")
33+
var artistId: Int? = null,
34+
35+
@Column(name = "Name", length = 120, nullable = false)
36+
var name: String = "",
37+
)
38+
39+
@Entity
40+
@Table(name = "Customers")
41+
class CustomersEntity(
42+
@Id
43+
@GeneratedValue(strategy = GenerationType.IDENTITY)
44+
@Column(name = "CustomerId")
45+
var customerId: Int? = null,
46+
47+
@Column(name = "FirstName", length = 40, nullable = false)
48+
var firstName: String = "",
49+
50+
@Column(name = "LastName", length = 20, nullable = false)
51+
var lastName: String = "",
52+
53+
@Column(name = "Company", length = 80)
54+
var company: String? = null,
55+
56+
@Column(name = "Address", length = 70)
57+
var address: String? = null,
58+
59+
@Column(name = "City", length = 40)
60+
var city: String? = null,
61+
62+
@Column(name = "State", length = 40)
63+
var state: String? = null,
64+
65+
@Column(name = "Country", length = 40)
66+
var country: String? = null,
67+
68+
@Column(name = "PostalCode", length = 10)
69+
var postalCode: String? = null,
70+
71+
@Column(name = "Phone", length = 24)
72+
var phone: String? = null,
73+
74+
@Column(name = "Fax", length = 24)
75+
var fax: String? = null,
76+
77+
@Column(name = "Email", length = 60, nullable = false)
78+
var email: String = "",
79+
80+
@Column(name = "SupportRepId")
81+
var supportRepId: Int? = null,
82+
)
83+
84+
// DataFrame schema to get typed accessors similar to Exposed example
85+
@DataSchema
86+
data class DfCustomers(
87+
@ColumnName("Address") val address: String?,
88+
@ColumnName("City") val city: String?,
89+
@ColumnName("Company") val company: String?,
90+
@ColumnName("Country") val country: String?,
91+
@ColumnName("CustomerId") val customerId: Int,
92+
@ColumnName("Email") val email: String,
93+
@ColumnName("Fax") val fax: String?,
94+
@ColumnName("FirstName") val firstName: String,
95+
@ColumnName("LastName") val lastName: String,
96+
@ColumnName("Phone") val phone: String?,
97+
@ColumnName("PostalCode") val postalCode: String?,
98+
@ColumnName("State") val state: String?,
99+
@ColumnName("SupportRepId") val supportRepId: Int?,
100+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package org.jetbrains.kotlinx.dataframe.examples.hibernate
2+
3+
import jakarta.persistence.Tuple
4+
import jakarta.persistence.criteria.CriteriaBuilder
5+
import jakarta.persistence.criteria.CriteriaDelete
6+
import jakarta.persistence.criteria.CriteriaQuery
7+
import jakarta.persistence.criteria.Expression
8+
import jakarta.persistence.criteria.Root
9+
import org.hibernate.FlushMode
10+
import org.hibernate.SessionFactory
11+
import org.hibernate.cfg.Configuration
12+
import org.jetbrains.kotlinx.dataframe.DataFrame
13+
import org.jetbrains.kotlinx.dataframe.DataRow
14+
import org.jetbrains.kotlinx.dataframe.api.asSequence
15+
import org.jetbrains.kotlinx.dataframe.api.count
16+
import org.jetbrains.kotlinx.dataframe.api.describe
17+
import org.jetbrains.kotlinx.dataframe.api.groupBy
18+
import org.jetbrains.kotlinx.dataframe.api.print
19+
import org.jetbrains.kotlinx.dataframe.api.sortByDesc
20+
import org.jetbrains.kotlinx.dataframe.api.toDataFrame
21+
import org.jetbrains.kotlinx.dataframe.size
22+
23+
/**
24+
* Example showing Kotlin DataFrame with Hibernate ORM + H2 in-memory DB.
25+
* Mirrors logic from the Exposed example: load data, convert to DataFrame, group/describe, write back.
26+
*/
27+
fun main() {
28+
val sessionFactory: SessionFactory = buildSessionFactory()
29+
30+
sessionFactory.insertSampleData()
31+
32+
val df = sessionFactory.loadCustomersAsDataFrame()
33+
34+
// Pure Hibernate + Criteria API approach for counting customers per country
35+
println("=== Hibernate + Criteria API Approach ===")
36+
sessionFactory.countCustomersPerCountryWithHibernate()
37+
38+
println("\n=== DataFrame Approach ===")
39+
df.analyzeAndPrintResults()
40+
41+
sessionFactory.replaceCustomersFromDataFrame(df)
42+
43+
sessionFactory.close()
44+
}
45+
46+
private fun SessionFactory.insertSampleData() {
47+
withTransaction { session ->
48+
// a few artists and albums (minimal, not used further; just demo schema)
49+
val artist1 = ArtistsEntity(name = "AC/DC")
50+
val artist2 = ArtistsEntity(name = "Queen")
51+
session.persist(artist1)
52+
session.persist(artist2)
53+
session.flush()
54+
55+
session.persist(AlbumsEntity(title = "High Voltage", artistId = artist1.artistId!!))
56+
session.persist(AlbumsEntity(title = "Back in Black", artistId = artist1.artistId!!))
57+
session.persist(AlbumsEntity(title = "A Night at the Opera", artistId = artist2.artistId!!))
58+
// customers we'll analyze using DataFrame
59+
session.persist(
60+
CustomersEntity(
61+
firstName = "John",
62+
lastName = "Doe",
63+
email = "[email protected]",
64+
country = "USA",
65+
),
66+
)
67+
session.persist(
68+
CustomersEntity(
69+
firstName = "Jane",
70+
lastName = "Smith",
71+
email = "[email protected]",
72+
country = "USA",
73+
),
74+
)
75+
session.persist(
76+
CustomersEntity(
77+
firstName = "Alice",
78+
lastName = "Wang",
79+
email = "[email protected]",
80+
country = "Canada",
81+
),
82+
)
83+
}
84+
}
85+
86+
private fun SessionFactory.loadCustomersAsDataFrame(): DataFrame<DfCustomers> {
87+
return withReadOnlyTransaction { session ->
88+
val criteriaBuilder: CriteriaBuilder = session.criteriaBuilder
89+
val criteriaQuery: CriteriaQuery<CustomersEntity> = criteriaBuilder.createQuery(CustomersEntity::class.java)
90+
val root: Root<CustomersEntity> = criteriaQuery.from(CustomersEntity::class.java)
91+
criteriaQuery.select(root)
92+
93+
session.createQuery(criteriaQuery)
94+
.resultList
95+
.map { c ->
96+
DfCustomers(
97+
address = c.address,
98+
city = c.city,
99+
company = c.company,
100+
country = c.country,
101+
customerId = c.customerId ?: -1,
102+
email = c.email,
103+
fax = c.fax,
104+
firstName = c.firstName,
105+
lastName = c.lastName,
106+
phone = c.phone,
107+
postalCode = c.postalCode,
108+
state = c.state,
109+
supportRepId = c.supportRepId,
110+
)
111+
}
112+
.toDataFrame()
113+
}
114+
}
115+
116+
/** DTO used for aggregation projection. */
117+
private data class CountryCountDto(
118+
val country: String,
119+
val customerCount: Long,
120+
)
121+
122+
/**
123+
* **Hibernate + Criteria API:**
124+
* - ✅ Database-level aggregation (efficient)
125+
* - ✅ Type-safe queries
126+
* - ❌ Verbose syntax
127+
* - ❌ Limited to SQL-like operations
128+
*/
129+
private fun SessionFactory.countCustomersPerCountryWithHibernate() {
130+
withReadOnlyTransaction { session ->
131+
val cb = session.criteriaBuilder
132+
val cq: CriteriaQuery<CountryCountDto> = cb.createQuery(CountryCountDto::class.java)
133+
val root: Root<CustomersEntity> = cq.from(CustomersEntity::class.java)
134+
135+
val countryPath = root.get<String>("country")
136+
val idPath = root.get<Long>("customerId")
137+
138+
val countExpr = cb.count(idPath)
139+
140+
cq.select(
141+
cb.construct(
142+
CountryCountDto::class.java,
143+
countryPath, // country
144+
countExpr, // customerCount
145+
),
146+
)
147+
cq.groupBy(countryPath)
148+
cq.orderBy(cb.desc(countExpr))
149+
150+
val results = session.createQuery(cq).resultList
151+
results.forEach { dto ->
152+
println("${dto.country}: ${dto.customerCount} customers")
153+
}
154+
}
155+
}
156+
157+
/**
158+
* **DataFrame approach: **
159+
* - ✅ Rich analytical operations
160+
* - ✅ Fluent, readable API
161+
* - ✅ Flexible data transformations
162+
* - ❌ In-memory processing (less efficient for large datasets)
163+
*/
164+
private fun DataFrame<DfCustomers>.analyzeAndPrintResults() {
165+
println(size())
166+
167+
// same operation as Exposed example: customers per country
168+
groupBy { country }.count()
169+
.sortByDesc { "count"<Int>() }
170+
.print(columnTypes = true, borders = true)
171+
172+
// general statistics
173+
describe()
174+
.print(columnTypes = true, borders = true)
175+
}
176+
177+
private fun SessionFactory.replaceCustomersFromDataFrame(df: DataFrame<DfCustomers>) {
178+
withTransaction { session ->
179+
val criteriaBuilder: CriteriaBuilder = session.criteriaBuilder
180+
val criteriaDelete: CriteriaDelete<CustomersEntity> =
181+
criteriaBuilder.createCriteriaDelete(CustomersEntity::class.java)
182+
criteriaDelete.from(CustomersEntity::class.java)
183+
184+
session.createMutationQuery(criteriaDelete).executeUpdate()
185+
}
186+
187+
withTransaction { session ->
188+
df.asSequence().forEach { row ->
189+
session.persist(row.toCustomersEntity())
190+
}
191+
}
192+
}
193+
194+
private fun DataRow<DfCustomers>.toCustomersEntity(): CustomersEntity {
195+
return CustomersEntity(
196+
customerId = null, // let DB generate
197+
firstName = this.firstName,
198+
lastName = this.lastName,
199+
company = this.company,
200+
address = this.address,
201+
city = this.city,
202+
state = this.state,
203+
country = this.country,
204+
postalCode = this.postalCode,
205+
phone = this.phone,
206+
fax = this.fax,
207+
email = this.email,
208+
supportRepId = this.supportRepId,
209+
)
210+
}
211+
212+
private inline fun <T> SessionFactory.withSession(block: (session: org.hibernate.Session) -> T): T {
213+
return openSession().use(block)
214+
}
215+
216+
private inline fun SessionFactory.withTransaction(block: (session: org.hibernate.Session) -> Unit) {
217+
withSession { session ->
218+
session.beginTransaction()
219+
try {
220+
block(session)
221+
session.transaction.commit()
222+
} catch (e: Exception) {
223+
session.transaction.rollback()
224+
throw e
225+
}
226+
}
227+
}
228+
229+
/** Read-only transaction helper for SELECT queries to minimize overhead. */
230+
private inline fun <T> SessionFactory.withReadOnlyTransaction(block: (session: org.hibernate.Session) -> T): T {
231+
return withSession { session ->
232+
session.beginTransaction()
233+
// Minimize overhead for read operations
234+
session.isDefaultReadOnly = true
235+
session.hibernateFlushMode = FlushMode.MANUAL
236+
try {
237+
val result = block(session)
238+
session.transaction.commit()
239+
result
240+
} catch (e: Exception) {
241+
session.transaction.rollback()
242+
throw e
243+
}
244+
}
245+
}
246+
247+
248+
private fun buildSessionFactory(): SessionFactory {
249+
// Load configuration from resources/hibernate/hibernate.cfg.xml
250+
return Configuration().configure("hibernate/hibernate.cfg.xml").buildSessionFactory()
251+
}

0 commit comments

Comments
 (0)