Hibernate ORM (Kotlin)
Hibernate ORMは「JavaとKotlin向けの包括的なオブジェクト関係マッピングソリューション」として開発された、JVMエコシステムで最も成熟したORMフレームワークです。20年以上の開発実績を誇り、複雑なエンタープライズアプリケーションでの大規模データ処理を可能にします。JPA (Jakarta Persistence API) の参照実装として、標準的なJPA APIと独自の拡張機能を組み合わせ、Kotlinの言語機能と完全統合したタイプセーフなデータベース操作を提供します。Spring BootやQuarkusなどのモダンフレームワークとの深い連携により、現代的なKotlinアプリケーション開発の基盤として確固たる地位を築いています。
GitHub概要
hibernate/hibernate-orm
Hibernate's core Object/Relational Mapping functionality
トピックス
スター履歴
ライブラリ
Hibernate ORM (Kotlin)
概要
Hibernate ORMは「JavaとKotlin向けの包括的なオブジェクト関係マッピングソリューション」として開発された、JVMエコシステムで最も成熟したORMフレームワークです。20年以上の開発実績を誇り、複雑なエンタープライズアプリケーションでの大規模データ処理を可能にします。JPA (Jakarta Persistence API) の参照実装として、標準的なJPA APIと独自の拡張機能を組み合わせ、Kotlinの言語機能と完全統合したタイプセーフなデータベース操作を提供します。Spring BootやQuarkusなどのモダンフレームワークとの深い連携により、現代的なKotlinアプリケーション開発の基盤として確固たる地位を築いています。
詳細
Hibernate ORM 2025年版はKotlin開発に最適化された次世代のORM体験を提供します。Jakarta Persistence 3.0完全対応により最新のJava EE標準をサポートし、Kotlin Coroutinesとの統合により非同期処理を自然に扱えます。HibernateのCriteriaAPI、HQL、ネイティブSQLクエリに加え、Kotlin拡張関数による型安全なクエリ構築が可能。二次キャッシュ、遅延ローディング、バッチ処理最適化など企業レベルのパフォーマンス機能を標準装備し、PostgreSQL、MySQL、Oracle、SQL Serverなど主要データベースへの完全対応を実現しています。
主な特徴
- Jakarta Persistence標準: JPA 3.0完全準拠の標準化されたORM操作
- Kotlinネイティブサポート: Kotlin言語機能との深い統合と型安全性
- 企業レベル機能: 二次キャッシュ、コネクションプール、トランザクション管理
- 柔軟なクエリ: HQL、Criteria API、ネイティブSQLの包括的サポート
- パフォーマンス最適化: 遅延ローディング、バッチ処理、N+1問題対策
- 広範なデータベース対応: 主要なRDBMSへの完全対応
メリット・デメリット
メリット
- 20年以上の実績による高い安定性と豊富な機能セット
- JPA標準準拠によりポータビリティと学習リソースが豊富
- Spring Boot、Quarkus等のエコシステムとの優れた統合
- エンタープライズレベルのパフォーマンス最適化機能
- 包括的なドキュメントと活発なコミュニティサポート
- Kotlinとの親和性による開発効率向上
デメリット
- 学習コストが高く、初心者には複雑な設定が必要
- 軽量アプリケーションには過剰な機能とオーバーヘッド
- 設定やアノテーションが複雑になりがちでボイラープレートが多い
- マジック的な動作によりデバッグが困難な場合がある
- メモリ使用量が大きく、小規模プロジェクトには不向き
- ネイティブクエリに依存すると移植性が損なわれる
参考ページ
書き方の例
セットアップ
// 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")
// データベースドライバー
implementation("org.postgresql:postgresql:42.7.1")
// または implementation("mysql:mysql-connector-java:8.0.33")
// Kotlin サポート
implementation("org.jetbrains.kotlin:kotlin-reflect")
// Spring Boot との統合
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
// hibernate.properties または 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
基本的な使い方
import jakarta.persistence.*
import org.hibernate.annotations.GenericGenerator
import java.time.LocalDateTime
// エンティティクラスの定義
@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,
// リレーションシップ
@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())
}
クエリ実行
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) {
// 基本的なCRUD操作
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 クエリ
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(型安全)
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
}
// ネイティブSQLクエリ
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]
)
}
}
}
データ操作
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)
}
}
// トランザクション管理の例
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
)
設定とカスタマイズ
// カスタムコンバーターの実装
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) }
}
}
// カスタムエンティティの例
@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 = ""
)
// リポジトリパターンの実装
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関数を使った検索(PostgreSQL例)
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
}
}
エラーハンドリング
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統合の例
@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)
}
}