Persisting set of Enums in a many-to-many unidirectional mapping - java

I'm using Hibernate 3.5.2-FINAL with annotations to specify my persistence mappings. I'm struggling with modelling a relationship between an Application and a set of Platforms. Each application is available for a set of platforms.
From all the reading and searching I've done, I think I need to have the platform enum class be persisted as an Entity, and to have a join table to represent the many-to-many relationship. I want the relationship to be unidirectional at the object level, that is, I want to be able to get the list of platforms for a given application, but I don't need to find out the list of applications for a given 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'm using MySQL):
create table TBL_APP_PLATFORM (
APP_ID bigint not null,
PLATFORM_ID bigint not null,
primary key (APP_ID, PLATFORM_ID)
);
The appropriate foreign keys are also created from this table to the application table and platform table. So far so good.
One problem I'm running into is when I try to persist an 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);
What I would like to happen is for the appropriate rows in the Platform table to created, i.e. create a row for Windows and Linux, if they don't already exist. Then, a row for the new application should be created, and then the mapping between the new application and the two platforms in the join table.
One issue I'm running into is getting the following runtime exception:
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
Somehow, the platform set is not being persisted when I try to persist the application. The cascade annotations are supposed to take care of that, but I don't know what's wrong.
So my questions are:
Is there a better way to model what I want to do, e.g. is using an Enum appropriate?
If my model is alright, how do I properly persist all of the objects?
I've been struggling with this for hours, and I've tried to recreate all of the code above, but it might not be complete and/or accurate. I'm hoping someone will point out something obvious!

You should decide whether your Platform is an entity or not.
If it's an entity, it can't be an enum, because list of possible platforms is stored in the database, not in the application. It should be a regular class with #Entity annotation and you will have a normal many-to-many relation.
If it isn't an entity, then you don't need TBL_PLATFORM table, and you don't have a many-to-many relation. In this case you can represent a set of Platforms either as an integer field with bit flags, or as a simple one-to-many relation. JPA 2.0 makes the latter case simple with #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)
);
and enum Platform without annotations.

Simple use below mapping on your entity. Suppose that we have:
public enum TestEnum { A, B }
Then in your Entity class:
#ElementCollection(targetClass = TestEnum.class)
#CollectionTable(
name = "yourJoinTable",
joinColumns = #JoinColumn(name = "YourEntityId")
)
#Column(name = "EnumId")
private final Set<TestEnum> enumSet= new HashSet<>();

The following example shows what the situation is when Module is an entity and Langue is an enum.
#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
}
You should create link_module_langue table, please see the following sql code :
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);
NB: Langue is not an entity and would not have its own table.

Related

Hibernate-OneToMany mapping for existing DB Tables

I am trying to join to Hibernate Entities in a OneToOne Mapping. I am able to fetch the data for a given primary key from the Main Entity, the joining entity, however, returns null. I am new to hibernate and any help will be appreciated.
I have two Tables,
PT_CORE
Primary Key: ptId - Integer;
Foreign Key: stId(ST_AUX) - Integer;
Columns: ptId, ptName
ST_AUX
Primary Key: stId;
Columns: stId, stName
The two tables get populated by other applications and mine is a read-only operation.
Below is my first Entity class(PtCore.java)
#Entity
#Table(name="PT_CORE")
public class PtCore implements Serializable{
#Id
#Column(name="ptId", nullable = false)
private int id;
#Column(nullable=false)
private int stId; //The Foreign key column
#OneToOne
#JoinTable( name = "core_aux", joinColumns = {#JoinColumn(Name="ptId")},
inverseJoinColumns = {#JoinColumn(Name="stId")}
)
private StAux staux;
//Getters, setters and toString() for above
}
StAux is another Entity, defined as below,
#Entity
#Table(name="ST_AUX")
public class StAux implements Serializable {
#Id
#Column(nullable=false)
private Integer stId;
#OneToOne
private PtCore ptcore;
#Column
private String stName;
//Getters, Setters and toString follow.
}
I do below in the Service method:
PtCore obj = (PtCore) session.get(PtCore.class,1);
System.out.println(obj);
In the Results, I get the value of ptName, but the stAux class variables are null, Indicating that the join does not work as expected.
First of all you have the mapping information existing in your PT_CORE. And I assume it is something like FOREIGN KEY (stid) REFERENCES (stid). If you want to use existing schema and existing data I guess there is no mapping table core_aux really existing. At least you did not mention it. However it is visible as #JoinTable annotation but still there is this above mentioned foreign key which seems to be the real mapping (so again not the join table).
I suggest the following
remove this
#Column(nullable=false)
private int stId; //The Foreign key column
from your PtCore. I think it is not needed. Also in PtCore, remove the #JoinTable (because what I told above) and add mapping informaiion to #OneToOne annotation, like:
#OneToOne
#JoinColumn(name = "stid")
private StAux staux;
from your PT_CORE.
Then in StAux alter also a bit:
#Id
#Column(name = "stid") // this might not be needed but if there is like "st_id"...
private Integer stId; // so just for sure
#OneToOne(mappedBy = "staux")
private PtCore ptcore;
Because you have existing tables and constraints there might raise errors if hibernate tries to auto-generate those again by JPA instructions.
Check this for example for more information.
UPDATE: just realized also that in your title is #OneToMany but in your code is #OneToOne.
So you might want to elaborate your question and/or title a bit.
In your relation, the owning side is PtCore, the inverse side is StAux.
In bidirectional OneToOne relations, the inverse side has to have the mappedBy attribute. Actually, the mappedBy attribute contains the name of the association-field on the owning side.
So, you must change your inverse side code (StAux Entity). You have to add mappedBy attribute to #OneToOne in StAux class:
#OneToOne(mappedBy="staux")
private PtCore ptcore;

