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;
Related
I have two tables that already exist inside postgres, lets call the Table A and Table B. One column of Table B has a foreign key constraint in that it has to be the primary key of Table A. Thus there is a many-to-one relationship between B and A, where multiple records in Table B correspond to one record of Table A.
The Entity for both these tables are defined as follows.
public class TableA implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "phone_number")
private String phoneNumber;
}
TableB's entity is defined as follows:
public class Shots implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private Long itemId;
#Column(name = "user_id")
private Long userId;
}
Where userId is the foreign key mapping to the primary key user_id in Table A.
These constraints have already been defined in the underlying postgres database, so i didn't consider using the #ManyToOne annotation relationship (still trying to wrap my head around it).
The way i currently handle the case when a foreign key constraint violation occurs is by doing the following:
try {
tableBrepository.save(newShot);
} catch (ConstraintViolationException ex) {
logger.error("Violating foreign key constraint" + ex.getMessage());
}
My question is, is there a better way to check for this violation? Is there anything i can do to generally better structure the foreign key constraint in Spring Data JPA?
Thus there is a many-to-one relationship between B and A, where multiple records in Table B correspond to one record of Table A.
This kind of stuff in JPA entities is handled with #ManyToOne annotation. You usually do not refer to any id field directly but tell JPA what there should be. So in your class TableB (or should I call it... Shots?) should be something like:
#ManyToOne
private TableA tableA;
// and get rid of this
// #Column(name = "user_id")
// private Long userId;
And optionally - so not necessarily - you could have, in your TableA:
#OneToMany
private List<TableB> tableBsOrShouldICallYouShots;
I am not sure what is your actual problem but when setting and referring to id fields directly might cause your difficulties.
Now if you -for example- use repository to find some TableB you can then after that just do
tableB.getTableA()
And when saving you would before that do:
tableB.setTableA(somSortOftableA);
// so not tableB.setUserId(someLongIdFOrtableA);
Now the point is that there is no problem with referential integrity because you do not need to know any IDs and you cannot set any wrong ID. Unless you first need to fetch TableA by id before setting it to TableB but in that case you would still not set any IDs.
TLDR;
I'm using spring boot and jpa.
I want to switch the foreign key of an object, in this case just switching the category of a vehicle.
But when i try to do that hibernate interprets it as if i'm trying to change the primary key of the category object instead of just switching the foreign key and I get this error
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I have an entity Category which i'm using only for categorizing vehicle entity object.
#Entity
public class Category {
#Id
private Long id;
private String name;
}
Here is the Vehicle class which needs to be categorized.
#Entity
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="dish_seq")
private Long id;
private String name;
private Integer price;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.DETACH)
private Category category;
}
Lets say there's 3 categories,
'Sedan'
'Convertible'
'Hatchback'
If i have a car object,
Nissan-PT76, $30000, category: [id:1, name:Sedan]
When i try to change category manually to [id:2, name:Convertible] and persist it, i get
org.hibernate.HibernateException:identifier of an instance of abc.package.mode.Category was altered from 1 to 2
I cannot switch from one existing object to another. I have tried to look this up in the internet but i couldn't find the right keywords to search for this kind of relationship in hibernate, or does it not allow this kind of relationship at all?
Add column reference to your Category field in the Vehicle class
#JoinColumn(name = "category_id", nullable = false)
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;
}
i am trying on many to many relationship, Team member can work on multiple projects and a project can have multiple team member , the table structure is as follows,
create table TBL_PROJECT_ONE(
id integer primary key generated always as identity(start with 12,increment by 3),
name varchar(50)
)
create table TBL_TEAM_MEMBER_ONE(
id integer primary key generated always as identity(start with 7,increment by 5),
name varchar(50),
salary integer
)
create table EMP_PRJ_CADRE(
MEMBER_ID integer references TBL_TEAM_MEMBER_ONE,
PRJ_ID integer references TBL_PROJECT_ONE,
CADRE varchar(10),
constraint PK_001_EMP_TEAM primary key (MEMBER_ID,PRJ_ID)
)
Here i have created a new table just to store the relationship,
Now please follow the Employee entity,
#Entity
#Table(name="TBL_TEAM_MEMBER_ONE")
public class EmployeeEntityFour implements Serializable{
public EmployeeEntityFour(){}
public EmployeeEntityFour(String empName,Integer salary){
...
..
}
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="ID")
private Integer empId;
#Column(name="NAME")
private String empName;
#Column(name="SALARY")
private Integer empSal;
#ElementCollection(fetch= FetchType.LAZY)
#CollectionTable(name="EMP_PRJ_CADRE")
#MapKeyJoinColumn(name="PRJ_ID")
#Column(name="CADRE")
private Map<ProjectEntityOne,String> employeeCadre;
...
..
.
}
Please follow the mapping for Project Entity,
#Entity
#Table(name="TBL_PROJECT_ONE")
public class ProjectEntityOne implements Serializable{
public ProjectEntityOne(){}
public ProjectEntityOne(String name){
this.projectName = name;
}
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="ID")
private Integer projectId;
#Column(name="NAME")
private String projectName;
#ElementCollection(fetch= FetchType.LAZY)
#CollectionTable(name="EMP_PRJ_CADRE")
#MapKeyJoinColumn(name="MEMBER_ID")
#Column(name="CADRE")
private Map<EmployeeEntityFour,String> employeeCadre;
....
..
.
}
In main method testing the code written is as follows,
ProjectEntityOne proj = new ProjectEntityOne("Citi Grand Central");
Map<EmployeeEntityFour,String> cadreMap = new HashMap<EmployeeEntityFour,String>();
cadreMap.put(new EmployeeEntityFour("Murlinarayan Muthu",34000), "Senior Software Engineer");
cadreMap.put(new EmployeeEntityFour("Gopalkrishna Rajnathan",64000), "Software Engineer");
cadreMap.put(new EmployeeEntityFour("Premanna Swaminathan",94000), "Project Manager");
proj.setEmployeeCadre(cadreMap);
em.persist(proj);
but i am getting an error which is
ERROR: 'PROJECTENTITYONE_ID' is not a column in table or VTI 'APP.EMP_PRJ_CADRE'.
When in both the entities i have specified #MapKeyJoinColumn than too i am getting an error as improper column for the third table.
Where i am missing
It somehow worked, i had to do some changes in the code,
first, the edited code in Entity ProjectEntityOne is as follows,
#ElementCollection(fetch= FetchType.LAZY)
#CollectionTable(name="EMP_PRJ_CADRE",joinColumns=#JoinColumn(name="PRJ_ID"))
#MapKeyJoinColumn(name="MEMBER_ID")
#Column(name="CADRE")
private Map<EmployeeEntityFour,String> employeeCadre;
What i have done here is i added #JoinedColumn in #CollectionTable,
Second change i did in Entity EmployeeEntityFour, the change is I removed Map of PorjectEntityOne from it,
in test,
i can save Project with Employee mapping but here all the employees should be already saved one.
i.e. the key of map
Map<EmployeeEntityFour,String> employeeCadre;
should be already persisted
and than we can persist project entity.
On employeeCadre in EmployeeEntityFour you need a #JoinColumn(name="MEMBER_ID") and you would also need a #JoinColumn(name="PRJ_ID") in the ProjectEntityOne employeeCadre.
But, I would not model it this way. First of all you cannot have a bi-directional ElementCollection mapping, and ElementCollection can only be owned by one side. The best solution would be to define an Cadre entity mapping to EMP_PRJ_CADRE table and have a OneToMany to it from both sides, and have it have a ManyToOne to each.
Alternatively you may use a ManyToMany with a MapKeyColumn, but I think you would be better off having an entity.
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.