# 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.