Conoce: «El infierno del constructor vacío» en Kotlin + JPA
\n\nSi alguna vez has intentado hacer que Kotlin funcione bien con JPA (Java Persistence API), seguramente te has encontrado con la situación en la que, al iniciar la aplicación, Hibernate lanza una excepción como esta:
\n\norg.hibernate.InstantiationException: No default constructor for entity: com.example.User\n\n\n¿Te suena? Claro que sí. Este es el famoso problema que ha acumulado miles de visitas en Stack Overflow y ha recibido el sombrío nombre de «Kotlin with JPA: default constructor hell».
\n\nEn este artículo, de forma tranquila y paso a paso, analizaremos:
\n- \n
- Por qué ocurre este error \n
- Por qué Kotlin «no quiere» generar un constructor vacío \n
- Tres formas probadas de resolver el problema (de lo simple a lo profesional) \n
- Qué método elegir para tu proyecto \n
¡Allá vamos!
\n\n¿Por qué Hibernate requiere un constructor sin parámetros?
\n\nEmpecemos con Java. En Java clásico, estamos acostumbrados a escribir:
\n\npublic class User {\n private Long id;\n private String name;\n\n // Constructor vacío obligatorio\n public User() {}\n\n // Getters, setters...\n}\n\n\nHibernate (y cualquier implementación de JPA) usa reflexión para crear objetos. Cuando lee una fila de la base de datos, necesita crear una instancia de la clase User y luego, mediante reflexión, llenar los campos. Para ello, necesita un constructor sin parámetros (default constructor).
En Java, el compilador crea automáticamente un constructor vacío si no has definido ningún otro. Por lo tanto, el problema generalmente no surge.
\n\n¿Y cuál es la particularidad de Kotlin?
\n\nKotlin es un lenguaje con un fuerte énfasis en la inmutabilidad y la seguridad. Observa una clase típica de Kotlin:
\n\ndata class User(\n val id: Long = 0,\n val name: String = ""\n)\n\n\nAquí ocurren dos cosas clave:
\n- \n
- Todas las propiedades se declaran en el constructor primario (primary constructor) \n
- Las propiedades se declaran como
val(inmutables) y tienen valores por defecto \n
Kotlin no genera un constructor vacío separado si tienes un constructor primario con parámetros. Incluso si todos los parámetros tienen valores por defecto, esto no se considera un «verdadero» constructor sin parámetros a nivel de bytecode de la JVM.
\n\nCuando Hibernate intenta llamar a User.class.newInstance() mediante reflexión, no encuentra un constructor vacío y falla con un error.
Método 1: El más simple — agregar explícitamente un constructor secundario
\n\nLa forma más directa es agregar un segundo constructor vacío manualmente:
\n\ndata class User(\n val id: Long = 0,\n val name: String = ""\n) {\n // Constructor secundario sin parámetros\n constructor() : this()\n}\n\n\nAquí, constructor() : this() significa: «llama al constructor primario con los valores por defecto».
Ventajas:
\n- \n
- Muy simple y claro \n
- No requiere ningún plugin ni configuración adicional \n
Desventajas:
\n- \n
- Hay que escribir este constructor para cada entidad \n
- Si tienes 10-15 entidades, se vuelve tedioso \n
- Es fácil olvidar agregarlo \n
Método 2: Usar open class y hacer todas las propiedades var
\n\nMuchos ejemplos en internet proponen esta solución:
\n\nopen class User {\n var id: Long = 0\n var name: String = ""\n}\n\n\nAquí hacemos la clase open (para que Hibernate pueda crear proxies) y las propiedades var (mutables). Y sin constructor primario, por lo que Kotlin generará un constructor vacío por defecto.
Ventajas:
\n- \n