Spring Data Cassandra多态下子类如何重写父类列定义?
首先咱们得先搞清楚为啥会出现“Multiple definition of identifier”异常:当子类继承父类的age字段(带@Column注解)后,又在主键类K里声明了同名的age字段,ORM框架会把这两个当成同一个数据库列的重复映射,自然就报错了。下面给你几个可行的解决方案:
1. 使用@AttributeOverride注解覆盖父类字段定义
这是最直接的方式,通过这个注解可以在子类里重新定义父类继承来的字段的映射规则,包括把它指定为主键的一部分。不过要注意,主键相关的注解需要配合@IdClass来使用,同时要改用getter/setter的字段访问方式。
举个例子:
// 基础父类:只定义字段,注解留到子类处理 public class A { protected int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // 主键类:实现Serializable是ORM框架的强制要求 @PrimaryKeyClass public class K implements Serializable { @PrimaryKeyColumn private int age; @PrimaryKeyColumn private int ignore; // 必须重写equals和hashCode方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; K k = (K) o; return age == k.age && ignore == k.ignore; } @Override public int hashCode() { return Objects.hash(age, ignore); } } // 子类:覆盖父类字段并标记为主键 @Table @IdClass(K.class) @AttributeOverride(name = "age", column = @Column(name = "age")) public class B extends A { @Id @Override public int getAge() { return super.getAge(); } @Id private int ignore; // 其他字段和方法 }
这里通过重写父类的getAge()方法并添加@Id注解,配合@AttributeOverride明确映射关系,ORM框架就会把父类的age和主键里的age视为同一个字段,不会再触发重复定义的异常。
2. 使用嵌入式主键(@EmbeddedId)+ @MapsId注解
嵌入式主键的方式更灵活,能更好地适配继承场景。你可以把主键类做成嵌入式的,然后用@MapsId把父类的字段和主键里的属性绑定起来。
示例代码:
// 基础父类 public class A { protected int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } } // 嵌入式主键类 @Embeddable public class K implements Serializable { @Column(name = "age") private int age; @Column(name = "ignore") private int ignore; // 同样要实现equals、hashCode和Serializable @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; K k = (K) o; return age == k.age && ignore == k.ignore; } @Override public int hashCode() { return Objects.hash(age, ignore); } } // 子类 @Table public class B extends A { @EmbeddedId private K key; // 用@MapsId把父类的age和主键的age属性绑定 @MapsId("age") @Column(name = "age") @Override public int getAge() { return super.getAge(); } // 不需要单独声明ignore,因为已经包含在嵌入式主键里 }
@MapsId是这里的核心,它能明确告诉ORM框架:父类的age字段对应嵌入式主键里的age属性,两者是同一个数据库列的映射,完美解决重复定义的问题。
3. 把父类标记为@MappedSuperclass
如果父类只是用来共享通用字段,不需要被映射成独立的数据库表,可以给父类加上@MappedSuperclass注解。这样它的字段会直接被继承到子类的表结构中,子类可以自由修改这些字段的注解规则。
示例:
@MappedSuperclass public class A { protected int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } } @Table @IdClass(K.class) public class B extends A { @Id @Column(name = "age") @Override public int getAge() { return super.getAge(); } @Id private int ignore; } @PrimaryKeyClass public class K implements Serializable { private int age; private int ignore; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; K k = (K) o; return age == k.age && ignore == k.ignore; } @Override public int hashCode() { return Objects.hash(age, ignore); } }
@MappedSuperclass的父类不会被ORM框架当作实体类,只是作为字段的容器,子类可以毫无冲突地把继承来的字段标记为主键。
额外注意事项
- 所有主键类都必须实现
Serializable接口,并且正确重写equals()和hashCode()方法,这是ORM框架的硬性要求。 - 尽量保持字段访问策略一致:要么都在字段上加注解,要么都在getter方法上加,避免框架混淆。
- 不同ORM框架(比如Hibernate、EclipseLink)对注解的支持细节可能略有差异,遇到问题可以针对性调整注解的使用方式。
内容的提问来源于stack exchange,提问作者Richard Watson

