Projekce a Query

V předchozím příspěvku jsem ukázal základní možnost projekce ve Spring Data. Na pár věcí je ale třeba si dávat pozor. Je možné používat @Query a také nativní @Query(nativeQuery = true), ale je třeba specifikovat název výsledného sloupce pomocí klíčového slova AS.

@Query("SELECT b.title AS title, b.numberOfPages AS numberOfPages FROM BookEntity b")
fun findAllInfo(): List<BaseBookInfo>

@Query("SELECT b.title AS title, b.numberOfPages AS numberOfPages FROM t_book b", nativeQuery = true)
fun findAllInfoNative(): List<BaseBookInfo>

Pokud byste to neudělali, našel by se sice správný počet záznamů, ale všechny záznamy by měly všechny hodnoty null (nebo by došlo k vyhození výjimky v případě přístupu k primitivním datovým typům).

Spring Data a projekce

Pokud pro dotazování používáte Spring Data, v rámci výsledku dostanete root entitu (všechny její hodnoty). Pokud tedy budete mít následující entitu:

@Entity
@Table(name = "t_book")
@SequenceGenerator(initialValue = 100, name = "t_book_seq_gen", sequenceName = "t_book_seq")
class BookEntity (
        @Id
        @GeneratedValue(generator = "t_book_seq_gen", strategy = GenerationType.SEQUENCE)
        var id: Long = 0L,

        var author: String = "",

        var publisher: String = "",

        var title: String = "",

        var foreword: String = "",

        var numberOfPages: Int = 0,

        var publishingYear: Int = 0
)

vždy získáte všechny hodnoty (id, author, publisher, title, foreword, numberOfPages, …), i když je nebudete vždy potřebovat. Pokud chcete použe některé hodnoty, je zde možnost tzv. projekce. Vytvoříte si rozhraní, které bude obsahovat jen to, co potřebujete. Např. budete chtít pouze titul knihy a počet stran.

Číst dálSpring Data a projekce

Vrácení pouze některých sloupců při vytvoření dotazu pomocí entity manažera

Pokud vytváříte dotaz pomocí entity manažera (viz příspěvek Spring Data a vytvárení dotazů pomocí entiy managera) můžete omezit sloupce, které daným dotazem budete vracet. V tomto případě používám tabulku t_order se sloupci id, identifier a sum, která je v představována entitou Order.

Číst dálVrácení pouze některých sloupců při vytvoření dotazu pomocí entity manažera

Spring Data a získání metod pro základní operace nad tabulkou

Spring Data poskytuje rozhraní CrudRepository<T, ID>, takže pokud chceme získávat data z databázové tabulky, stačí nám vytvořit vlastní rozhraní, který bude rozšiřovat CrudRepository a určit typ entity, kterou tabulka obsahuje. Jednoduchý příklad to objasní. Pokud budeme mít entitu Order

import java.math.BigDecimal
import java.util.*
import javax.persistence.*

@Entity
@Table(name = "t_order")
@SequenceGenerator(name = "t_order_seq_gen", sequenceName = "t_order_seq", allocationSize = 1)
class Order(

        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "t_order_seq_gen")
        var id: Long = 0L,

        var identifier: UUID = UUID.randomUUID(),

        var sum: BigDecimal = BigDecimal.ZERO
)

vytvoří se nám (volba spring.jpa.generate-ddl: true v application.properties) tabulka t_order. Pro operace s daty této tabulky vytvoříme rozhraní OrderRepository, které bude rozšiřovat CrudRepository.

import cz.vitfo.springdata.entity.Order
import org.springframework.data.repository.CrudRepository

interface OrderRepository : CrudRepository<Order, Long>

Toto stačí, abychom měli k dispozici sadu základních metod (count, delete, deleteAll, deleteById, existsById, findAll, findAllById, findById, save, saveAll) a další metody mohli jednoduše vytvářet použitím jmenné konvence – findByUuid, findByUuidAndSum, …

Spring Data a vytváření dotazů pomocí entity manažera

To jak Spring Data zjednodušuje práci s databází jsem ukázal v příspěvku Spring Data a získání metod pro základní operace nad tabulkou. V tomto příspěvku si ale ukážeme jak vytvářet sql příkazy pomocí entity manažera (EntityManager).

Vytvoříme si třídu s názvem OrderRepository a označíme ji jako repository anotací @Repository (org.springframework.stereotype.Repository).

Číst dálSpring Data a vytváření dotazů pomocí entity manažera

Spring Data a Liquibase

Liquibase umožňuje hlídat změny v databázi. Každá změna (blok změn) má svůj vlastní changeset. Liquibase si vytváří dvě vlastní tabulky: databasechnagelog, kde jsou uloženy provedené changesety a databasechangeloglock, která slouží pro zamykání (řeší případ přístupu, kdy více vývojářů dělá změny v databázi současně). Může se stát, že pokud Liquibase neskončí korektně a zůstane zámek. Pak je potřeba locked nastavit na false manuálně.

Číst dálSpring Data a Liquibase

Chyba relation „hibernate_sequence“ does not exist

Tuto chybu jsem dostal při použití Spring Data se Spring Boot a databází PostgreSQL. Takto vypadala třída definující entity.

@Entity
@Table(name = "t_login")
@SequenceGenerator(name = "t_login_id_seq_gen", sequenceName = "t_login_id_seq", allocationSize = 1)
data class Login(
        @Id
        @GeneratedValue
        var id: Long = 0,

        @Column
        var token: UUID = UUID.randomUUID()
)

Pro id bylo třeba více specifikovat @GeneratedValue:

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "t_login_id_seq_gen")

Tím se problém vyřešil.

Změna verze Javy z 8 na 11

Nedávno jsem prováděl změny verze Javy na projektu, který používá Spring Boot. Měnil jsem verzi Javy z verze 8 na verzi 11. Nebylo to až tak strašné, jak jsem čekal (spíše naopak), přesto se ale některé komplikace objevily. V tomto příspěvku budu postupovat od chyby k chybě, tak jak se u mě objevovaly.

Číst dálZměna verze Javy z 8 na 11

Spring JPA neukládá vazbu na rodiče

Pokud používáte Spring JPA a nedochází k ukládání vazeb na rodiče (potomek je uložen, ale foreign key na rodiče je null), chyba může být v tom, jak máte vytvořeny vazby. Pokud totiž používáte bidirectional (oboustrannou) vazbu (rodič ví o potomkovi a potomek ví o rodiči), může vám chybě právě jedna strana vazby.

Ukážu to na příkladu rodičovské entity Quiz a potomků QuizQuestion.

Číst dálSpring JPA neukládá vazbu na rodiče

Spring Boot JPA: vytváření dotazů

Vytváření dotazů ve Spring Boot JPA je jednoduché. Nejdříve je třeba si vytvořit objekty, které představují tabulky a jejich sloupce. V tomto příkladu si vytvoříme objekt User.

@Entity(name="t_user")
public class User implements Serializable {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;
	private String email;
	private String password;

        // getry a setry
}

Pomocí anotace @Entity říkám, že tato třída má představovat tabulku v databázi, name="t_user" definuje, že tabulka v databázi se bude jmenovat t_user. Anotací @Id označuji private long id jako primární klíč tabulky a @GeneratedValue určuje jak se má id generovat.

Číst dálSpring Boot JPA: vytváření dotazů