# Zusammenfassung
# Dependencies
In Spring Initializr Spring Data JPA auswählen bzw. in build.gradle.kts:
plugins {
// ...
kotlin("plugin.jpa") version "1.4.30"
}
dependencies {
// ...
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
# Konfiguration
# immer
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.globally_quoted_identifiers=true
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
# 9 minutes
spring.datasource.hikari.max-lifetime=540000
# optional
#spring.datasource.username=
#spring.datasource.password=
#spring.datasource.hikari.maximum-pool-size=10
# nur für Debugging
#logging.level.com.zaxxer.hikari=DEBUG
#spring.jpa.show-sql=true
#spring.jpa.properties.hibernate.format_sql=true
#logging.level.org.hibernate.type.descriptor.sql=TRACE
# H2
runtimeOnly("com.h2database:h2")
Standardmäßig In-Memory, ansonsten:
spring.datasource.url=jdbc:h2:./test-db
# MariaDB / MySQL
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
// oder
runtimeOnly("mysql:mysql-connector-java")
spring.datasource.url=jdbc:mariadb://some.host:3306/db-name
# oder
spring.datasource.url=jdbc:mysql://some.host:3306/db-name
# MSSQL
runtimeOnly("com.microsoft.sqlserver:mssql-jdbc")
spring.datasource.url=jdbc:sqlserver://some.host:1433;database=db-name
# PostgreSQL
runtimeOnly("org.postgresql:postgresql")
spring.datasource.url=jdbc:postgresql://some.host:5432/db-name
# Entities
@Entity
@Table(indexes = [
Index(columnList = "date-of-birth")
])
class Person(
var name: String,
@Column(name = "date-of-birth")
var dateOfBirth: LocalDate,
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
var favoriteColor: Color? = null
@Column(nullable = false)
var created: OffsetDateTime = OffsetDateTime.now()
}
# Enums
enum class Color(
val value: Int
) {
Red(10),
Green(20),
Blue(30);
@Converter(autoApply = true)
object ColorConverter : AttributeConverter<Color, Int> {
override fun convertToDatabaseColumn(attribute: Color?): Int? {
return attribute?.value
}
override fun convertToEntityAttribute(dbData: Int?): Color? {
return Color.values().find { it.value == dbData }
}
}
}
# Relationen
@OneToOne(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, optional = false)
var settings: UserSettings
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "users_projects",
joinColumns = [JoinColumn(name = "user_id")],
inverseJoinColumns = [JoinColumn(name = "project_id")])
var projects: MutableList<Project> = mutableListOf()
// Gegenpart
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "users_projects",
joinColumns = [JoinColumn(name = "project_id")],
inverseJoinColumns = [JoinColumn(name = "user_id")])
var users: MutableList<User> = mutableListOf()
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
@JoinColumn(name = "invoice_id")
var items: MutableList<InvoiceItem> = mutableListOf()
@ManyToOne(fetch = FetchType.LAZY, optional = true)
@JoinColumn(name = "invoice_id")
lateinit var invoice: Invoice
# Embedded
spärlich verwenden, wenn überhaupt
// Property
@Embeddable
data class Period(
var start: OffsetDateTime? = null,
var end: OffsetDateTime? = null
)
// Property
@Embedded
val planningTime: Period
# ElementCollection
für simple Datentypen und Embeddables, funktionieren wie @OneToMany mit cascade = [CascadeType.ALL] und orphanRemoval = true
@ElementCollection
var luckyNumbers: MutableList<Int> = mutableListOf()
@ElementCollection
var periods: MutableList<Period> = mutableListOf()
# Sortierung
via vorhandener Spalte:
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
@OrderBy("amount DESC")
var items: List<InvoiceItem>
via automatisch angelegter Spalte, nimm Reihenfolge der List
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.LAZY, orphanRemoval = true)
@OrderColumn
var items: List<InvoiceItem>
# Maps
// Simple Values
@ElementCollection
var map1: MutableMap<String, String> = mutableMapOf()
// Entity Values
@ManyToMany // oder @OneToMany, dann unique Values
var map2: MutableMap<Long, User> = mutableMapOf()
# Repositories
Eigene Methoden:
interface UserRepo : JpaRepository<User, Long> {
fun findAllByFirstNameContains(firstName: String): Collection<User>
fun findFirst2ByFirstNameOrderByIdDesc(firstName: String): Collection<User>
fun countByLastNameEndingWith(lastName: String): Long
fun deleteAllByIdBetween(from: Long, to: Long)
fun existsByLastNameAndFirstNameIsNotLike(last: String, first: String): Boolean
}
Sortieren:
val users = userRepo.findAll(Sort.by(
Sort.Order.asc("lastName"),
Sort.Order(Sort.Direction.DESC, "firstName"))
)
Paginierung:
val page = userRepo.findAll(PageRequest.of(5/*page*/, 3/*size*/))
fun findAllByLastName(lastName: String, pageable: Pageable): Page<User>
// oder List<User>
Eigenes Query:
@Query("SELECT u FROM User u WHERE u.lastName = :myFilter")
fun findRelevantUsers(myFilter: String): Collection<User>
Projections:
interface FirstNameOnly {
val firstName: String?
}
fun findByLastName(lastName: String): Collection<FirstNameOnly>
@Query("SELECT u.firstName AS firstName " +
"FROM User u WHERE u.lastName = :lastName")
fun findByLastName(lastName: String): Collection<FirstNameOnly>
TIP
Best Practice: Nur mit einem @Service auf das Repository zugreifen.
← Migrationen Intro →