I use Hibernate 3.5.2-FINAL with annotations to indicate my persistence mappings. I struggle with modeling the relationship between the Application and the set of platforms. Each application is available for a set of platforms.
Of all the checks and searches I've done, I think I need the platform enumeration class to be saved as Entity, and have a join table to represent many-to-many relationships. I want the relationship to be unidirectional at the object level, that is, I would like to get a list of platforms for this application, but I do not need to look for a list of applications for this platform.
Here are my simplified model classes:
@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... }
When I run the Hibernate hbm2ddl tool, I see the following (I use MySQL):
create table TBL_APP_PLATFORM ( APP_ID bigint not null, PLATFORM_ID bigint not null, primary key (APP_ID, PLATFORM_ID) );
Corresponding foreign keys are also generated from this table to the application table and platform table. So far so good.
One problem that I am facing is when I try to save the application object:
Application newApp = new Application(); newApp.setName("The Test Application"); Set<DevicePlatform> platforms = EnumSet.of(Platform.Windows, Platform.Linux); newApp.setPlatforms(platforms); applicationDao.addApplication(newApp);
I would like the corresponding rows in the Platform table to be created, i.e. created a string for Windows and Linux if they do not already exist. Then a row should be created for the new application, and then a mapping between the new application and the two platforms in the connection table.
One issue that I am facing is the following exception to execute:
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
Be that as it may, the toolbox is not saved when I try to save the application. Cascading annotations should take care of this, but I don't know what happened.
So my questions are:
- Is there a better way to simulate what I want to do, for example. Is Enum suitable?
- If my model is fine, how do I save all objects correctly?
I struggled with this for several hours and I tried to recreate all the code above, but it may not be complete and / or accurate. I hope someone points out something obvious!