@@ -4,7 +4,9 @@ import jakarta.persistence.Tuple
4
4
import jakarta.persistence.criteria.CriteriaBuilder
5
5
import jakarta.persistence.criteria.CriteriaDelete
6
6
import jakarta.persistence.criteria.CriteriaQuery
7
+ import jakarta.persistence.criteria.Expression
7
8
import jakarta.persistence.criteria.Root
9
+ import org.hibernate.FlushMode
8
10
import org.hibernate.SessionFactory
9
11
import org.hibernate.cfg.Configuration
10
12
import org.jetbrains.kotlinx.dataframe.DataFrame
@@ -48,11 +50,11 @@ private fun SessionFactory.insertSampleData() {
48
50
val artist2 = ArtistsEntity (name = " Queen" )
49
51
session.persist(artist1)
50
52
session.persist(artist2)
53
+ session.flush()
51
54
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!! ))
56
58
// customers we'll analyze using DataFrame
57
59
session.persist(
58
60
CustomersEntity (
@@ -82,14 +84,14 @@ private fun SessionFactory.insertSampleData() {
82
84
}
83
85
84
86
private fun SessionFactory.loadCustomersAsDataFrame (): DataFrame <DfCustomers > {
85
- return withSession { session ->
87
+ return withReadOnlyTransaction { session ->
86
88
val criteriaBuilder: CriteriaBuilder = session.criteriaBuilder
87
89
val criteriaQuery: CriteriaQuery <CustomersEntity > = criteriaBuilder.createQuery(CustomersEntity ::class .java)
88
90
val root: Root <CustomersEntity > = criteriaQuery.from(CustomersEntity ::class .java)
89
91
criteriaQuery.select(root)
90
92
91
93
session.createQuery(criteriaQuery)
92
- .list()
94
+ .resultList
93
95
.map { c ->
94
96
DfCustomers (
95
97
address = c.address,
@@ -111,6 +113,12 @@ private fun SessionFactory.loadCustomersAsDataFrame(): DataFrame<DfCustomers> {
111
113
}
112
114
}
113
115
116
+ /* * DTO used for aggregation projection. */
117
+ private data class CountryCountDto (
118
+ val country : String ,
119
+ val customerCount : Long ,
120
+ )
121
+
114
122
/* *
115
123
* **Hibernate + Criteria API:**
116
124
* - ✅ Database-level aggregation (efficient)
@@ -119,26 +127,29 @@ private fun SessionFactory.loadCustomersAsDataFrame(): DataFrame<DfCustomers> {
119
127
* - ❌ Limited to SQL-like operations
120
128
*/
121
129
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)
126
134
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
+ ),
135
146
)
147
+ cq.groupBy(countryPath)
148
+ cq.orderBy(cb.desc(countExpr))
136
149
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" )
142
153
}
143
154
}
144
155
}
@@ -183,18 +194,18 @@ private fun SessionFactory.replaceCustomersFromDataFrame(df: DataFrame<DfCustome
183
194
private fun DataRow<DfCustomers>.toCustomersEntity (): CustomersEntity {
184
195
return CustomersEntity (
185
196
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 ,
198
209
)
199
210
}
200
211
@@ -215,6 +226,25 @@ private inline fun SessionFactory.withTransaction(block: (session: org.hibernate
215
226
}
216
227
}
217
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
+
218
248
private fun buildSessionFactory (): SessionFactory {
219
249
// Load configuration from resources/hibernate/hibernate.cfg.xml
220
250
return Configuration ().configure(" hibernate/hibernate.cfg.xml" ).buildSessionFactory()
0 commit comments