Spring JPA
参考: https://terasolunaorg.github.io/guideline/public_review/ArchitectureInDetail/DataAccessJpa.html
設定
build.gradle
mysql-connector
はruntimeOnly
にする。
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
インデックス
@Table
にindexes
を設定する。
@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してメソッドを呼ぶようにする。