JPA mapping 2 entity table with a relationship table between them

I'm trying to map these two entitiy tables to one model class, game and platform. A game can have multiple platforms. So in my db I have the following tables:
CREATE TABLE IF NOT EXISTS games (
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR,
summary TEXT,
avg_score REAL,
);
CREATE TABLE IF NOT EXISTS platforms (
id SERIAL NOT NULL PRIMARY KEY,
name VARCHAR
);
And a relationship table called Game_Platforms
CREATE TABLE IF NOT EXISTS game_platforms (
id serial not null primary key,
game_id INTEGER NOT NULL,
platform_id INTEGER NOT NULL,
release_date date not null,
FOREIGN KEY (game_id) REFERENCES games (id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (platform_id) REFERENCES platforms (id) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE(game_id,platform_id, release_date) --The same game can be released for the same platform multiple times (i.e. remaster)
);
Note that the tables have more columns but I'm just showing the ones that are relevant to this problem.
So on my model I have the following class which I want to map to the db using JPA
#Entity
#Table(name = "games")
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "games_gameid_seq")
private long id;
#Column(length = 100, nullable = false, unique = true)
private String name;
//Map with platforms
private Map<String, LocalDate> platforms;
And platform which doesn't have a class because I didn't find it necessary. I don't think a #OneToMany annotation will be enough to map this. Adding a class "Platform" to my model would be of last resort as I would have to change most of my interfaces, needing to change the whole app. Any idea on how to aproach this? Thanks in advance!
When using an Object Relational Mapping tool such as Hibernate / the JPA API, you should be thinking in an object relational model - keyword: object. Thus not wanting to map a table as an entity is very counterproductive to your needs as you won't be able to use the technology as intended; you'd have to resort to using native queries.
Instead, do map all tables involved (GamPlatform in this case) and actually put JPA to work by adding the relational mappings.
#Entity
public class Game {
...
#OneToMany(mappedBy="game")
private Set<GamePlatform> gamePlatforms;
...
}
The real problem to be solved is the fact that there is a large pile of existing code which will break because of this change. To temporarily deal with that you could maintain the existing getter for the platforms property as it is and under water build up the returned HashMap from the newly defined model. Assuming the getter is named getPlatforms():
#Deprecated
public Map<String, LocalDate> getPlatforms(){
Map<String,LocalDate> ret = new HashMap<>();
gamePlatforms.stream().forEach(gamePlatform -> {
ret.put(gamePlatform.getPlatform().getName(), gamePlatform.getReleaseDate());
});
return ret;
}
You could mark that method as deprecated and over time migrate code to use the proper entity classes instead.
Footnote: I'm sure there is an even compacter Java 8 way to do the above using Collectors.toMap, that is left as an exercise to the reader.
if I understand your meaning correctly you have one game and each game have several platform!
you need onToMany relation and need to creat platform model.
look at this example
Game.java
#Entity
#Table(name = "games")
public class Game {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "games_gameid_seq")
private long id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy="game_name")
#Column(name = "PlatFormId", nullable = false)
private Set<PlatForm> PlatFormId= new HashSet<PlatForm>(0);
}
PlatForm.java
#Entity
#Table(name = "platForm")
public class PlatForm {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "games_gameid_seq")
private long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "game_name", nullable = false)
private Game game;

Hibernate: #OneToOne not producing "one to one" relationship in database

