Hibernate ORM (Kotlin)
Hibernate ORM is "a comprehensive object-relational mapping solution for Java and Kotlin" developed as the most mature ORM framework in the JVM ecosystem. With over 20 years of development experience, it enables large-scale data processing in complex enterprise applications. As the reference implementation of JPA (Jakarta Persistence API), it combines standard JPA APIs with proprietary extensions to provide type-safe database operations fully integrated with Kotlin language features. Through deep integration with modern frameworks like Spring Boot and Quarkus, it has established a solid position as the foundation for contemporary Kotlin application development.
GitHub Overview
hibernate/hibernate-orm
Hibernate's core Object/Relational Mapping functionality
Topics
Star History
Library
Hibernate ORM (Kotlin)
Overview
Hibernate ORM is "a comprehensive object-relational mapping solution for Java and Kotlin" developed as the most mature ORM framework in the JVM ecosystem. With over 20 years of development experience, it enables large-scale data processing in complex enterprise applications. As the reference implementation of JPA (Jakarta Persistence API), it combines standard JPA APIs with proprietary extensions to provide type-safe database operations fully integrated with Kotlin language features. Through deep integration with modern frameworks like Spring Boot and Quarkus, it has established a solid position as the foundation for contemporary Kotlin application development.
Details
Hibernate ORM 2025 edition provides next-generation ORM experience optimized for Kotlin development. Full support for Jakarta Persistence 3.0 provides the latest Java EE standards, and integration with Kotlin Coroutines allows natural handling of asynchronous processing. In addition to Hibernate's Criteria API, HQL, and native SQL queries, type-safe query construction through Kotlin extension functions is possible. It comes standard with enterprise-level performance features such as second-level caching, lazy loading, and batch processing optimization, achieving complete support for major databases including PostgreSQL, MySQL, Oracle, and SQL Server.
Key Features
- Jakarta Persistence Standard: JPA 3.0 compliant standardized ORM operations
- Kotlin Native Support: Deep integration with Kotlin language features and type safety
- Enterprise-Level Features: Second-level caching, connection pooling, transaction management
- Flexible Queries: Comprehensive support for HQL, Criteria API, and native SQL
- Performance Optimization: Lazy loading, batch processing, N+1 problem solutions
- Broad Database Support: Complete support for major RDBMS systems
Pros and Cons
Pros
- High stability and rich feature set due to over 20 years of track record
- Rich portability and learning resources due to JPA standard compliance
- Excellent integration with ecosystems like Spring Boot and Quarkus
- Enterprise-level performance optimization features
- Comprehensive documentation and active community support
- Improved development efficiency through affinity with Kotlin
Cons
- High learning cost and complex configuration required for beginners
- Excessive features and overhead for lightweight applications
- Configuration and annotations tend to be complex with much boilerplate
- Magic behavior can make debugging difficult in some cases
- Large memory usage, unsuitable for small projects
- Reliance on native queries can compromise portability
Reference Pages
Code Examples
Setup
// build.gradle.kts (Kotlin DSL)
dependencies {
implementation("org.hibernate.orm:hibernate-core:6.4.0.Final")
implementation("org.hibernate.orm:hibernate-hikaricp:6.4.0.Final")
implementation("jakarta.persistence:jakarta.persistence-api:3.1.0")
// Database drivers
implementation("org.postgresql:postgresql:42.7.1")
// or implementation("mysql:mysql-connector-java:8.0.33")
// Kotlin support
implementation("org.jetbrains.kotlin:kotlin-reflect")
// Spring Boot integration
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
// hibernate.properties or application.yml
// hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
// hibernate.connection.driver_class=org.postgresql.Driver
// hibernate.connection.url=jdbc:postgresql://localhost:5432/mydb
// hibernate.connection.username=user
// hibernate.connection.password=password
// hibernate.hbm2ddl.auto=update
// hibernate.show_sql=true
Basic Usage
import jakarta.persistence.*
import org.hibernate.annotations.GenericGenerator
import java.time.LocalDateTime
// Entity class definition
@Entity
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(nullable = false, length = 100)
val name: String,
@Column(unique = true, nullable = false)
val email: String,
@Column(name = "created_at")
val createdAt: LocalDateTime = LocalDateTime.now(),
@Column(name = "is_active")
val isActive: Boolean = true,
// Relationships
@OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
val posts: MutableList<Post> = mutableListOf()
) {
// JPA requires default constructor
constructor() : this(name = "", email = "")
}
@Entity
@Table(name = "posts")
data class Post(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(nullable = false)
val title: String,
@Column(columnDefinition = "TEXT")
val content: String,
@Column(name = "published_at")
val publishedAt: LocalDateTime? = null,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
val user: User,
@ElementCollection
@CollectionTable(name = "post_tags", joinColumns = [JoinColumn(name = "post_id")])
@Column(name = "tag")
val tags: MutableSet<String> = mutableSetOf()
) {
constructor() : this(title = "", content = "", user = User())
}
Query Execution
import jakarta.persistence.EntityManager
import jakarta.persistence.EntityManagerFactory
import jakarta.persistence.Persistence
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
class UserRepository(private val entityManager: EntityManager) {
// Basic CRUD operations
fun save(user: User): User {
return entityManager.merge(user)
}
fun findById(id: Long): User? {
return entityManager.find(User::class.java, id)
}
fun findAll(): List<User> {
val criteriaBuilder = entityManager.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(User::class.java)
val root = criteriaQuery.from(User::class.java)
criteriaQuery.select(root)
return entityManager.createQuery(criteriaQuery).resultList
}
fun delete(user: User) {
entityManager.remove(entityManager.merge(user))
}
// JPQL queries
fun findByEmail(email: String): User? {
return entityManager.createQuery(
"SELECT u FROM User u WHERE u.email = :email",
User::class.java
).setParameter("email", email)
.resultList
.firstOrNull()
}
fun findActiveUsers(): List<User> {
return entityManager.createQuery(
"SELECT u FROM User u WHERE u.isActive = true ORDER BY u.createdAt DESC",
User::class.java
).resultList
}
// Criteria API (type-safe)
fun findUsersByNamePattern(namePattern: String): List<User> {
val cb = entityManager.criteriaBuilder
val cq = cb.createQuery(User::class.java)
val root = cq.from(User::class.java)
cq.select(root)
cq.where(cb.like(cb.lower(root.get("name")), "%${namePattern.lowercase()}%"))
cq.orderBy(cb.asc(root.get<String>("name")))
return entityManager.createQuery(cq).resultList
}
// Native SQL queries
fun getUserStatistics(): List<Map<String, Any>> {
val query = entityManager.createNativeQuery("""
SELECT
DATE_TRUNC('month', created_at) as month,
COUNT(*) as user_count,
COUNT(CASE WHEN is_active THEN 1 END) as active_count
FROM users
GROUP BY DATE_TRUNC('month', created_at)
ORDER BY month DESC
""")
@Suppress("UNCHECKED_CAST")
return query.resultList as List<Array<Any>>
.map { row ->
mapOf(
"month" to row[0],
"userCount" to row[1],
"activeCount" to row[2]
)
}
}
}
Data Operations
import jakarta.persistence.EntityTransaction
class UserService(private val userRepository: UserRepository) {
fun createUser(name: String, email: String): User {
val user = User(name = name, email = email)
return userRepository.save(user)
}
fun createUserWithPosts(
name: String,
email: String,
postTitles: List<String>
): User {
val user = User(name = name, email = email)
postTitles.forEach { title ->
val post = Post(
title = title,
content = "Initial content for $title",
user = user
)
user.posts.add(post)
}
return userRepository.save(user)
}
fun updateUserActivity(userId: Long, isActive: Boolean): User? {
val user = userRepository.findById(userId) ?: return null
val updatedUser = user.copy(isActive = isActive)
return userRepository.save(updatedUser)
}
fun searchUsers(searchTerm: String): List<User> {
return userRepository.findUsersByNamePattern(searchTerm)
}
}
// Transaction management example
class TransactionalUserService(
private val entityManagerFactory: EntityManagerFactory
) {
fun performBulkUserOperation(userUpdates: List<UserUpdate>): Result<Unit> {
val entityManager = entityManagerFactory.createEntityManager()
val transaction = entityManager.transaction
return try {
transaction.begin()
userUpdates.forEach { update ->
val user = entityManager.find(User::class.java, update.userId)
?: throw IllegalArgumentException("User not found: ${update.userId}")
val updatedUser = user.copy(
name = update.newName ?: user.name,
email = update.newEmail ?: user.email,
isActive = update.newActiveStatus ?: user.isActive
)
entityManager.merge(updatedUser)
}
transaction.commit()
Result.success(Unit)
} catch (e: Exception) {
if (transaction.isActive) {
transaction.rollback()
}
Result.failure(e)
} finally {
entityManager.close()
}
}
}
data class UserUpdate(
val userId: Long,
val newName: String? = null,
val newEmail: String? = null,
val newActiveStatus: Boolean? = null
)
Configuration and Customization
// Custom converter implementation
import jakarta.persistence.AttributeConverter
import jakarta.persistence.Converter
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Converter(autoApply = true)
class StringListConverter : AttributeConverter<List<String>, String> {
override fun convertToDatabaseColumn(attribute: List<String>?): String? {
return attribute?.let { Json.encodeToString(it) }
}
override fun convertToEntityAttribute(dbData: String?): List<String>? {
return dbData?.let { Json.decodeFromString<List<String>>(it) }
}
}
// Custom entity example
@Entity
@Table(name = "user_profiles")
data class UserProfile(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@OneToOne
@JoinColumn(name = "user_id", unique = true)
val user: User,
@Column(name = "bio", columnDefinition = "TEXT")
val bio: String? = null,
@Convert(converter = StringListConverter::class)
@Column(name = "interests")
val interests: List<String> = emptyList(),
@Embedded
val address: Address? = null,
@ElementCollection
@CollectionTable(name = "user_social_links", joinColumns = [JoinColumn(name = "profile_id")])
@MapKeyColumn(name = "platform")
@Column(name = "url")
val socialLinks: MutableMap<String, String> = mutableMapOf()
) {
constructor() : this(user = User())
}
@Embeddable
data class Address(
@Column(name = "street")
val street: String = "",
@Column(name = "city")
val city: String = "",
@Column(name = "postal_code")
val postalCode: String = "",
@Column(name = "country")
val country: String = ""
)
// Repository pattern implementation
interface UserProfileRepository {
fun save(profile: UserProfile): UserProfile
fun findByUserId(userId: Long): UserProfile?
fun findProfilesWithInterest(interest: String): List<UserProfile>
fun updateBio(userId: Long, bio: String): Boolean
}
class HibernateUserProfileRepository(
private val entityManager: EntityManager
) : UserProfileRepository {
override fun save(profile: UserProfile): UserProfile {
return entityManager.merge(profile)
}
override fun findByUserId(userId: Long): UserProfile? {
return entityManager.createQuery(
"SELECT p FROM UserProfile p WHERE p.user.id = :userId",
UserProfile::class.java
).setParameter("userId", userId)
.resultList
.firstOrNull()
}
override fun findProfilesWithInterest(interest: String): List<UserProfile> {
// JSON function search (PostgreSQL example)
return entityManager.createQuery(
"SELECT p FROM UserProfile p WHERE JSON_EXTRACT(p.interests, '$') LIKE :interest",
UserProfile::class.java
).setParameter("interest", "%$interest%")
.resultList
}
override fun updateBio(userId: Long, bio: String): Boolean {
val rowsAffected = entityManager.createQuery(
"UPDATE UserProfile p SET p.bio = :bio WHERE p.user.id = :userId"
).setParameter("bio", bio)
.setParameter("userId", userId)
.executeUpdate()
return rowsAffected > 0
}
}
Error Handling
import jakarta.persistence.EntityNotFoundException
import jakarta.persistence.PersistenceException
import jakarta.persistence.RollbackException
import org.hibernate.exception.ConstraintViolationException
sealed class DatabaseError : Exception() {
object UserNotFound : DatabaseError()
data class ConstraintViolation(val constraintName: String) : DatabaseError()
data class PersistenceError(override val message: String) : DatabaseError()
data class UnknownError(val cause: Throwable) : DatabaseError()
}
class SafeUserService(
private val entityManagerFactory: EntityManagerFactory
) {
fun createUserSafely(name: String, email: String): Result<User> {
return executeInTransaction { entityManager ->
val existingUser = entityManager.createQuery(
"SELECT u FROM User u WHERE u.email = :email",
User::class.java
).setParameter("email", email)
.resultList
.firstOrNull()
if (existingUser != null) {
throw IllegalArgumentException("User with email $email already exists")
}
val user = User(name = name, email = email)
entityManager.persist(user)
user
}
}
fun updateUserSafely(userId: Long, updates: UserUpdate): Result<User> {
return executeInTransaction { entityManager ->
val user = entityManager.find(User::class.java, userId)
?: throw EntityNotFoundException("User not found: $userId")
val updatedUser = user.copy(
name = updates.newName ?: user.name,
email = updates.newEmail ?: user.email,
isActive = updates.newActiveStatus ?: user.isActive
)
entityManager.merge(updatedUser)
}
}
private fun <T> executeInTransaction(operation: (EntityManager) -> T): Result<T> {
val entityManager = entityManagerFactory.createEntityManager()
val transaction = entityManager.transaction
return try {
transaction.begin()
val result = operation(entityManager)
transaction.commit()
Result.success(result)
} catch (e: ConstraintViolationException) {
if (transaction.isActive) transaction.rollback()
Result.failure(DatabaseError.ConstraintViolation(e.constraintName ?: "unknown"))
} catch (e: EntityNotFoundException) {
if (transaction.isActive) transaction.rollback()
Result.failure(DatabaseError.UserNotFound)
} catch (e: RollbackException) {
Result.failure(DatabaseError.PersistenceError("Transaction rolled back: ${e.message}"))
} catch (e: PersistenceException) {
if (transaction.isActive) transaction.rollback()
Result.failure(DatabaseError.PersistenceError(e.message ?: "Unknown persistence error"))
} catch (e: Exception) {
if (transaction.isActive) transaction.rollback()
Result.failure(DatabaseError.UnknownError(e))
} finally {
entityManager.close()
}
}
}
// Spring Boot integration example
@Service
@Transactional
class SpringUserService(
@PersistenceContext
private val entityManager: EntityManager
) {
@Transactional(readOnly = true)
fun findAllUsers(): List<User> {
return entityManager.createQuery("SELECT u FROM User u", User::class.java)
.resultList
}
@Transactional
fun createUser(name: String, email: String): User {
val user = User(name = name, email = email)
entityManager.persist(user)
return user
}
@Transactional
fun updateUser(id: Long, updates: UserUpdate): User {
val user = entityManager.find(User::class.java, id)
?: throw EntityNotFoundException("User not found: $id")
val updatedUser = user.copy(
name = updates.newName ?: user.name,
email = updates.newEmail ?: user.email,
isActive = updates.newActiveStatus ?: user.isActive
)
return entityManager.merge(updatedUser)
}
}