Hibernate ORM (Kotlin)

Hibernate ORMは「JavaとKotlin向けの包括的なオブジェクト関係マッピングソリューション」として開発された、JVMエコシステムで最も成熟したORMフレームワークです。20年以上の開発実績を誇り、複雑なエンタープライズアプリケーションでの大規模データ処理を可能にします。JPA (Jakarta Persistence API) の参照実装として、標準的なJPA APIと独自の拡張機能を組み合わせ、Kotlinの言語機能と完全統合したタイプセーフなデータベース操作を提供します。Spring BootやQuarkusなどのモダンフレームワークとの深い連携により、現代的なKotlinアプリケーション開発の基盤として確固たる地位を築いています。

ORMKotlinJavaJPAデータベース企業向け

GitHub概要

hibernate/hibernate-orm

Hibernate's core Object/Relational Mapping functionality

スター6,188
ウォッチ303
フォーク3,649
作成日:2010年10月4日
言語:Java
ライセンス:Apache License 2.0

トピックス

databaseenvershibernatehibernate-ormjakarta-persistencejakartaeejavajdbcjpaobject-relational-mapperobject-relational-mappingormpersistencepersistence-frameworksql

スター履歴

hibernate/hibernate-orm Star History
データ取得日時: 2025/7/17 02:30

ライブラリ

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)
    }
}