JPA 自定义字段映射

4,587 阅读2分钟

问题:SpringBoot JPA 项目中实体类字段和数据库字段映射不够灵活。

我想实现的是

  • 字段如果没加 @Column 注解,就用驼峰式命名。
  • 如果加了 @Column 注解就按照注解中 name 的来。

重写了一个配置类后发现还是很不灵活,不过这个思路应该是对的,这里做个记录,以后解决了再补充。

jpa 提供了配置属性可以改变映射规则,代码如下

jpa:
    hibernate:
      naming:
        physical-strategy: org.springframework.boot.orm.jpa.hibernate.PhysicalNamingStrategyStandardImpl
  • SpringPhysicalNamingStrategy
  • PhysicalNamingStrategyStandardImpl

默认采用的配置是 SpringPhysicalNamingStrategy,即不加 @Column 时默认驼峰字段转成下划线映射,但问题是,项目中有些地方加了这个注解 name 的值被转成含下划线的了,比如

@Column(name = "orderStatus")
private Integer orderStatus;

我想要的是采用 name 后面的字符串作为映射结果,可是默认的映射是变成了 order_status, 竟然改变了注解中 name 的值。本来这里数据库中应该是下划线命名的,但是由于数据库字段命名不规范,所以在不改数据库的前提下,只能改变映射结果。 如果我用 PhysicalNamingStrategyStandardImpl 这个配置类映射结果是 @Column 里 name 属性的值,但是没法实现我想要有些实体类不加 @Column 时默认驼峰字段转下划线映射。

所以接下来实现重写 PhysicalNamingStrategyStandardImpl 中的 toPhysicalColumnName 方法来尝试解决这个问题。

Identifier 类是如何拿到 @Column 的 name 属性的,不好意思,我没看懂。但实际上我要拿的是字段名和字段名上有没有注解,这时要用到反射,先拿到实体类的反射再拿到字段,但问题是要把 @Table 从实体类上拿掉,Identifier 中才保存类名,这样改动又很大了。后面可以把这些类名存入 map 再循环反射从而根据每个类的属性的情况做自定义映射。

思路大概是这样,因为实现后代码中要改的地方也很多,所以我直接都加上了注解。

新建配置类继承 PhysicalNamingStrategyStandardImpl

@Slf4j
@Data
public class MyJpaPhysicalNamingStrategy extends PhysicalNamingStrategyStandardImpl {

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        String text = name.getText();

        //有大写字符,说明没加 @Column 注解,映射到数据库的字段名按驼峰式命名
        if(hasUpperCase(text)){
            return apply(name, jdbcEnvironment);
        }else{
            //加了 @Column 注解,按 name 原文命名
            return new Identifier(text, name.isQuoted());
        }
    }

    //是否包含大写字母
    public static boolean hasUpperCase(String text){
        for(Short i = 0; i < text.length(); i++){
            if(Character.isUpperCase(text.charAt(i))){
                return true;
            }
        }
        return false;
    }

    private Identifier apply(Identifier name, JdbcEnvironment jdbcEnvironment) {
        if (name == null) {
            return null;
        } else {
            StringBuilder builder = new StringBuilder(name.getText().replace('.', '_'));

            for(int i = 1; i < builder.length() - 1; ++i) {
                if (this.isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i), builder.charAt(i + 1))) {
                    builder.insert(i++, '_');
                }
            }

            return this.getIdentifier(builder.toString(), name.isQuoted(), jdbcEnvironment);
        }
    }

    private boolean isUnderscoreRequired(char before, char current, char after) {
        return Character.isLowerCase(before) && Character.isUpperCase(current) && Character.isLowerCase(after);
    }

    protected Identifier getIdentifier(String name, boolean quoted, JdbcEnvironment jdbcEnvironment) {
        if (this.isCaseInsensitive(jdbcEnvironment)) {
            name = name.toLowerCase(Locale.ROOT);
        }

        return new Identifier(name, quoted);
    }

    protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
        return true;
    }
}

最后把 yml 那里改成自己创建的配置类。

参考:
segmentfault.com/a/119000001…
www.jianshu.com/p/a0bb6d40b…

完。