由于在Oracle数据库中,允许没有主键的存在,而hibernate体系又不允许没有主键,这在逻辑上是有冲突的。事实上,表不需要主键。表没有显式键对数据库没有任何影响,因为数据库中的每一行都有一个隐式的唯一数据点,Oracle将其用于存储和某些内部引用,即rowid伪列。rowid是唯一标识数据库中每一行的一段数据。严格来说,没有必要把钥匙放在桌子上。比如现在的项目中,使用的oracle10g数据库有超过1000个系统表,它们没有主键或者说在各自的表上有唯一键。dba和开发人员都可以决定如何在数据库表上创建密钥。然而在开发过程中总是创建主键,这个估计是开发框架导致的,而框架才不管它们是否有用或健全。但作为优秀dba,他们只在有意义的地方创建键。
很多时候线上和线下开发是完全隔离开的,线下开发后的测试环境也不可能那么完美的贴合生产环境,笔者的项目中所使用数据库为marridb,而生产环境中使用的数据库为Oracle,这个联调过程中产生的bug也真的是不可多得:),我这边获取到测试数据确实是来自生产环境,然而这个是通过select语句而来的,这就直接导致这个表是没有主键的。之后把这些测试数据导入到marridb。

现在的情况就是数据库一个没有主键的表,然而通过springboot所创建实体却要求配置主键,这跟数据库中的配置不相符,此外由于实体类中所定义的主键,在数据库中重复出现。

主键问题_第1张图片

这实际上是违背了数据库中主键的定义唯一地标识表中的每一行,通过它可强制表的实体完整性,导致出现这样表的原因是,在Oracle中允许没有主键的定义,通过多表联查汇总到一张表中,然后将这张表导入到marridb中,才会出现这么尴尬的结果

主键问题_第2张图片

导致的结果就是搜索到某一个uuid,其结果有17条,通过jpa取结果整个list长度确实为17,然而,数据展开之后会发现,里面几乎都是重复数据

2019-12-09 19:39:20.411  WARN 26340 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bussiness' defined in file [/xxx/yyy/zzz/service/Bussiness.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'alarmStatisticServiceImpl' defined in file [xxx/yyy/zzz/service/impl/AlarmStatisticServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'processAccountRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List xxx.yyy.zzz.repository.ProcessAccountRepository.getAllData()!

无法创建一个bean,而实际上是由于某一类是未指定标识符的实体,主要原因是hibernate在进行扫描实体的时候,未发现其主键标识。所以就在其类上添加主键标识。因为我的这个类比较特殊,需要添加联合主键

联合主键用Hibernate注解映射方式主要有三种:

  1. 将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并重写equals和hascode,再将该类注解为@Embeddable,最后在主类中(该类不包含联合主键类中的字段)保存该联合主键类的一个引用,并生成set和get方法,之后将该引用注解为@Id
  

@Entity
@Table(name = "Process_Account")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ProcessAccount {

    @Id
    private ProcessAccountPk priAccountpk;
}

  
主键类:  

@Embeddable
public class ProcessAccountPk implements Serializable {
    @Column(name = "UUID")
    private String uuid;

    @Column(name = "xxx")
    private String priAccount;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ProcessAccountPk)) return false;
        ProcessAccountPk that = (ProcessAccountPk) o;
        return Objects.equals(getUuid(), that.getUuid()) &&
            Objects.equals(getPriAccount(), that.getPriAccount());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUuid(), getPriAccount());
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getPriAccount() {
        return priAccount;
    }

    public void setPriAccount(String priAccount) {
        this.priAccount = priAccount;
    }
}
  1. 将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并重写equals和hascode,最后在主类中(该类不包含联合主键类中的字段)保存该联合主键类的一个引用,并生成set和get方法,并将该引用注解为@EmbeddedId
@Entity  
@Table(name="Process_Account")  
public class ProcessAccount {  

    @Id
    @Column(name = "RESUUID")
    private String uuid;

    @Id
    @Column(name = "xxx")
    private String priAccount; 
    @EmbeddedId
    private ProcessAccountPk processAccountPk ;
}

  这个时候ProcessAccountPk为普通Java类即可。

  三、将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并要重写equals和hashcode.最后在主类中(该类包含联合主键类中的字段)将联合主键字段都注解为@Id,并在该类上方将上这样的注解:@IdClass(联合主键类.class),经过踩坑,我用了这个方法:),中间的判断过程就一笔带过吧,有问题私聊

@Entity
@Table(name = "Process_Account")
@IdClass(ProcessAccountPk.class)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ProcessAccount {

    @Id
    @Column(name = "RESUUID")
    private String uuid;

    @Id
    @Column(name = "xxx")
    private String priAccount;
}

ProcessAccountPk 继承自Serializable类

public class ProcessAccountPk implements Serializable {

    private String uuid;

    private String priAccount;

    public ProcessAccountPk() {
    }

    public ProcessAccountPk(String uuid, String priAccount) {
        this.uuid = uuid;
        this.priAccount = priAccount;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ProcessAccountPk)) return false;
        ProcessAccountPk that = (ProcessAccountPk) o;
        return Objects.equals(getUuid(), that.getUuid()) &&
            Objects.equals(getPriAccount(), that.getPriAccount());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUuid(), getPriAccount());
    }

    public String getUuid() {
        return uuid;
    }

    public void setUuid(String uuid) {
        this.uuid = uuid;
    }

    public String getPriAccount() {
        return priAccount;
    }

    public void setPriAccount(String priAccount) {
        this.priAccount = priAccount;
    }
}

主键问题_第3张图片

主键问题_第4张图片

至此,问题解决了,我取出来的数据不会由于uuid或者bussinessName导致数据重复,两个字段同时作为主键进行查询就可以解决这个问题