Conforme a JPA exige, @Entity
classes devem ter um construtor padrão (não arg) para instanciar os objetos ao recuperá-los do banco de dados.
No Kotlin, é muito conveniente declarar propriedades no construtor principal, como no exemplo a seguir:
class Person(val name: String, val age: Int) { /* ... */ }
Mas quando o construtor não-arg é declarado como secundário, ele exige que valores sejam passados para o construtor primário, portanto, alguns valores válidos são necessários para eles, como aqui:
@Entity
class Person(val name: String, val age: Int) {
private constructor(): this("", 0)
}
Caso as propriedades tenham um tipo mais complexo do que apenas String
e Int
não sejam anuláveis, será totalmente ruim fornecer os valores para elas, especialmente quando houver muito código no construtor primário einit
blocos principais e quando os parâmetros forem usados ativamente - - quando eles serão reatribuídos por reflexão, a maior parte do código será executada novamente.
Além disso, val
-properties não pode ser reatribuído após a execução do construtor, portanto a imutabilidade também é perdida.
Portanto, a pergunta é: como o código Kotlin pode ser adaptado para funcionar com o JPA sem duplicação de código, escolhendo valores iniciais "mágicos" e perda de imutabilidade?
PS É verdade que o Hibernate, além do JPA, pode construir objetos sem construtor padrão?
fonte
INFO -- org.hibernate.tuple.PojoInstantiator: HHH000182: No default (no-argument) constructor for class: Test (class must be instantiated by Interceptor)
- então, sim, o Hibernate pode funcionar sem o construtor padrão.Respostas:
A partir do Kotlin 1.0.6 , o
kotlin-noarg
plug-in do compilador gera construtores sintéticos padrão para classes que foram anotadas com anotações selecionadas.Se você usa gradle, aplicar o
kotlin-jpa
plug-in é suficiente para gerar construtores padrão para as classes anotadas com@Entity
:Para Maven:
fonte
data class foo(bar: String)
não muda". Seria bom ver um exemplo mais completo de como isso se encaixa. Obrigadokotlin-noarg
ekotlin-jpa
com ligações detalhando seu propósito blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here@Embeddable
atributo, mesmo que não seja necessário. Dessa forma, será captado porkotlin-jpa
.Apenas forneça valores padrão para todos os argumentos, o Kotlin fará o construtor padrão para você.
Veja o
NOTE
caixa abaixo da seguinte seção:https://kotlinlang.org/docs/reference/classes.html#secondary-constructors
fonte
O @ D3xter tem uma boa resposta para um modelo, o outro é um recurso mais recente do Kotlin chamado
lateinit
:Você usaria isso quando tiver certeza de que algo preencherá os valores no momento da construção ou logo após (e antes do primeiro uso da instância).
Você notará que eu mudei
age
parabirthdate
porque você não pode usar valores primitivos comlateinit
e eles também no momento devem servar
(a restrição poderá ser liberada no futuro).Portanto, não é uma resposta perfeita para a imutabilidade, o mesmo problema que a outra resposta a esse respeito. A solução é plugins para bibliotecas que podem lidar com a compreensão do construtor Kotlin e mapear propriedades para parâmetros do construtor, em vez de exigir um construtor padrão. O módulo Kotlin para Jackson faz isso, portanto é claramente possível.
Consulte também: https://stackoverflow.com/a/34624907/3679676 para explorar opções semelhantes.
fonte
lateinit
quando você tem um ciclo de vida bem definido, garantindo a inicialização logo após a construção, ele se destina a esses casos. Considerando que o delegado se destina mais a "algum tempo antes do primeiro uso". Embora tecnicamente tenham comportamento e proteção semelhantes, não são idênticos.false
Ints e Booleans, respectivamente. Não tenho certeza como isso afetaria código do framework emboraOs valores iniciais são requeridos se você deseja reutilizar o construtor para campos diferentes, o kotlin não permite nulos. Portanto, sempre que você planeja omitir o campo, use este formulário no construtor:
var field: Type? = defaultValue
O jpa não exigiu construtor de argumentos:
não há duplicação de código. Se você precisa construir uma entidade e configurar apenas a idade, use este formulário:
não há mágica (basta ler a documentação)
fonte
Não há como manter a imutabilidade assim. Vals DEVE ser inicializado ao construir a instância.
Uma maneira de fazer isso sem imutabilidade é:
fonte
Trabalho com o Kotlin + JPA há um bom tempo e criei minha própria idéia de como escrever classes de entidade.
Eu apenas estendo um pouco sua ideia inicial. Como você disse, podemos criar um construtor privado sem argumentos e fornecer valores padrão para primitivas , mas quando tentamos usar outras classes, fica um pouco confuso. Minha ideia é criar um objeto STUB estático para a classe de entidade que você escreve atualmente, por exemplo:
e quando tenho uma classe de entidade relacionada ao TestEntity , posso usar facilmente o stub que acabei de criar. Por exemplo:
Claro que esta solução não é perfeita. Você ainda precisa criar um código padrão que não deve ser necessário. Além disso, há um caso que não pode ser resolvido bem com stubbing - relação pai-filho dentro de uma classe de entidade - assim:
Esse código produzirá NullPointerException devido a um problema de ovo de galinha - precisamos do STUB para criar o STUB. Infelizmente, precisamos tornar esse campo anulável (ou alguma solução semelhante) para que o código funcione.
Também na minha opinião, ter o ID como último campo (e anulável) é bastante ideal. Não devemos atribuí-lo manualmente e deixar o banco de dados fazer isso por nós.
Não estou dizendo que essa é uma solução perfeita, mas acho que ela aproveita a legibilidade do código da entidade e os recursos do Kotlin (por exemplo, segurança nula). Só espero que os lançamentos futuros do JPA e / ou Kotlin tornem nosso código ainda mais simples e agradável.
fonte
Como mencionado acima, você deve usar o no
no-arg
plugin fornecido pelo Jetbrains.Se você estiver usando Eclispe poderá ser necessário editar as configurações do compilador Kotlin.
Janela> Preferências> Kotlin> Compilador
Ative o
no-arg
plug in na seção Plug-ins do compilador.Veja: https://discuss.kotlinlang.org/t/kotlin-allopen-plugin-doesnt-work-with-sts/13277/10
fonte
Eu sou um nub mim, mas parece que você precisa explicitar inicializador e fallback para um valor nulo como este
fonte
Semelhante a @pawelbial, usei o objeto complementar para criar uma instância padrão; no entanto, em vez de definir um construtor secundário, use apenas argumentos do construtor padrão como @iolo. Isso poupa a necessidade de definir vários construtores e mantém o código mais simples (embora concedido, a definição de objetos complementares "STUB" não é exatamente simples)
E então para as classes que se relacionam com
TestEntity
Como o @pawelbial mencionou, isso não funcionará onde o
TestEntity
classe "possui uma", umaTestEntity
vez que o STUB não terá sido inicializado quando o construtor for executado.fonte
Essas linhas de construção da Gradle me ajudaram:
https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa/1.1.50 .
Pelo menos, ele cria no IntelliJ. Está falhando na linha de comando no momento.
E eu tenho um
e
caminho var: LtreeType não funcionou.
fonte
Se você incluiu o plug-in gradle https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.jpa mas não funcionou, é provável que a versão esteja desatualizada. Eu estava em 1.3.30 e não funcionou para mim. Depois que eu atualizei para 1.3.41 (o mais recente no momento da redação), funcionou.
Nota: a versão do kotlin deve ser igual a este plugin, por exemplo: foi assim que adicionei os dois:
fonte