由于在Oracle数据库中,允许没有主键的存在,而hibernate体系又不允许没有主键,这在逻辑上是有冲突的。事实上,表不需要主键。表没有显式键对数据库没有任何影响,因为数据库中的每一行都有一个隐式的唯一数据点,Oracle将其用于存储和某些内部引用,即rowid伪列。rowid是唯一标识数据库中每一行的一段数据。严格来说,没有必要把钥匙放在桌子上。比如现在的项目中,使用的oracle10g数据库有超过1000个系统表,它们没有主键或者说在各自的表上有唯一键。dba和开发人员都可以决定如何在数据库表上创建密钥。然而在开发过程中总是创建主键,这个估计是开发框架导致的,而框架才不管它们是否有用或健全。但作为优秀dba,他们只在有意义的地方创建键。
很多时候线上和线下开发是完全隔离开的,线下开发后的测试环境也不可能那么完美的贴合生产环境,笔者的项目中所使用数据库为marridb,而生产环境中使用的数据库为Oracle,这个联调过程中产生的bug也真的是不可多得:),我这边获取到测试数据确实是来自生产环境,然而这个是通过select
语句而来的,这就直接导致这个表是没有主键的。之后把这些测试数据导入到marridb。
现在的情况就是数据库一个没有主键的表,然而通过springboot所创建实体却要求配置主键,这跟数据库中的配置不相符,此外由于实体类中所定义的主键,在数据库中重复出现。
这实际上是违背了数据库中主键的定义唯一地标识表中的每一行,通过它可强制表的实体完整性,导致出现这样表的原因是,在Oracle中允许没有主键的定义,通过多表联查汇总到一张表中,然后将这张表导入到marridb中,才会出现这么尴尬的结果
导致的结果就是搜索到某一个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;
}
}
- 将联合主键的字段单独放在一个类中,该类需要实现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;
}
}
至此,问题解决了,我取出来的数据不会由于uuid或者bussinessName导致数据重复,两个字段同时作为主键进行查询就可以解决这个问题