I'm relatively new to JPA and Hibernate and am trying to see how the #OneTo One annotation works, let's say I have an entity "Task" with the following relation:
#OneToOne
#JoinColumn(name = "manager_id")
private Manager manager;
And there's the entity "Manager":
#Entity
#Table(name = "manager")
public class Manager {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
public Manager() {
}
When I run the test file along with the "hibernate.hbm2ddl.auto" set to "update" I get a Many to One relation in the database (as you can see, there is no unique constraint of any kind that'd make it a one to one relation):
CREATE TABLE IF NOT EXISTS `timesheet`.`task` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`completed` BIT(1) NOT NULL,
`description` VARCHAR(255) NULL DEFAULT NULL,
`manager_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `FK3635851B178516` (`manager_id` ASC),
CONSTRAINT `FK3635851B178516`
FOREIGN KEY (`manager_id`)
REFERENCES `timesheet`.`manager` (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
To be sure of this I tried adding two records with the same manager id and were indeed added, I also tried setting the unique constraint like "#Table(name = "Task",uniqueConstraints = #UniqueConstraint(columnNames =..." but no luck.
So Why is this happening and what's exactly the pros of using #OneToOne annotaion if no application logic is applied to validate this?
Also, Is there any chance that Hibernate is not able to do the DDL generation properly?
(I know that generation of schemas through hibernate is only meant for testing)
In a unidirectional relationship you will get the expected unique constraint if you mark it as "optional=false". You also get it if you set the join column explicitly as unique, of course.
So either
#OneToOne(optional=false)
#JoinColumn(name = "manager_id")
private Manager manager;
or
#OneToOne
#JoinColumn(name = "manager_id", unique=true)
private Manager manager;
So why do you need to mark it as not optional?
My guess is that, when a value is optional, the column can contain many null values, but in many databases this can not be done when a unique constraint is present. You can do it in MySQL though, so maybe the Hibernate generator is not taking the database into account in this case (a bug?).
See a discussion about MySQL handling of nulls here.
I had this issue too and I just needed to add the referenced column so I can get a generated table:
#Entity(name = "news")
public class News extends BaseEntity {
#Column(length = 500)
private String title;
#Column(length = 2000)
private String description;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "file_id", referencedColumnName = "id", unique = true)
private Picture picture;
}

jpa eclipselink Exception [EclipseLink-4002]

Here is an exception I'm getting:
[EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd):
org.eclipse.persistence.exceptions.DatabaseException Internal Exception:
java.sql.SQLSyntaxErrorException: Vergleiche zwischen 'BIGINT' und 'VARCHAR (UCS_BASIC)'
werden nicht unterstützt.
Error Code: 30000
Call: SELECT t1.ID, t1.TEXTINFO FROM COORDINATESLOCATION_INFORMATION t0, TEXTINFORMATION t1
WHERE ((t0.CoordinatesLocation_ID = ?) AND (t1.ID = t0.informationList_ID))
bind => [1 parameter bound]
When I run my application first (without an empty database) everything works. I easily can manage data in all CRUD functions. Later (after an unspecific time or several requests), the exception appears.
This is a tourist information application. There are authors that create tours. One tour contains many locations. One location contains many information. The associations are realised as compositions.
As I changed every association to eager fetch, the exception appears earlier in the workflow.
Here are the code snippets:
#Entity
public class CoordinatesLocation implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String description;
#OneToMany(fetch = FetchType.EAGER)
private List<Information> informationList = new ArrayList<>();
private double lat;
private double lng;
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Information implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
#Entity
public class TextInformation extends Information {
private String textInfo;
}
Plus getter & setter!
Thanks for helping!
With the code you have provided, I can recreate the issue in EclipseLink . The generated script is;
CREATE TABLE COORDINATESLOCATION_INFORMATION (CoordinatesLocation_ID BIGINT
NOT NULL, informationList_ID VARCHAR(255) NOT NULL, PRIMARY KEY
(CoordinatesLocation_ID, informationList_ID))
I have tried explicitly defining the columns of the join table;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable (name = "COORDINATESLOCATION_INFORMATION",
joinColumns = #JoinColumn(name = "CoordinatesLocation_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "Information_ID",referencedColumnName = "ID" ))
private List<Information> informationList;
….but still get VARCHAR(255).
I have removed the
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
From the Information class (so defaulting to SINGLE_TABLE strategy) and this leads to the desired script:
CREATE TABLE COORDINATESLOCATION_INFORMATION (CoordinatesLocation_ID BIGINT NOT
NULL, informationList_ID BIGINT NOT NULL, PRIMARY KEY (CoordinatesLocation_ID,
informationList_ID)).
So, following that somewhat empirical analysis (I’m not sure why ID was mapped to type VARCHAR(255)) you have a couple of choices.
Modify you CREATE TABLE COORDINATESLOCATION_INFORMATION script to include informationList_ID BIGINT.
Change to #Inheritance(strategy = InheritanceType. SINGLE_TABLE). This requires the addition of a discriminator column and may not be in line with your approach.
Note also that with #OneToMany you do not need a join table if you add a Foreign Key in your Information table and define #ManyToOne on the Information side and #OneToMany on the CoordinatesLocation side with the mappedBy Attribute.
This link will give some idea of that. https://en.wikibooks.org/wiki/Java_Persistence
I can't read the localized message in your exception, however I'll guess that it says you can't compare bigint and varchar. I'll further guess that the error is in this part of the SQL: (t1.ID = t0.informationList_ID). Is one of those columns a bigint and the other a varchar?
basically here t0.CoordinatesLocation_ID = ? at sometime your parameter is of type BIGINT and the reqested type is VARCHAR, you should look at this

Hibernate, automatically persist dependant objects

I'm quite new to Hibernate and have been trying to determine what it will do for you and what it requires you to do.
A big one is dealing with an object that has dependants that don't yet exist in the database. For example, I have a Project object that includes a Manufacturer field that accepts a Manufacturer object as its value. In the database I have a products table with a mfr_id column that's a reference to the manufacturers table (a fairly typical unidirectional one-to-many relationship).
If the manufacturer assigned to the product object relates to one that's already in the database then there's no problem. However, when I try to save or update an object that references a manufacturer that hasn't been persisted yet, the operation fails with an exception.
Exception in thread "Application" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
I can of course manually check the state of the product's manufacturer by seeing if it's ID field is null and saving it if it is, but this seems like a cumbersome solution. Does Hibernate support automatically persisting dependants if the dependant in question isn't yet persisted? If so, how do I enable that behaviour? I'm using the version of Hibernate bundled with Netbeans (3.5, I believe) and inline annotations for specifying the mapping behaviour. Below are my product and manufacturer classes, cut down to the parts that handle the dependency. (Product extends Sellable which maps to a sellable table, using JOINED as the inheritance strategy It's that table that contains the primary key that identifies the product)
#Entity
#Table (
name="products",
schema="sellable"
)
public abstract class Product extends Sellable {
private Manufacturer manufacturer;
#ManyToOne (fetch = FetchType.EAGER)
#JoinColumn (name = "mfr_id")
public Manufacturer getManufacturer () {
return this.manufacturer;
}
/**
*
* #param manufacturer
*/
public Product setManufacturer (Manufacturer manufacturer) {
this.manufacturer = manufacturer;
return this;
}
}
The dependant Manufacturer
#Entity
#Table (
name="manufacturers",
schema="sellable",
uniqueConstraints = #UniqueConstraint(columnNames="mfr_name")
)
public class Manufacturer implements Serializable {
private Integer mfrId = null;
private String mfrName = null;
#Id
#SequenceGenerator (name = "manufacturers_mfr_id_seq", sequenceName = "sellable.manufacturers_mfr_id_seq", allocationSize = 1)
#GeneratedValue (strategy = GenerationType.SEQUENCE, generator = "manufacturers_mfr_id_seq")
#Column (name="mfr_id", unique=true, nullable=false)
public Integer getMfrId () {
return mfrId;
}
private Manufacturer setMfrId (Integer mfrId) {
this.mfrId = mfrId;
return this;
}
#Column(name="mfr_name", unique=true, nullable=false, length=127)
public String getMfrName () {
return mfrName;
}
public Manufacturer setMfrName (String mfrName) {
this.mfrName = mfrName;
return this;
}
}
UPDATE: I tried the following from this question, but I still get the transient object exception.
#ManyToOne (fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
I also checked what version of Hibernate is bundled with Netbeans, it's 3.2.5
UPDATE 2: I found that the following appears to apparently work as I wanted.
#ManyToOne (fetch = FetchType.EAGER, cascade = CascadeType.ALL)
However, I suspect that this is not the cascade type I really want. If I delete a product, I don't think deleting its associated manufacturer is the correct action, which is what I believe will happen now.
I did try creating a cascade type that consisted of all the types that were available, but that didn't work either. I got the same exception when I tried to save a product that had an unsaved manufacturer associated with it.
#ManyToOne (fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
I've seen CascadeType.SAVE_UPDATE mentioned in several places, but that mode doesn't seem to be available in the version of Hibernate that comes with Netbeans.
You have to look at cascading operations; this type of operation permits you to manage lifecycle of inner object respect their parent.
#ManyToOne(cascade) if you use Session.persist() operation or org.hibernate.annotations.#Cascade if you use not JPA function Session.saveOrUpdate().
This is just an example, for full doc point here
For your code, if you want to automatically save Manufacturer when saving Project use:
#ManyToOne (fetch = FetchType.EAGER, cascade = {javax.persistence.CascadeType.PERSIST})
#JoinColumn (name = "mfr_id")
public Manufacturer getManufacturer () {
return this.manufacturer;
}
or
#Cascade(CascadeType.PERSIST)

Categories