Spring JPA

参考: https://terasolunaorg.github.io/guideline/public_review/ArchitectureInDetail/DataAccessJpa.html

設定

build.gradle

mysql-connectorruntimeOnlyにする。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly  'mysql:mysql-connector-java'
}

application.yml

MySQLを使う場合の例:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test_db?useUnicode=yes&characterEncoding=UTF-8
    username: user
    password: secret
    hikari:
      connection-test-query: "SELECT 1"
      maximum-pool-size: 3
      minimum-idle: 1

  jpa:
    database: MYSQL
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    show-sql: false
    hibernate:
      # 勝手にテーブルを作ってくれる設定。
      ddl-auto: update

エンティティ

基本

以下のアノテーションは省略できる。

  • @Table
  • @Column
@Entity
class Hoge(
    // 主キー
    @Id
    var id: Long = 0,

    // オートインクリメントな主キー
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0,

    // UUID
    @GenericGenerator(name = "guid", strategy = "uuid2")
    @Column(columnDefinition = "BINARY(16)")
    var uuid: UUID = UUID.randomUUID(),

    // Enum
    @Convert(converter = MyEnumConverter::class)
    var myEnum: MyEnum = MyEnum.A,
)


enum class MyEnum(val value: Int) {
    A(0), B(1), C(2);
    companion object {
        fun of(value: Int) = values().find { it.value == value } ?: throw IllegalArgumentException()
    }
}
class MyEnumConverter : AttributeConverter<MyEnum, Int> {
    override fun convertToDatabaseColumn(attribute: MyEnum?): Int? = attribute?.value
    override fun convertToEntityAttribute(dbData: Int?): MyEnum? = dbData?.let { MyEnum.of(dbData) }
}

複合キー

@Embeddableを利用する。

@Entity
@Table
class User(
    @EmbeddedId
    var id: UserId = UserId(),
    @Column
    var age: Int = 0,
)

// Serializableは`java.io.Serializable`
@Embeddable
class PermissionId(val firstName: String = "", val lastName: String = "") : Serializable

インデックス

@Tableindexesを設定する。

@Table(indexes = [Index(columnList = "id")])

リポジトリ

基本

@Transactional
@Repository
interface UserRepository : JpaRepository<User, UUID> {

    // JPQL: update
    @Modifying
    @Query("update User set aaa = ?2, bbb = ?3 where guid = ?1")
    fun updateHoge(guid: UUID, aaa: Int?, bbb: String): Int

    // findBy[フィールド名]で、そのフィールドを条件にエンティティを取得するメソッドになる
    fun findByAge(age: Int): List<User>
}

findByとかのメソッド命名ルール

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

単体テスト

H2を使う。

testRuntimeOnly 'com.h2database:h2'

application-unit.yaml

spring:
  datasource:
    driverClassName: org.h2.Driver
    # DATABASE_TO_UPPER=false: デフォルトでH2はデータベース名を大文字にするのでそれを防ぐ
    url: jdbc:h2:mem:;DB_CLOSE_ON_EXIT=TRUE;DATABASE_TO_UPPER=false;MODE=MySQL
    username: sa
    password: sa

  jpa:
    hibernate:
      ddl-auto: none

テストクラスに@ActiveProfilesを付ける。

@ActiveProfiles("unit")
class MyTest {
    // ...
}

src/test/resources/schema.sqlを置くと、起動時に実行してくれる。

create table user(
  -- ...
) charset=utf8;

src/test/resources/data.sqlを置くと、起動時に実行してくれる。

insert into user values(...)

tips

@Transactionalが効かない

@Serviceをつけたクラスのメソッドに@Transactionalをつけて、使う側はそのクラスをDIしてメソッドを呼ぶようにする。

Ref:
https://ameblo.jp/kochablo/entry-11578714824.html