Spring - Bludwarf/CodeBank GitHub Wiki

Bonnes pratiques

Field VS Getter

Ne pas rendre visibles les champs d'un @Component visibles (> private) car cela peut perturber l'injection. Observé par un TU qui passe dans l'IDE mais plus dans maven. Utiliser à la place des getters.

Logs

Logs debug

Pour debug les échanges HTTP :

logging.level.reactor.netty.http.client=debug
logging.level.org.apache.http=DEBUG

https://gist.github.com/Bludwarf/5bd0acd470f426363d4006e62adc72c8

RAF :

Logs ExceptionHandlerExceptionResolver

Certains logs sont activés par la propriété spring.mvc.log-resolved-exception=true. Cette propriété est positionnée par défaut lorsqu'on utilise DevTools. Exemple de logs concernés :

2024-08-23_16:02:42.957 WARN  o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolved [org.modelmapper.MappingException: ModelMapper mapping errors:

1) Converter [org.modelmapper.internal.converter.BooleanConverter@6b0f62e3](mailto:org.modelmapper.internal.converter.BooleanConverter@6b0f62e3) failed to convert int to boolean.

1 error]

Authent

Méthode qui ajoute l'authent quand on utilise un WebClient : org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager#createAuthorizationContext

Transactions

tx

Source

Pour afficher le statut de la transaction courante liée au @Transactional :

TransactionAspectSupport.currentTransactionStatus()

Jackson

Dates

Spring définit un ObjectMapper par défaut qui se configure avec le fichier de propriétés mais aussi avec du code.

Exemples de propriétés :

spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.SSSXXX
# Déjà à false par défaut dans Spring
spring.jackson.serialization.write-dates-as-timestamps=false

Exemple de code :

@Configuration
@RequiredArgsConstructor
public class JacksonConfig {

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
        return builder -> builder
                .timeZone(TimeZone.getTimeZone("UTC"))
                ;
    }
}

Spécifications des properties

Si aucune source pour décrire les properties n'est disponible, on peut en spécifier une dans le fichier META-INF/additional-spring-configuration-metadata.json. Exemple :

{
	"properties": [
		{
			"name": "module.oauth2.check-token-uri",
			"type": "java.net.URI",
			"description": "ACL check_token URI."
		}
	],
	"hints": [
		{
			"name": "module.oauth2.check-token-uri",
			"values": [
				{
					"value": "http://acl:8085/oauth/check_token",
					"description": "ACL through gateway/registry."
				},
				{
					"value": "http://localhost:8085/oauth/check_token",
					"description": "Local ACL."
				}
			]
		}
	]
}

Custom SpEL expression @PreAuthorize

Gist.

Spring Data JPA

Derived Query Methods

Principe de nommer une méthode de Repository en suivant une convention pour en déduire la requête SQL. Exemples :

  • getById
  • findById

Source

Ce post explique comment Spring implémente ce mécanisme. L'implémentation par défaut se trouve dans la classe SimpleJpaRepository.

getById sur une entité qui n'existe pas

EntityNotFoundException (Spring Boot 3)

Pile d'appel :

  • repo.getById(id) : Interface perso qui renvoie T ou Optional
  • simpleJpaRepository.getById(id) : Implémentation par défaut de Spring Data JPA
  • entityManager.getReference(id) : Appel à Hibernate

Quand on appelle getById c'est bien Hibernate qui lance l'exception via la méthode org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.JpaEntityNotFoundDelegate#handleEntityNotFound (testé avec org.hibernate.orm:hibernate-core:6.4.10.Final dans Spring Boot 3.2.9). Cette exception n'est lancée que si on utilise réellement l'entité (cf. propriété Hibernate hibernate.jpa.compliance.proxy).

Voir cet article, en se focalisant sur le 1er cas EntityManager.getReference.

Ce qui n'est pas cohérent, c'est qu'une méthode getByCode, par exemple, renvoie null au lieu de lancer une exception.

Solution possible : définir une méthode findById dans le repo qui renvoie un objet plutôt qu'un Optional. On reste cohérent avec le principe get => exception / find => null.

null (Spring Boot 2)

Pile d'appel :

  • repo.getById(id)
  • org.springframework.data.jpa.repository.query.JpaQueryExecution#execute
  • org.hibernate.query.internal.AbstractProducedQuery#getSingleResult : lance une exception NoResultException catchée par JpaQueryExecution

Avec Spring Boot 2 on ne renvoie pas d'exception mais simplement null.

Comparatif

Version de Spring Boot 2 3
Implémentation Spring Data JPA org.springframework.data.jpa.repository.query.JpaQueryExecution#execute org.springframework.data.jpa.repository.support.SimpleJpaRepository#getById
Implémentation Hibernate org.hibernate.query.internal.AbstractProducedQuery#getSingleResult org.hibernate.proxy.AbstractLazyInitializer#initialize
Exception lancée par Hibernate javax.persistence.NoResultException jakarta.persistence.EntityNotFoundException
Résultat de Spring Data JPA Renvoie null Relaie l'exception lancée par Hibernate

Résolution de propriétés

Voir aussi