【.com快译】作为一种适用于多平台应用的静态编程语言,Kotlin比Java更加简洁、表达力和代码安全。同时,Kotlin提供与Java的无缝互操作性。也就是说,Java允许开发人员将他们的项目迁移到Kotlin,而无需重写整个代码库。当然,对于这样的迁移,我们可能需要在Kotlin应用中使用JPA(JavaPersistenceAPI)。尽管许多开发人员普遍认为,如果没有JPA,实体将不存在。但是当他们在Kotlin中定义JPA时,经常会遇到各种警告。下面我们一起来探讨一下:作为JPA的经典实现——Hibernate,如何避免各种常见错误,充分利用Kotlin。JPA实体的规则注意这里的实体不是常规的数据传输对象(DataTransferObject,DTO)。为了顺利运行,需要正确定义实体。链接中详细阐述了JPA的一系列规范和限制。其中最重要的是以下两项:尽管实体类可以有其他构造函数,但它必须有一个无参数构造函数。而这个无参数的构造函数必须是公共的(public)或受保护的(protected)。实体类的任何方法或持久实例变量都不能是最终的。上面的规范足以使实体类工作。但是为了让它运行的更顺畅,我们需要附加如下两条规则:所有的Lasy关联只有在明确请求的时候才能加载。否则,我们可能会遇到LazyInitializationException,或者各种意想不到的性能问题。equals()和hashCode()的实现必须考虑实体的可变性质。无参数构造函数主构造函数是Kotlin最受欢迎的特性之一。但是,当添加主构造函数时,它取代了原来的默认函数。因此,如果你在Hibernate中使用它,你可能会遇到异常如:org.hibernate.InstantiationException:Nodefaultconstructorforentity。那么要解决这个问题,可以在所有实体中手动定义无参构造函数。同时,最好使用kotlin-jpa编译器插件,保证在字节码中,为每一个JPA定义相关的类,如:@Entity、@MappedSuperclass或@Embeddable,生成一个无参构造函数。要启用此插件,只需将其添加到kotlin-maven-plugin和compilerPlugins的依赖项中,请参见以下代码片段:org.jetbrains.kotlinkotlin-maven-plugin...jpa......org.jetbrains。kotlinkotlin-maven-noarg${kotlin.version}...和对应的代码段在Gradle中(译者注:一个基于ApacheAnt和ApacheMaven概念的项目自动化构建开源工具)是:buildscript{dependencies{classpath"org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"}}applyplugin:"kotlin-jpa》开启各种类和属性根据JPA规范,所有与JPA相关的类和属性都必须开启。但是,某些JPA提供程序可能不会强制执行此规范。例如,Hibernate在遇到final实体类时不会抛出异常。但是,由于final类不能被子类化(subclassed),Hibernate的代理机制将被关闭。如果没有代理,延迟加载又如何呢?另外,由于程序需要紧急获取所有的ToOne关联,很可能会导致严重的性能问题。不过对于使用静态编织(staticweaving)的EclipseLink来说情况就不同了,毕竟它的懒加载机制并没有使用子类化。如下代码片段所示,与Java不同,在Kotlin中,所有的类、属性和方法默认都是final的。您必须将它们显式标记为打开:@Table(name="project")@EntityopenclassProject{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)openvarid:Long?=null@Column(name="name",nullable=false)openvarname:String?=null...}或者如下代码片段所示,你最好使用一个全开放的编译器插件(https://kotlinlang.org/docs/all-open-plugin.html),默认打开所有与JPA相关的类和属性。通过正确的配置,它可以应用于所有注释为@Entity、@MappedSuperclass和@Embeddable的类:org.jetbrains.kotlinkotlin-maven-plugin...all-openorg。jetbrains.kotlinkotlin-maven-allopen${kotlin.version}对应的是代码中的Gradle中的代码片段是:buildscript{dependencies{classpath"org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"}}applyplugin:"kotlin-allopen"allOpen{annotations("javax.persistence.Entity","javax.persistence.MappedSuperclass","javax.persistence.Embedabble")}为JPA实体使用各种数据类数据类(Dataclasses)吧是为DTO设计的一个很棒的Kotlin特性。它是默认设计的,并配备了各种非常有用的equals()、hashCode()和toString()实现。但是,此类实现不太适合JPA实体。原因是虽然数据类被设计成final类,但是不能像Kotlin一样标记为open。因此,将它们标记为开放以应用于实体的唯一方法是启用完全开放的编译器插件。如下面的代码片段所示,我们将使用以下实体进一步检查数据类。它有一个生成的id、一个名称属性和两个惰性的OneToMany关联:@Table(name="client")@EntitydataclassClient(@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="client")@Column(name="client")id",nullable=false)varid:Long?=null,@Column(name="name",nullable=false)varname:String?=null,@OneToMany(mappedBy="client",orphanRemoval=true)varprojects:MutableSet=mutableSetOf(),@JoinColumn(name="client_id")@OneToManyvarcontacts:MutableSet=mutableSetOf(),)意外得到LAZY关联默认情况下,所有ToMany关联都是惰性的,原因是:不必要地加载它们很容易影响程序性能,例如equals()、hashCode()、toString()在实现过程中通常会用到包括lazy在内的所有属性,因此调用它们会导致对数据库的不必要请求和LazyInitializationException。此外,数据类的默认行为是在其方法中使用主构造函数中的所有字段。这里,我们可以使用IDE生成toString()来通过简单的覆盖来排除所有LAZY字段。如下代码片段所示,由于JPABuddy有自己的toString()生成机制,因此它根本不会提供LAZY字段。@OverrideoverridefuntoString():String{returnthis::class.simpleName+"(id=$id,name=$name)"}当然,仅仅从equals()和hashCode()中排除LAZY字段是不够的,毕竟它们可能仍包含可变属性。Equals()和HashCode()的问题由于JPA实体本质上是可变的,因此为它们实现equals()和hashCode()并不像常规DTO那样简单。有些实体id甚至是由数据库生成的,所以实体第一次持久化后id会发生变化。这意味着我们将没有可依赖的字段来计算hashCode。接下来我们对Client实体做一个简单的测试。valawesomeClient=Client(name="Awesomeclient")valhashSet=hashSetOf(awesomeClient)clientRepository.save(awesomeClient)assertTrue(awesomeClientinhashSet)正如上面的代码片段所说,即使将实体添加到前面几行的集合中,它的最后一个A单行断言也会引发错误。毕竟第一次生成id的时候hashCode是变化的。这就导致HashSet无法在不同的存储桶中找到实体。可以看出,如果在创建实体对象的时候设置了id(比如应用设置的UUID),那么就没有问题;如果id是数据库生成的(其实更常见),就会出现上面的问题。为此,我们可以在使用实体的数据类时持续覆盖equals()和hashCode()。如果想知道详细使用方法,请参考--https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/.其中,对于Client实体,对应的代码段为:overridefunequals(other:Any?):Boolean{if(this===other)returntrueif(other==null||Hibernate.getClass(this)!=Hibernate。getClass(other))returnfalsetherasClientreturnid!=null&&id==other.id}overridefunhashCode():Int=1756406093使用应用设置的ID其实数据类的各种方法主要由main中指定的字段决定构造函数生成。如果只包含eagerimmutablefields,那么数据类就不会存在上述问题。以下片段显示了应用程序设置的不可变id字段:@Table(name="contact")@EntitydataclassContact(@Id@Column(name="id",nullable=false)valid:UUID,){@Column(name="email",nullable=false)valemail:String?=null//otherpropertiesomitted}如果你更喜欢使用数据库生成id,可以参考下面代码段在构造函数中使用immutable自然id:@Table(name="contact")@EntitydataclassContact(@NaturalId@Column(name="email",nullable=false,updatable=false)valemail:String){@Id@GeneratedValue(strategy=GenerationType.IDENTITY)@Column(name="id",nullable=false)varid:Long?=null//otherpropertiesomitted}虽然你可以安全地使用上面的方法,但它几乎违背了使用数据类的初衷。毕竟,这种用法不仅使分解无效,而且还使实体的toString()比普通的旧类更糟糕。Kotlin相对于Java的优势之一是它具有内置的空安全性。我们可以通过非空约束来保证数据库端的空指针安全。最简单的实现方法是在主构造函数中使用非空类型定义各种非空属性(请参考以下代码段):@Table(name="contact")@EntityclassContact(@NaturalId@Column(name="email",nullable=false,updatable=false)valemail:String,@Column(name="name",nullable=false)varname:String@ManyToOne(fetch=FetchType.LAZY,optional=false)@JoinColumn(name="client_id",nullable=false)varclient:Client){//idandotherpropertiesomitted}当然,如果你需要将它们从构造函数中排除(例如:在数据类中),你可以提供默认值,或者把lateinit的修饰符添加到其属性中(请参考以下代码片段):@EntitydataclassContact(@NaturalId@Column(name="email",nullable=false,updatable=false)valemail:String,){@Column(name="name",nullable=false)varname:String=""@ManyToOne(fetch=FetchType.LAZY,optional=false)@JoinColumn(name="client_id",nullable=false)lateinitvarclient:Client//idandotherpropertiesomitted}据此,如果在数据库中确认该属性为非空值,那么我们就可以在Kotlin代码中省略所有对空值的检查。总结让我们通过以下列表总结一下如何在Kotlin中定义JPA实体:请将所有与JPA相关的类及其属性标记为开放,以避免出现重大性能问题。为ManytoOne和OnetoOne关联启用延迟加载,或将完全开放的编译器插件应用于所有使用@Entity、@MappedSuperclass和@Embeddable注释的类。要避免InstantiationException,请在所有JPA相关类中定义无参数构造函数,或使用kotlin-jpa编译器插件。通过启用完全开放的插件,在编译后的字节码中创建一个数据类,并使其具有开放属性。覆盖equals()、hashCode()和toString()。让JPABuddy生成有效的实体,例如:equals()、hashCode()和toString()。此外,如果您想更深入地研究此类实践,请点击我们在GitHub存储库中为您准备的带有测试示例的链接。原标题:BestPracticesofUsingJPA(Hibernate)WithKotlin,作者:AndreyOganesyan