从 Hibernate 3/Grails 2.2.4 迁移到 Hiberate 5/Grails 3.2.4 时出现 MappingException
MappingException when migrating from Hibernate 3 / Grails 2.2.4 to Hiberate 5 / Grails 3.2.4
我有一个使用 grails 2.2.4 和 hibernate 3 的项目。我正在将它迁移到使用 hibernate 5 的 grails 3.2.4。我从头开始创建一个 grails 3.2.4 项目,现在我正在慢慢地从旧项目中复制一小部分代码并让它运行起来。起初,我只复制了我的用户、用户角色和角色 类 并让我的整个 spring 安全登录流程正常工作。然后,我复制了剩余的域对象并尝试启动我的项目。我的域对象使用 GORM 映射,而不是 JPA 注释。该项目现在甚至不会启动,并在启动时出现以下异常(我链接到 public 要点,因为内容对于堆栈溢出允许的内容来说太长了)。
https://gist.github.com/schmickie/10522d3a2b8a66b6fb79f76e2af0fd72
我在网上搜索了一些错误,我能找到的所有内容都表明它与设置复合主键的问题有关。但是,我没有使用任何复合主键。任何人都知道出了什么问题?错误中引用的域对象 Api 和方法如下所示。
class Api implements Serializable {
String name
boolean enabled
String apiVersion
String swaggerVersion
String resourcePath
String jsonData
boolean https = true
String regionSource
String contractName
String contractBasePath
Date dateCreated
Date lastUpdated
Date deprecationDate
static hasMany = [methods: Method, models: Model, apiServers: ApiServer]
static mapping = {
cache true
}
static constraints = {
name(nullable: false, blank: false)
enabled()
apiVersion(nullable: true, blank: true)
resourcePath(nullable: true, blank: true)
swaggerVersion(nullable: true, blank: true)
jsonData(type: 'text', nullable: true, blank: true)
dateCreated()
lastUpdated()
deprecationDate(nullable: true)
regionSource(nullable: true)
contractName(nullable: true)
contractBasePath(nullable: true)
}
public Method getMethod(String name) {
for (Method method : methods) {
if (method.name.equals(name)) {
return method
}
}
return null
}
}
class Method implements Serializable, Comparable<Method> {
String name
boolean enabled
String edgePath
HttpMethodEnum edgeHttpMethod
String servicePath
HttpMethodEnum serviceHttpMethod
String summary
String notes
boolean deprecatedMethod
String responseClass
SortedSet<EdgeParameter> edgeParameters
SortedSet<ServiceParameter> serviceParameters
Date dateCreated
Date lastUpdated
String publicResponseTransformScript
String baseResponseTransformScript
String regionFieldName
String platformFieldName
String apiKeyFieldName
String uriTransformScript
long cacheExpiry
static belongsTo = [api: Api]
static hasMany = [edgeParameters: EdgeParameter, serviceParameters: ServiceParameter, errorResponses: ApiErrorResponse]
static mapping = {
cache true
errorResponses sort: 'code', order: "asc"
}
static constraints = {
name(blank: false)
enabled()
edgePath(nullable: false, blank: false)
edgeHttpMethod(nullable: false, blank: false)
servicePath(nullable: false, blank: false)
serviceHttpMethod(nullable: false, blank: false)
summary(nullable: false, blank: false)
notes(nullable: true, blank: true)
deprecatedMethod()
responseClass(nullable: true, blank: true)
publicResponseTransformScript(nullable: true)
baseResponseTransformScript(nullable: true)
regionFieldName(nullable: true)
platformFieldName(nullable: true)
apiKeyFieldName(nullable: true)
uriTransformScript(nullable: true)
cacheExpiry(nullable: false, blank: true)
}
}
好吧,我明白了。我对 hibernate/GORM 初始化代码做了很多单步执行。我发现问题实际上出在同一 class 中的 无关 映射。在方法 class 中,还有另一个如上所示的映射,称为 errorResponses
。当调用方法 DefaultGrailsDomainClass.establishRelationshipForCollection
来建立 Api 和方法 class 之间的关系时,它会调用另一个方法 GrailsClassUtils.getPropertiesOfType
。此 class 遍历方法 class 的所有属性,并尝试查找其类型与 api
关联类型匹配的任何属性。这是方法实现:
public static PropertyDescriptor[] getPropertiesOfType(Class<?> clazz, Class<?> propertyType) {
if (clazz == null || propertyType == null) {
return new PropertyDescriptor[0];
}
Set<PropertyDescriptor> properties = new HashSet<PropertyDescriptor>();
try {
for (PropertyDescriptor descriptor : BeanUtils.getPropertyDescriptors(clazz)) {
Class<?> currentPropertyType = descriptor.getPropertyType();
if (isTypeInstanceOfPropertyType(propertyType, currentPropertyType)) {
properties.add(descriptor);
}
}
}
catch (Exception e) {
// if there are any errors in instantiating just return null for the moment
return new PropertyDescriptor[0];
}
return properties.toArray(new PropertyDescriptor[properties.size()]);
}
它找到的第一个 属性 是 api
属性,它通过了 isTypeInstanceOfPropertyType
检查并被添加到 properties
集合。由于名为 getApiErrorResponse
的辅助方法命名不当,对 BeanUtils.getPropertyDescriptors(clazz)
的调用在 returned 集合中包含了一个名为 apiErrorResponse
的字段。但是,在我的方法 class 中没有这样的字段(有一个名为 errorResponses
的映射关联,但没有名为 apiErrorResponse
的关联)。因此,此 non-existent 字段的 PropertyDescriptor
的 propertyType
为空。然后,当使用 currentPropertyType
的 null
值调用 isTypeInstanceOfPropertyType
时,它会抛出 NullPointerException。这导致 getPropertiesOfType
方法 returning new PropertyDescriptor[0]
,如 catch
子句中所示。稍后,return 映射到 api
的 属性 描述符的失败导致为关联生成不正确的 OneToOne 映射类型,并且该映射也缺少映射字段.
基本上这是一个 grails 代码没有提供足够的信息来说明真正出错的情况。它默默地抑制了另一个字段上发生的 NullPointerException,而另一个字段上的错误导致 api
字段的初始化不正确。这意味着后来的错误消息是错误的抱怨,因为 information/configuration 被假定为丢失,而实际上它没有丢失,导致工程师非常困惑。
我的最终解决方法是将 getApiErrorResponse 辅助方法从 Method class 移到 MethodService 中。请注意,在我的域对象中使用此辅助方法在我从中迁移的旧 Grails 项目(带有 Hiberate 3 的 Grails 2.2.4)中运行良好,因此可能是最新版本 Grails/GORM/Spring/Groovy 中的新行为假设所有 get* 方法都映射到字段。我不知道其中哪一个是罪魁祸首,但它们的版本都比我的旧项目新。
我还向 grails-core 提交了一个 PR,以便在发生此类异常时记录有用的错误消息,而不是静静地吞下它们。
我有一个使用 grails 2.2.4 和 hibernate 3 的项目。我正在将它迁移到使用 hibernate 5 的 grails 3.2.4。我从头开始创建一个 grails 3.2.4 项目,现在我正在慢慢地从旧项目中复制一小部分代码并让它运行起来。起初,我只复制了我的用户、用户角色和角色 类 并让我的整个 spring 安全登录流程正常工作。然后,我复制了剩余的域对象并尝试启动我的项目。我的域对象使用 GORM 映射,而不是 JPA 注释。该项目现在甚至不会启动,并在启动时出现以下异常(我链接到 public 要点,因为内容对于堆栈溢出允许的内容来说太长了)。
https://gist.github.com/schmickie/10522d3a2b8a66b6fb79f76e2af0fd72
我在网上搜索了一些错误,我能找到的所有内容都表明它与设置复合主键的问题有关。但是,我没有使用任何复合主键。任何人都知道出了什么问题?错误中引用的域对象 Api 和方法如下所示。
class Api implements Serializable {
String name
boolean enabled
String apiVersion
String swaggerVersion
String resourcePath
String jsonData
boolean https = true
String regionSource
String contractName
String contractBasePath
Date dateCreated
Date lastUpdated
Date deprecationDate
static hasMany = [methods: Method, models: Model, apiServers: ApiServer]
static mapping = {
cache true
}
static constraints = {
name(nullable: false, blank: false)
enabled()
apiVersion(nullable: true, blank: true)
resourcePath(nullable: true, blank: true)
swaggerVersion(nullable: true, blank: true)
jsonData(type: 'text', nullable: true, blank: true)
dateCreated()
lastUpdated()
deprecationDate(nullable: true)
regionSource(nullable: true)
contractName(nullable: true)
contractBasePath(nullable: true)
}
public Method getMethod(String name) {
for (Method method : methods) {
if (method.name.equals(name)) {
return method
}
}
return null
}
}
class Method implements Serializable, Comparable<Method> {
String name
boolean enabled
String edgePath
HttpMethodEnum edgeHttpMethod
String servicePath
HttpMethodEnum serviceHttpMethod
String summary
String notes
boolean deprecatedMethod
String responseClass
SortedSet<EdgeParameter> edgeParameters
SortedSet<ServiceParameter> serviceParameters
Date dateCreated
Date lastUpdated
String publicResponseTransformScript
String baseResponseTransformScript
String regionFieldName
String platformFieldName
String apiKeyFieldName
String uriTransformScript
long cacheExpiry
static belongsTo = [api: Api]
static hasMany = [edgeParameters: EdgeParameter, serviceParameters: ServiceParameter, errorResponses: ApiErrorResponse]
static mapping = {
cache true
errorResponses sort: 'code', order: "asc"
}
static constraints = {
name(blank: false)
enabled()
edgePath(nullable: false, blank: false)
edgeHttpMethod(nullable: false, blank: false)
servicePath(nullable: false, blank: false)
serviceHttpMethod(nullable: false, blank: false)
summary(nullable: false, blank: false)
notes(nullable: true, blank: true)
deprecatedMethod()
responseClass(nullable: true, blank: true)
publicResponseTransformScript(nullable: true)
baseResponseTransformScript(nullable: true)
regionFieldName(nullable: true)
platformFieldName(nullable: true)
apiKeyFieldName(nullable: true)
uriTransformScript(nullable: true)
cacheExpiry(nullable: false, blank: true)
}
}
好吧,我明白了。我对 hibernate/GORM 初始化代码做了很多单步执行。我发现问题实际上出在同一 class 中的 无关 映射。在方法 class 中,还有另一个如上所示的映射,称为 errorResponses
。当调用方法 DefaultGrailsDomainClass.establishRelationshipForCollection
来建立 Api 和方法 class 之间的关系时,它会调用另一个方法 GrailsClassUtils.getPropertiesOfType
。此 class 遍历方法 class 的所有属性,并尝试查找其类型与 api
关联类型匹配的任何属性。这是方法实现:
public static PropertyDescriptor[] getPropertiesOfType(Class<?> clazz, Class<?> propertyType) {
if (clazz == null || propertyType == null) {
return new PropertyDescriptor[0];
}
Set<PropertyDescriptor> properties = new HashSet<PropertyDescriptor>();
try {
for (PropertyDescriptor descriptor : BeanUtils.getPropertyDescriptors(clazz)) {
Class<?> currentPropertyType = descriptor.getPropertyType();
if (isTypeInstanceOfPropertyType(propertyType, currentPropertyType)) {
properties.add(descriptor);
}
}
}
catch (Exception e) {
// if there are any errors in instantiating just return null for the moment
return new PropertyDescriptor[0];
}
return properties.toArray(new PropertyDescriptor[properties.size()]);
}
它找到的第一个 属性 是 api
属性,它通过了 isTypeInstanceOfPropertyType
检查并被添加到 properties
集合。由于名为 getApiErrorResponse
的辅助方法命名不当,对 BeanUtils.getPropertyDescriptors(clazz)
的调用在 returned 集合中包含了一个名为 apiErrorResponse
的字段。但是,在我的方法 class 中没有这样的字段(有一个名为 errorResponses
的映射关联,但没有名为 apiErrorResponse
的关联)。因此,此 non-existent 字段的 PropertyDescriptor
的 propertyType
为空。然后,当使用 currentPropertyType
的 null
值调用 isTypeInstanceOfPropertyType
时,它会抛出 NullPointerException。这导致 getPropertiesOfType
方法 returning new PropertyDescriptor[0]
,如 catch
子句中所示。稍后,return 映射到 api
的 属性 描述符的失败导致为关联生成不正确的 OneToOne 映射类型,并且该映射也缺少映射字段.
基本上这是一个 grails 代码没有提供足够的信息来说明真正出错的情况。它默默地抑制了另一个字段上发生的 NullPointerException,而另一个字段上的错误导致 api
字段的初始化不正确。这意味着后来的错误消息是错误的抱怨,因为 information/configuration 被假定为丢失,而实际上它没有丢失,导致工程师非常困惑。
我的最终解决方法是将 getApiErrorResponse 辅助方法从 Method class 移到 MethodService 中。请注意,在我的域对象中使用此辅助方法在我从中迁移的旧 Grails 项目(带有 Hiberate 3 的 Grails 2.2.4)中运行良好,因此可能是最新版本 Grails/GORM/Spring/Groovy 中的新行为假设所有 get* 方法都映射到字段。我不知道其中哪一个是罪魁祸首,但它们的版本都比我的旧项目新。
我还向 grails-core 提交了一个 PR,以便在发生此类异常时记录有用的错误消息,而不是静静地吞下它们。