JPA - 实体设计问题

7

我正在开发一个Java桌面应用程序,使用JPA进行持久化。我有以下问题:

我有两个实体:

  • Country(国家)
  • City(城市)

Country具有以下属性:

  • CountryName(主键)

City具有以下属性:

  • CityName(城市名称)

由于在两个不同的国家中可能存在两个具有相同名称的城市,因此数据库中City表的主键是由CityNameCountryName组成的复合主键。

现在我的问题是如何将City的主键作为Entity在Java中实现

   @Entity
   public class Country implements Serializable {
       private String countryName;

       @Id
       public String getCountryName() {
           return this.countryName;
       }
   }

  @Entity
  public class City implements Serializable {
           private CityPK cityPK;
           private Country country;

           @EmbeddedId
           public CityPK getCityPK() {
               return this.cityPK;
           }
   }


   @Embeddable
   public class CityPK implements Serializable {
       public String cityName;
       public String countryName;
   }

现在我们知道,CountryCity之间的关系是OneToMany。为了展示这种关系,我在City类中添加了一个country变量。
但是,City类对象中有重复数据(countryName)存储在两个地方:一个在country对象中,另一个在cityPK对象中。
然而,两者都是必要的:
  • cityPK对象中的countryName是必要的,因为我们通过这种方式实现复合主键。

  • country对象中的countryName是必要的,因为这是展示对象间关系的标准方式。

如何解决这个问题?
2个回答

7
CityPK中,countryName应该使用@Column(insertable = false, updatable = false)标记为只读,并且两个countryName应映射到同一列(使用name属性):
  @Entity
  public class City implements Serializable {
           @EmbeddedId
           private CityPK cityPK;

           @ManyToOne
           @JoinColumn(name = "countryName")
           private Country country;
  }


   @Embeddable
   public class CityPK implements Serializable {
       public String cityName;

       @Column(name = "countryName", insertable = false, updatable = false)
       public String countryName;
   }

这是处理这种类型问题的标准方式吗?在JPA中,这个问题常见吗? - Amit
@Yatendra:是的,这是一种标准方法(如果您不使用代理键,就像Peter建议的那样)。 - axtavt

3

我认为解决这类问题的正确方法是使用生成的内部(通常是Long)ID代替自然主键 - 这样可以消除整个问题。当然,这需要修改您的数据库架构,但从您的帖子中我可以假设这是可能的。

@Entity
public class City implements Serializable {
    private Long id;

    private String name;
    private Country country;

    @Id
    @GeneratedValue
    @Column(name = "CITY_ID")
    public Long getId() {
        return this.id;
    }
    private void setId(Long id) {
        this.id = id;
    }

    // more getters, setters and annotations
}

那么我认为在 equals 方法中不能使用 id 进行比较相等,对吗? - Amit
@Yatendra 为什么不行呢?同名但不同国家的两个城市必须具有不同的ID。您只需要确保在表中不会插入同一国家的相同城市两次。这可以通过数据库触发器或Hibernate拦截器来实现。 - Péter Török
你不能在equals()中使用id,因为id是由数据库生成的。并且在对象持久化之前不可用。请参考Gavin King的《Java Persistence with Hibernate》,查看关于业务键和代理键的讨论。 - Ram
@Ram,只有在您需要比较两个尚未持久化的实体时才会出现这个问题(在我看来,这种情况并不常见,但我可能是错的)。而且在这种情况下,如果idnull,则可以扩展equals以比较其他重要属性。 - Péter Török
如果我们执行new City()并将其添加到一个Set中等操作,这会导致问题。因为ID可能会发生改变。我也不确定,但这就是书上说的。 - Ram

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接