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 :
- on a que l'URL de mémoire, ajouter un exemple
- l'une des deux propriétés n'a aucun effet, la supprimer dans ce cas
- sensible à la casse ?
- quelle différence avec https://www.baeldung.com/spring-resttemplate-logging#logging-headersbody-with-apache-httpclient ?
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
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
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
- Solution la plus simple : On peut même plus simplement injecter un
Environment
- Solution intéressante