Jpa@Column注解的name失效的问题 1.问题
首先我jpa使用的spring-boot-data-jpa 。springboot 版本 2.2.6.RELEASE
jpa也没有去配置命名策略,使用的默认驼峰转下划线。
表结构大致如下
1 locationId | locationName
实体如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Getter @Setter @NoArgsConstructor @Accessors(chain = true) @Entity @Access(AccessType.FIELD) @Table(name = "FDYJXH_INSTRUCTOR_TALK_LOCATION") public class StudentTalkLocation implements Serializable { private static final long serialVersionUID = -95507753868472868L; /** * 地点编号 **/ @Id @Column(name = "locationId") @NotBlank private String locationId; /** * 地点名 **/ @Column(name = "locationName") private String locationName; }
因为数据库表中的字段就是使用的驼峰式命名,所以我加上了@Column注解,希望jpa不去转换下划线直接使用@column中的name属性。但是事与愿违。报错如下
1 ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - 列名 'location_id' 无效。
2.解决方法(一) 网上百度了下,很容易的找到了一个答案。修改 jpa 的命名策略 。
在application配置文件加上如下配置。
1 spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
测试了下确实可以解决这个问题。
但是随着这个配置的增加,原先需要自动驼峰转下划线的地方都需要你去加上@Column配置
所以这里没采用这个解决方法,毕竟需要加 @Column注解的只有这一个表(由于一些原因,我无法去修改表结构)
ps:假设这个解决方法解决了你的问题,但我还是希望你能看下解决方法二,因为看完你会发现,并不是 @Column注解失效,而是Jpa的默认命名策略就是这样的。
3.解决方法(二) 百度上大部分都是**解决方法(一)**,所以找了好久没找到,于是想先了解下jpa的命名转化原理。
学习Spring-Data-Jpa(四)—Naming命名策略,源码跟踪 这个博客不错。可以去看下。
jpa的命名转化规则 首先在Entity实体中,命名方式有两种; 一种是显示命名 ,即通过@Table的name属性指定对应的数据库表名称,@Column的name属性指定实体字段对应数据库字段的名称。 另一种是隐式命名 ,显示命名一般不是必要的,所以可以不设置名称,交给框架来进行隐式命名。
命名策略分两步走:
第一步 :如果我们没有使用@Table或@Column指定了表或字段的名称,则由SpringImplicitNamingStrategy为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy不起作用。第二步 :将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名,SpringPhysicalNamingStrategy都会被调用。
所以如果我们想要自定义命名策略,可以根据自己的需求选择继承二者,并在配置文件中通过spring.jpa.hibernate.naming.implicit-strategy 或 spring.jpa.hibernate.naming.physical-strategy 进行指定自己的策略(例如为表名添加指定前缀)。
我们可以看下 SpringPhysicalNamingStrategy 的源码,只贴了其中比较关键的一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 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); }
从代码可以得知 将locationId 转成 location_id 发生在 第二步显示命名的转换中。所以我们即使使用了 @Column 指定的字段名依然还是无法阻止他驼峰转下划线。
解决方法 自定义一个 显示命名策略 , 他的转换规则如下
当 columnName 包含 # 字符时不去驼峰转下划线,只是把#字符去除。
ps : columnName 指的是经过了隐式转换接下来在显示转换需要去转换的值。可以看做 @Column注解中 name 的值
自定义 CustomPhysicalNamingStrategy 的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 package com.qx.yxy.xljkzx.common.jpa;import org.hibernate.boot.model.naming.Identifier;import org.hibernate.boot.model.naming.PhysicalNamingStrategy;import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;import java.util.Locale;public class CustomPhysicalNamingStrategy implements PhysicalNamingStrategy { private static final String NOT_CAST_MARK = "#" ; @Override public Identifier toPhysicalCatalogName (Identifier name, JdbcEnvironment jdbcEnvironment) { return apply(name, jdbcEnvironment); } @Override public Identifier toPhysicalSchemaName (Identifier name, JdbcEnvironment jdbcEnvironment) { return apply(name, jdbcEnvironment); } @Override public Identifier toPhysicalTableName (Identifier name, JdbcEnvironment jdbcEnvironment) { return apply(name, jdbcEnvironment); } @Override public Identifier toPhysicalSequenceName (Identifier name, JdbcEnvironment jdbcEnvironment) { return apply(name, jdbcEnvironment); } @Override public Identifier toPhysicalColumnName (Identifier name, JdbcEnvironment jdbcEnvironment) { return apply(name, jdbcEnvironment); } private Identifier apply (Identifier name, JdbcEnvironment jdbcEnvironment) { if (name == null ) { return null ; } if (name.getText().contains(NOT_CAST_MARK)){ return getIdentifier(name.getText().replace(NOT_CAST_MARK,"" ), name.isQuoted(), jdbcEnvironment); } 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); } protected Identifier getIdentifier (String name, boolean quoted, JdbcEnvironment jdbcEnvironment) { if (isCaseInsensitive(jdbcEnvironment)) { name = name.toLowerCase(Locale.ROOT); } return new Identifier (name, quoted); } protected boolean isCaseInsensitive (JdbcEnvironment jdbcEnvironment) { return true ; } private boolean isUnderscoreRequired (char before, char current, char after) { return Character.isLowerCase(before) && Character.isUpperCase(current) && Character.isLowerCase(after); } }
然后在 application配置文件中指定 显式命名策略为我们自定义的
1 spring.jpa.hibernate.naming.physical-strategy=com.qx.yxy.xljkzx.common.jpa.CustomPhysicalNamingStrategy
然后我们的Entity代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class StudentTalkLocation implements Serializable { private static final long serialVersionUID = -95507753868472868L ; @Id @Column(name = "#locationId") @NotBlank private String locationId; @Column(name = "#locationName") private String locationName; }
测试通过