Como JPA requiere, las @Entity
clases deben tener un constructor predeterminado (no arg) para instanciar los objetos al recuperarlos de la base de datos.
En Kotlin, es muy conveniente declarar las propiedades dentro del constructor primario, como en el siguiente ejemplo:
class Person(val name: String, val age: Int) { /* ... */ }
Pero cuando el constructor no arg se declara como secundario, requiere valores para que se pase el constructor primario, por lo que se necesitan algunos valores válidos para ellos, como aquí:
@Entity
class Person(val name: String, val age: Int) {
private constructor(): this("", 0)
}
En caso de que las propiedades tengan un tipo más complejo que solo String
y Int
no sean anulables, se ve totalmente mal proporcionarles los valores, especialmente cuando hay mucho código en el constructor primario y los init
bloques y cuando los parámetros se usan activamente: - cuando van a reasignarse mediante reflexión, la mayor parte del código se ejecutará nuevamente.
Además, las val
propiedades no se pueden reasignar después de que se ejecuta el constructor, por lo que también se pierde la inmutabilidad.
Entonces la pregunta es: ¿cómo se puede adaptar el código de Kotlin para trabajar con JPA sin duplicación de código, eligiendo valores iniciales "mágicos" y pérdida de inmutabilidad?
PD ¿Es cierto que Hibernate aparte de JPA puede construir objetos sin un constructor predeterminado?
fuente
INFO -- org.hibernate.tuple.PojoInstantiator: HHH000182: No default (no-argument) constructor for class: Test (class must be instantiated by Interceptor)
- Entonces, sí, Hibernate puede funcionar sin el constructor predeterminado.Respuestas:
A partir de Kotlin 1.0.6 , el
kotlin-noarg
complemento del compilador genera constructores sintéticos predeterminados para las clases que se han anotado con anotaciones seleccionadas.Si usa gradle, aplicar el
kotlin-jpa
complemento es suficiente para generar constructores predeterminados para las clases anotadas con@Entity
:Para Maven:
fuente
data class foo(bar: String)
no cambia". Sería bueno ver un ejemplo más completo de cómo encaja esto en su lugar. Graciaskotlin-noarg
ykotlin-jpa
con enlaces que detallan su propósito blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here@Embeddable
atributo, incluso si no lo necesita. De esa manera, será recogido porkotlin-jpa
.solo proporcione valores predeterminados para todos los argumentos, Kotlin hará el constructor predeterminado para usted.
ver el
NOTE
cuadro debajo de la siguiente sección:https://kotlinlang.org/docs/reference/classes.html#secondary-constructors
fuente
@ D3xter tiene una buena respuesta para un modelo, el otro es una característica más nueva en Kotlin llamada
lateinit
:Usaría esto cuando esté seguro de que algo completará los valores en el momento de la construcción o muy poco después (y antes del primer uso de la instancia).
Notarás que cambié
age
abirthdate
porque no puedes usar valores primitivos conlateinit
y también por el momento deben serlovar
(la restricción podría liberarse en el futuro).Entonces, no es una respuesta perfecta para la inmutabilidad, el mismo problema que la otra respuesta a ese respecto. La solución para eso son los complementos a las bibliotecas que pueden manejar la comprensión del constructor de Kotlin y las propiedades de asignación a los parámetros del constructor, en lugar de requerir un constructor predeterminado. El módulo Kotlin para Jackson hace esto, por lo que es claramente posible.
Consulte también: https://stackoverflow.com/a/34624907/3679676 para explorar opciones similares.
fuente
lateinit
cuando tiene un ciclo de vida bien definido que garantiza la inicialización poco después de la construcción, está destinado para esos casos. Mientras que delegado está más destinado a "en algún momento antes del primer uso". Aunque técnicamente tienen un comportamiento y protección similares, no son idénticos.false
para Ints y Booleans respectivamente. Sin embargoLos valores iniciales son obligatorios si desea reutilizar el constructor para diferentes campos, kotlin no permite valores nulos. Entonces, cuando planee omitir el campo, use este formulario en constructor:
var field: Type? = defaultValue
jpa no requirió ningún argumento constructor:
No hay duplicación de código. Si necesita construir una entidad y solo configurar la edad, use este formulario:
no hay magia (solo lea la documentación)
fuente
No hay forma de mantener la inmutabilidad como esta. Los Vals DEBEN inicializarse al construir la instancia.
Una forma de hacerlo sin inmutabilidad es:
fuente
He estado trabajando con Kotlin + JPA durante bastante tiempo y he creado mi propia idea de cómo escribir clases de Entidad.
Solo extiendo un poco tu idea inicial. Como dijiste, podemos crear un constructor privado sin argumentos y proporcionar valores predeterminados para las primitivas , pero cuando intentamos usar otras clases se vuelve un poco complicado. Mi idea es crear un objeto STUB estático para la clase de entidad que actualmente escribe, por ejemplo:
y cuando tengo una clase de entidad relacionada con TestEntity , puedo usar fácilmente el código auxiliar que acabo de crear. Por ejemplo:
Por supuesto, esta solución no es perfecta. Aún necesita crear un código repetitivo que no debería ser necesario. También hay un caso que no se puede resolver bien con el apisonamiento (relación padre-hijo dentro de una clase de entidad) como este:
Este código producirá NullPointerException debido a un problema de huevo de gallina: necesitamos STUB para crear STUB. Desafortunadamente, necesitamos hacer que este campo sea anulable (o alguna solución similar) para que el código funcione.
También en mi opinión, tener Id como último campo (y anulable) es bastante óptimo. No debemos asignarlo a mano y dejar que la base de datos lo haga por nosotros.
No digo que esta sea la solución perfecta, pero creo que aprovecha la legibilidad del código de entidad y las características de Kotlin (por ejemplo, seguridad nula). Solo espero que las futuras versiones de JPA y / o Kotlin hagan que nuestro código sea aún más simple y agradable.
fuente
Como se indicó anteriormente, debe usar el
no-arg
complemento no proporcionado por Jetbrains.Si está utilizando Eclispe, es posible que deba editar la configuración del compilador de Kotlin.
Ventana> Preferencias> Kotlin> Compilador
Active el
no-arg
complemento en la sección Complementos del compilador.Ver: https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10
fuente
Soy una protuberancia, pero parece que tienes que inicializar explícitamente y retroceder a un valor nulo como este
fuente
Similar a @pawelbial, he usado un objeto complementario para crear una instancia predeterminada, sin embargo, en lugar de definir un constructor secundario, solo use argumentos de constructor predeterminados como @iolo. Esto le ahorra tener que definir múltiples constructores y hace que el código sea más simple (aunque está garantizado, definir objetos complementarios "STUB" no es exactamente simple)
Y luego para las clases que se relacionan con
TestEntity
Como @pawelbial ha mencionado, esto no funcionará donde la
TestEntity
clase "tiene una"TestEntity
clase ya que STUB no se habrá inicializado cuando se ejecuta el constructor.fuente
Estas líneas de compilación de Gradle me ayudaron:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50 .
Al menos, se construye en IntelliJ. Está fallando en la línea de comando en este momento.
Y tengo un
y
ruta var: LtreeType no funcionó.
fuente
Si agregó el complemento Gradle https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa pero no funcionó, es probable que la versión esté desactualizada. Estaba en 1.3.30 y no funcionó para mí. Después de actualizar a 1.3.41 (la última en el momento de la escritura), funcionó.
Nota: la versión de kotlin debe ser la misma que este complemento, por ejemplo: así es como agregué ambos:
fuente