背景
使用“Spring Boot With H2 Database”+“Spring Data JPA”写的一个小东西,第一次用下h2和jpa,有几个小问题,挑一个记录下。
问题说明
1. 定义一个实体类,其中有一个更新时间属性,定义如下:
2. 在数据库表中定义这个字段:
3. 查询sql:
4. 执行报错:
其实,我整个代码与sql脚本都没有update_time字段,初用jpa,也没仔细了解有什么规范,我当时猜测,可能是我的代码少了什么映射配置,或者是框架层面自己给我转换了。
分析
我看了下异常栈最近的有用的方法调用信息:
在源码这里:
这个columnLabel变量的值是update_time。
这里出现问题的基本原因是,执行sql拿到结果集,期望从结果集取update_time列的过程中出问题了。问题是在columnLabelMap这里,debug看了下这个变量的信息,根据经验猜测应该是框架初始化过程中,对实体类相关字段作映射解析的时候转换的时候发生的。
以前没怎么用过hibernate,比较好奇这个转换的过程,所以小小研究了下,
于是根据调用栈从下往上翻了一下源码及debug,花了几十分钟,终于找到了这个解析转换的源码位置:
先看下这代码入口:在AnnotationBinder.java的bindClass方法上,关注点在persistenClass变量上,这里将要处理为类就是我定义的这个实体类
中间过程这里不多说,接下来要处理实体类字段注解,会走到这里:在AnnotationBinder.java类里,会解析字段上的各种注解,
按我的配置是要走到这里:Ejb3Column.java里这个方法处
前面解析注解的时候,不论@Column注解是否存在并且定义的name是不是空,只要进入redefineColumnName方法,都会调用这段代码:
如果没有使用@Column注解指定,就是else上面使用的那个隐式命名:implicitName,都没有区别,重要是这个命名策略的转换过程,这个physicalNamingStrategy,这里默认的是SpringPhysicalNamingStrategy,看下这个实现,我加上了注释:
@Override
public Identifier toPhysicalColumnName(Identifier name,
JdbcEnvironment jdbcEnvironment) {
return apply(name, jdbcEnvironment);
}
private Identifier apply(Identifier name, JdbcEnvironment jdbcEnvironment) {
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder(name.getText().replace('.', '_'));
for (int i = 1; i < builder.length() - 1; i++) {
// 如果前一个字母是小写,当前字母是大写,后一个字母是小写(驼峰命名)
// 就在这个位置插入一个下划线
if (isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i),
builder.charAt(i + 1))) {
builder.insert(i++, '_');
}
}
// 转换小写返回
return getIdentifier(builder.toString(), name.isQuoted(), jdbcEnvironment);
}
这就是转换的地方,其实问题不大。
解决办法
我简单浏览下源码包下的所有注解,发现好像没看到哪个注解能达到我想要的效果。
必须承认我对这套东西用的少,不怎么了解,感觉还是Mybatis以sql为中心用着舒服,可能用惯了。
大致明白解析过程,其实有好几个地方可以绕过去,比如查询sql取个巧:
或者命名上不规范点,最终我觉得还是正常点,数据表定义规范点,这个字段改一下,改成update_time好了,图省事。
后续有时间再翻下资料,了解下规范,找个正常点通用的方案。