Kotlin con JPA: cómo vencer el infierno del constructor por defecto

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Conoce: «El infierno del constructor vacío» en Kotlin + JPA

\n\n

Si 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\n
org.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\n

En 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
\n\n

¡Allá vamos!

\n\n

¿Por qué Hibernate requiere un constructor sin parámetros?

\n\n

Empecemos con Java. En Java clásico, estamos acostumbrados a escribir:

\n\n
public 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\n

Hibernate (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).

\n\n

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\n

Kotlin es un lenguaje con un fuerte énfasis en la inmutabilidad y la seguridad. Observa una clase típica de Kotlin:

\n\n
data class User(\n    val id: Long = 0,\n    val name: String = ""\n)\n
\n\n

Aquí 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
\n\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\n

Cuando Hibernate intenta llamar a User.class.newInstance() mediante reflexión, no encuentra un constructor vacío y falla con un error.

\n\n

Método 1: El más simple — agregar explícitamente un constructor secundario

\n\n

La forma más directa es agregar un segundo constructor vacío manualmente:

\n\n
data class User(\n    val id: Long = 0,\n    val name: String = ""\n) {\n    // Constructor secundario sin parámetros\n    constructor() : this()\n}\n
\n\n

Aquí, constructor() : this() significa: «llama al constructor primario con los valores por defecto».

\n\n

Ventajas:

\n
    \n
  • Muy simple y claro
  • \n
  • No requiere ningún plugin ni configuración adicional
  • \n
\n\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
\n\n

Método 2: Usar open class y hacer todas las propiedades var

\n\n

Muchos ejemplos en internet proponen esta solución:

\n\n
open class User {\n    var id: Long = 0\n    var name: String = ""\n}\n
\n\n

Aquí 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.

\n\n

Ventajas:

\n
    \n

Blogs

Book Recommendations