Skip to content

Commit 2f4a1e9

Browse files
committed
Refactor Hibernate example: improve session handling, replace field access, and enhance query projection.
1 parent 474a031 commit 2f4a1e9

File tree

1 file changed

+65
-35
lines changed
  • examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/hibernate

1 file changed

+65
-35
lines changed

examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/hibernate/main.kt

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import jakarta.persistence.Tuple
44
import jakarta.persistence.criteria.CriteriaBuilder
55
import jakarta.persistence.criteria.CriteriaDelete
66
import jakarta.persistence.criteria.CriteriaQuery
7+
import jakarta.persistence.criteria.Expression
78
import jakarta.persistence.criteria.Root
9+
import org.hibernate.FlushMode
810
import org.hibernate.SessionFactory
911
import org.hibernate.cfg.Configuration
1012
import org.jetbrains.kotlinx.dataframe.DataFrame
@@ -48,11 +50,11 @@ private fun SessionFactory.insertSampleData() {
4850
val artist2 = ArtistsEntity(name = "Queen")
4951
session.persist(artist1)
5052
session.persist(artist2)
53+
session.flush()
5154

52-
session.persist(AlbumsEntity(title = "High Voltage", artistId = 1))
53-
session.persist(AlbumsEntity(title = "Back in Black", artistId = 1))
54-
session.persist(AlbumsEntity(title = "A Night at the Opera", artistId = 2))
55-
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!!))
5658
// customers we'll analyze using DataFrame
5759
session.persist(
5860
CustomersEntity(
@@ -82,14 +84,14 @@ private fun SessionFactory.insertSampleData() {
8284
}
8385

8486
private fun SessionFactory.loadCustomersAsDataFrame(): DataFrame<DfCustomers> {
85-
return withSession { session ->
87+
return withReadOnlyTransaction { session ->
8688
val criteriaBuilder: CriteriaBuilder = session.criteriaBuilder
8789
val criteriaQuery: CriteriaQuery<CustomersEntity> = criteriaBuilder.createQuery(CustomersEntity::class.java)
8890
val root: Root<CustomersEntity> = criteriaQuery.from(CustomersEntity::class.java)
8991
criteriaQuery.select(root)
9092

9193
session.createQuery(criteriaQuery)
92-
.list()
94+
.resultList
9395
.map { c ->
9496
DfCustomers(
9597
address = c.address,
@@ -111,6 +113,12 @@ private fun SessionFactory.loadCustomersAsDataFrame(): DataFrame<DfCustomers> {
111113
}
112114
}
113115

116+
/** DTO used for aggregation projection. */
117+
private data class CountryCountDto(
118+
val country: String,
119+
val customerCount: Long,
120+
)
121+
114122
/**
115123
* **Hibernate + Criteria API:**
116124
* - ✅ Database-level aggregation (efficient)
@@ -119,26 +127,29 @@ private fun SessionFactory.loadCustomersAsDataFrame(): DataFrame<DfCustomers> {
119127
* - ❌ Limited to SQL-like operations
120128
*/
121129
private fun SessionFactory.countCustomersPerCountryWithHibernate() {
122-
withSession { session ->
123-
val criteriaBuilder: CriteriaBuilder = session.criteriaBuilder
124-
val query: CriteriaQuery<Tuple> = criteriaBuilder.createTupleQuery()
125-
val root: Root<CustomersEntity> = query.from(CustomersEntity::class.java)
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)
126134

127-
// Select country and count of customers, grouped by country, ordered by count DESC
128-
query.multiselect(
129-
root.get<String>("country").alias("country"),
130-
criteriaBuilder.count(root.get<Long>("customerId")).alias("customerCount")
131-
)
132-
query.groupBy(root.get<String>("country"))
133-
query.orderBy(
134-
criteriaBuilder.desc(criteriaBuilder.count(root.get<Long>("customerId")))
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+
),
135146
)
147+
cq.groupBy(countryPath)
148+
cq.orderBy(cb.desc(countExpr))
136149

137-
val results = session.createQuery(query).list()
138-
results.forEach { tuple ->
139-
val country = tuple.get("country", String::class.java)
140-
val count = tuple.get("customerCount", Long::class.java)
141-
println("$country: $count customers")
150+
val results = session.createQuery(cq).resultList
151+
results.forEach { dto ->
152+
println("${dto.country}: ${dto.customerCount} customers")
142153
}
143154
}
144155
}
@@ -183,18 +194,18 @@ private fun SessionFactory.replaceCustomersFromDataFrame(df: DataFrame<DfCustome
183194
private fun DataRow<DfCustomers>.toCustomersEntity(): CustomersEntity {
184195
return CustomersEntity(
185196
customerId = null, // let DB generate
186-
firstName = this["FirstName"] as String,
187-
lastName = this["LastName"] as String,
188-
company = this["Company"] as String?,
189-
address = this["Address"] as String?,
190-
city = this["City"] as String?,
191-
state = this["State"] as String?,
192-
country = this["Country"] as String?,
193-
postalCode = this["PostalCode"] as String?,
194-
phone = this["Phone"] as String?,
195-
fax = this["Fax"] as String?,
196-
email = this["Email"] as String,
197-
supportRepId = this["SupportRepId"] as Int?,
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,
198209
)
199210
}
200211

@@ -215,6 +226,25 @@ private inline fun SessionFactory.withTransaction(block: (session: org.hibernate
215226
}
216227
}
217228

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+
218248
private fun buildSessionFactory(): SessionFactory {
219249
// Load configuration from resources/hibernate/hibernate.cfg.xml
220250
return Configuration().configure("hibernate/hibernate.cfg.xml").buildSessionFactory()

0 commit comments

Comments
 (0)