在多对多单向映射中保留枚举集

时间:2010-06-30 19:37:53

标签: java hibernate jpa persistence annotations

我正在使用Hibernate 3.5.2-FINAL和注释来指定我的持久性映射。我正在努力建模应用程序和一组平台之间的关系。每个应用程序都可用于一组平台。

从我所做的所有阅读和搜索中,我认为我需要将平台枚举类保持为实体,并使用连接表来表示多对多关系。我希望关系在对象级别是单向的,也就是说,我希望能够获得给定应用程序的平台列表,但我不需要找出给定平台的应用程序列表。 / p>

以下是我的简化模型类:

@Entity
@Table(name = "TBL_PLATFORM")
public enum Platform {
    Windows,
    Mac,
    Linux,
    Other;

    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id = null;

    @Column(name = "NAME")
    private String name;

    private DevicePlatform() {
        this.name = toString();
    }

    // Setters and getters for id and name...
}

@Entity
@Table(name = "TBL_APP")
public class Application extends AbstractEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    @Column(name = "NAME")
    protected String _name;

    @ManyToMany(cascade = javax.persistence.CascadeType.ALL)
    @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
    @JoinTable(name = "TBL_APP_PLATFORM", 
              joinColumns = @JoinColumn(name = "APP_ID"),
              inverseJoinColumns = @JoinColumn(name = "PLATFORM_ID"))
    @ElementCollection(targetClass=Platform.class)
    protected Set<Platform> _platforms;

    // Setters and getters...
}

当我运行Hibernate hbm2ddl工具时,我看到以下内容(我正在使用MySQL):

create table TBL_APP_PLATFORM (
    APP_ID bigint not null,
    PLATFORM_ID bigint not null,
    primary key (APP_ID, PLATFORM_ID)
);

还会从此表创建相应的外键到应用程序表和平台表。到目前为止一切都很好。

我遇到的一个问题是当我尝试持久化应用程序对象时:

Application newApp = new Application();
newApp.setName("The Test Application");
Set<DevicePlatform> platforms = EnumSet.of(Platform.Windows, Platform.Linux);
newApp.setPlatforms(platforms);
applicationDao.addApplication(newApp);

我想要发生的是创建Platform表中的相应行,即为Windows和Linux创建一行(如果它们尚不存在)。然后,应该创建新应用程序的行,然后创建新应用程序与连接表中的两个平台之间的映射。

我遇到的一个问题是获得以下运行时异常:

2010-06-30 13:18:09,382 6613126-0 ERROR FlushingEventListener Could not synchronize database state with session org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.model.Platform

不知何故,当我尝试保留应用程序时,平台集没有被持久化。级联注释应该用来处理,但我不知道出了什么问题。

所以我的问题是:

  1. 是否有更好的方法来模拟我想要做的事情,例如正在使用适当的Enum?
  2. 如果我的模型没问题,我该如何正确地保留所有对象?
  3. 我一直在努力解决这个问题几个小时,我试图重新创建上面的所有代码,但它可能不完整和/或准确。我希望有人会指出一些明显的东西!

3 个答案:

答案 0 :(得分:39)

您应该决定您的Platform是否是实体

如果它是一个实体,则它不能是enum,因为可能的平台列表存储在数据库中,而不是存储在应用程序中。它应该是具有@Entity注释的常规类,并且您将具有正常的多对多关系。

如果它不是实体,那么您不需要TBL_PLATFORM表,并且您没有多对多关系。在这种情况下,您可以将一组Platform表示为带有位标志的整数字段,或者作为简单的一对多关系。 JPA 2.0使后一种情况变得简单@ElementCollection

@ElementCollection(targetClass = Platform.class) 
@CollectionTable(name = "TBL_APP_PLATFORM",
    joinColumns = @JoinColumn(name = "APP_ID"))
@Column(name = "PLATFORM_ID")
protected Set<Platform> _platforms; 

-

create table TBL_APP_PLATFORM (   
    APP_ID bigint not null,   
    PLATFORM_ID bigint not null, -- the ordinal number of enum value   
    primary key (APP_ID, PLATFORM_ID)   
);

enum Platform没有注释。

答案 1 :(得分:2)

在您的实体下面的映射下使用。假设我们有:

public enum TestEnum { A, B }

然后在您的实体类中:

@ElementCollection(targetClass = TestEnum.class)
@CollectionTable(
        name = "yourJoinTable", 
        joinColumns = @JoinColumn(name = "YourEntityId")
)
@Column(name = "EnumId")
private final Set<TestEnum> enumSet= new HashSet<TestEnum >();

答案 2 :(得分:0)

下面的例子展示了当 Module 是一个实体而 Langue 是一个枚举时的情况。

@Entity
public class Module {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String libelle;

  @ElementCollection(targetClass = Langue.class, fetch = FetchType.EAGER)
  @CollectionTable(name = "link_module_langue",
            joinColumns = @JoinColumn(name = "module_id", referencedColumnName = "id"))
  @Enumerated(EnumType.STRING)
  @Column(name = "langue")
  private Set<Langue> langues;
} 
public enum Langue {
  FRANCAIS, ANGLAIS, ESPAGNOLE
}

你应该创建link_module_langue表,请看下面的sql代码:

CREATE TABLE `link_module_langue` (
  `module_id` BIGINT(20) NOT NULL,
  `langue` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`module_id`, `langue`),
  CONSTRAINT `module_fk`
    FOREIGN KEY (`module_id`)
    REFERENCES `module` (`id`)
    ON DELETE CASCADE
    ON UPDATE CASCADE);

注意:语言不是实体,不会有自己的表。