Jak vytvořit like dotazy ve Spring Data

V tomto příspěvku ukáži, jak lze vytvořit sql dotazy s LIKE ve Spring Data bez pomoci @Query anotace. Budeme mít entitu kniha

@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 = "",

        ...
)

a vytvoříme si jpa repository

Číst dálJak vytvořit like dotazy ve Spring Data

Spring CommandLineRunner

CommandLineRunner je funkční rozhraní s metodou void run(String... args) throws Exception. V této metodě můžeme uvést vše, co chceme aby se stalo a startu aplikace. Jednou z možností jak vytvořit CommandLineRunner je vlastní třída s anotací @Configuration a následně metoda s anotací @Bean vracející CommandLineRunner

@Configuration
class AppConfig {

    @Bean
    fun init(bookRepository: BookRepository): CommandLineRunner {
        return CommandLineRunner {
            bookRepository.save(BookEntity(title = "Cyber Leviathan"))
            bookRepository.save(BookEntity(title = "Last Hunt"))
        }
    }
}

Jiný způsob je extendovat CommandLineRunner a implementovat metodu run()

@SpringBootApplication
class SpringData2Application : CommandLineRunner {
	override fun run(vararg args: String?) {
		println("Running ...")
	}
}

fun main(args: Array<String>) {
	runApplication<SpringData2Application>(*args)
}

 

Vytvoření tabulek pro spring batch

Spring Batch obsahuje inicializační SQL skripty pro většinu populárních databází (více v tomto příspěvku). Spring Boot dokáže detekovat databázi a spustit správný skript při startu aplikace. Pokud chcete, aby Spring Batch vytvořil potřebné tabulky, přidejte do konfiguračního souboru

spring.batch.initialize-schema=always

Vypnutí této funkcionality

spring.batch.initialize-schema=never

Zdroj: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-initialize-a-spring-batch-database

Micrometer a DistributionSummary

Pokud použijeme micrometer a objekt DistributionSummary, zde jsou ukázky výstupu na actuatoru:

Pokud máme hodnoty

500, 1500, 2500, ..., 9500

a

val summary = DistributionSummary.builder("my.summary").baseUnit("miliseconds").register(meterRegistry)

a hodnoty uložíme/nahrajeme

summary.record(hodnota)

výsledek (na endpointu /actuator/prometheus)

# HELP my_summary_miliseconds
# TYPE my_summary_miliseconds summary
my_summary_miliseconds_count 10.0
my_summary_miliseconds_sum 50000.0
# HELP my_summary_miliseconds_max
# TYPE my_summary_miliseconds_max gauge
my_summary_miliseconds_max 9500.0

Další možnosti:

Číst dálMicrometer a DistributionSummary

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

Plánování pomocí kronu

Pokaždé, když bude 9 vteřina
9 * * * * ?
2020-07-04 13:16:09.057000
2020-07-04 13:14:09.011000
2020-07-04 13:13:09.004000

Pokaždé, když bude 20 mituta a 5 vteřina
5 20 * * * ?
2020-07-04 13:20:05.109000

Pokaždé, když bude 18 vteřina, 34 minuta a 13 hodina
18 34 13 * * ?

Pokaždé, když bude 0 vteřina, 50 minuta, 13 hodina a 24 den v měsíci
0 50 13 24 * ?

 

Takový to kron string se dá použít například při plánování jobů ve spring batch:

application.properties -> app.jobs.cron: 0 35 13 24 *
konfigurace jobu -> @Scheduled(cron = "\${app.jobs.cron}")

Weblux swagger pomocí springfox

Swagger je nástroj (knihovna), který umožňuje dokumentovat REST endpointy vaší aplikace. Dobré je to například pro vývoj, kdy konzument (např. vývojář frontendu) ví, jaké jsou aktuálně k dispozici endpointy, jaké mají vstupy a jaké objekty vrací. Jak zprovoznit swagger v projektu, který používá WebFlux a Spring Boot ukáži v tomto příspěvku.

Pokud máte projekt a používáte pro restové služby framework WebFlux a Spring Boot, je zprovoznění Swaggeru jednoduché. Stačí přidat následující závislosti (používám Gradle a Kotlin):

implementation("io.springfox:springfox-swagger2:3.0.0-SNAPSHOT")
implementation("io.springfox:springfox-swagger-ui:3.0.0-SNAPSHOT")
implementation("io.springfox:springfox-spring-webflux:3.0.0-SNAPSHOT")

a vytvořit konfigurační třídu pro Swagger:

Číst dálWeblux swagger pomocí springfox

Zakázání basic auth pro některé endpointy ve Spring OAuth2

To, že Spring OAuth2 řadu endpointů generuje, jsem se zmínil v jednom z předchozích příspěvků. Na jednu stranu je to výhoda, protože většinu věcí máte hotovou, na stranu druhou to může v některých případech být problém. Například můžete chtít, aby endpoint PUT /oauth/token neměl authentikaci. Normálně byste napsali něco následujícího:

override fun configure(http: HttpSecurity) {
    http
            .authorizeRequests()
            .antMatchers(HttpMethod.PUT, "/oauth/token").permitAll()
            .anyRequest().denyAll()
            .and().csrf().disable()
            .formLogin().disable()
}

To ale v tomto případě nestačí, protože request zachytí AuthorizationServerSecurityConfiguration. Ta má nastavený @Order na 0. Řešením je, ve třídě která dědí od WebSecurityConfigurerAdapter nastavit @Order na -1 (@Order(-1)) a přetížit metodu configure následujícím způsobem.

override fun configure(http: HttpSecurity) {
    http
            .requestMatchers()
            .antMatchers(HttpMethod.PUT, "/oauth/token")
            .and().authorizeRequests().anyRequest().permitAll()
            .and().csrf().disable()
            .formLogin().disable()
}

Nezapomeňte na csfr().disable(), jinak budete dostávat 403.

Spring OAuth2

Pro vytvoření oauth autorizace nabízí spring knihovnu

org.springframework.security.oauth:spring-security-oauth

Zde najdete celkem 8 endpointů označených anotací @FrameworkEndpoint:

  • /oauth/authorize (class AuthorizationEndpoint)
  • /oauth/authorize, method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL (class AuthorizationEndpoint)
  • /oauth/check_token (class CheckTokenEndpoint)
  • /oauth/token, method=RequestMethod.GET (class TokenEndpoint)
  • /oauth/token, method=RequestMethod.POST (class TokenEndpoint)
  • /oauth/token_key, method = RequestMethod.GET (class TokenKeyEndpoint)
  • /oauth/confirm_access (class WhitelabelApprovalEndpoint)
  • /oauth/error (class WhitelabelErrorEndpoint)

Anotace @FrameworkEndpoint znamená, že tyto endpointy jsou vytvořeny frameworkem. Nemusíte je tedy vytvářet sami.