Découvrez : « L'enfer du constructeur vide » dans Kotlin + JPA
\n\nSi vous avez déjà essayé de faire fonctionner ensemble Kotlin et JPA (Java Persistence API), vous avez probablement rencontré une situation où, au lancement de l'application, Hibernate lève une exception du genre :
\n\norg.hibernate.InstantiationException: No default constructor for entity: com.example.User\n\n\nCela vous dit quelque chose ? Bien sûr. C'est ce problème qui a rassemblé des milliers de vues sur Stack Overflow et a reçu le nom sinistre de « Kotlin with JPA: default constructor hell ».
\n\nDans cet article, nous allons calmement, étape par étape, analyser :
\n- \n
- Pourquoi cette erreur se produit-elle ? \n
- Pourquoi Kotlin « ne veut-il pas » générer un constructeur vide ? \n
- Trois méthodes éprouvées pour résoudre le problème (de la plus simple à la plus professionnelle) \n
- Quelle méthode choisir pour votre projet \n
C'est parti !
\n\nPourquoi Hibernate exige-t-il un constructeur sans paramètres ?
\n\nCommençons par Java. En Java classique, nous avons l'habitude d'écrire :
\n\npublic class User {\n private Long id;\n private String name;\n\n // Constructeur vide obligatoire\n public User() {}\n\n // Getters, setters...\n}\n\n\nHibernate (et toute implémentation JPA) utilise la réflexion pour créer des objets. Lorsqu'il lit une ligne de la base de données, il doit créer une instance de la classe User, puis remplir les champs par réflexion. Pour cela, il a besoin d'un constructeur sans paramètres (default constructor).
En Java, le compilateur crée automatiquement un constructeur vide si vous n'en avez défini aucun autre. Par conséquent, le problème ne se pose généralement pas.
\n\nEt quelle est la particularité de Kotlin ?
\n\nKotlin est un langage qui met fortement l'accent sur l'immuabilité et la sécurité. Regardez une classe Kotlin typique :
\n\ndata class User(\n val id: Long = 0,\n val name: String = ""\n)\n\n\nDeux choses clés se produisent ici :
\n- \n
- Toutes les propriétés sont déclarées dans le constructeur primaire (primary constructor) \n
- Les propriétés sont déclarées comme
val(immuables) et ont des valeurs par défaut \n
Kotlin ne génère pas de constructeur vide séparé si vous avez un constructeur primaire avec des paramètres. Même si tous les paramètres ont des valeurs par défaut, cela n'est pas considéré comme un « vrai » constructeur sans paramètres au niveau du bytecode JVM.
\n\nLorsque Hibernate tente d'appeler User.class.newInstance() par réflexion, il ne trouve pas de constructeur vide et échoue avec une erreur.
Méthode 1 : La plus simple — ajouter explicitement un constructeur secondaire
\n\nLa voie la plus directe est d'ajouter un deuxième constructeur, vide, manuellement :
\n\ndata class User(\n val id: Long = 0,\n val name: String = ""\n) {\n // Constructeur secondaire sans paramètres\n constructor() : this()\n}\n\n\nIci, constructor() : this() signifie : « appelle le constructeur primaire avec les valeurs par défaut ».
Avantages :
\n- \n
- Très simple et clair \n
- Ne nécessite aucun plugin ni configuration supplémentaire \n
Inconvénients :
\n- \n
- Il faut écrire ce constructeur pour chaque entité \n
- Si vous avez 10 à 15 entités, cela devient fastidieux \n
- Facile à oublier d'ajouter \n
Méthode 2 : Utiliser une classe ouverte et rendre toutes les propriétés var
\n\nDe nombreux exemples sur Internet proposent cette solution :
\n\nopen class User {\n var id: Long = 0\n var name: String = ""\n}\n\n\nIci, nous rendons la classe open (pour que Hibernate puisse créer un proxy), et les propriétés — var (mutables). Et pas de constructeur primaire — donc Kotlin générera un constructeur vide par défaut.
Avantages :
\n- \n