JPA and non-primitive ElementCollection - java

I've got two tables in my database that look like this, with a foreign key from job_label.job_id to equivalent column in job_record. Additionally, the triple of job_id, label_key, and label in job_record has a unique constraint.
mysql> select * from job_record;
+--------+---------+
| job_id | state |
+--------+---------+
| 1 | success |
| 2 | running |
| 3 | errored |
| 4 | success |
+--------+---------+
mysql> select * from job_label
+--------+-----------+--------+
| job_id | label_key | label |
+--------+-----------+--------+
| 1 | name | job 1 |
| 1 | type | normal |
+--------+-----------+--------+
On the Java class side I have this class:
#Entity
#Table(name = "job_record")
public class JobRecord {
#Id
#Column(name = "job_id")
private String jobId;
#Enumerated(EnumType.STRING)
#Column(name = "state")
private JobState state;
}
I've tried to define a class for job_label that looks something like this:
public class JobLabelRecord {
#Enumerated(EnumType.STRING)
#Column(name = "label_key")
private JobLabelKey key;
#Column(name = "label")
private String label;
}
And then I want a field in JobRecord that provides me all labels for that Job as a List<JobLabelRecord>. However, nothing I've tried works.
I've tried declaring JobLabelRecord as Embeddable with the equivalent field in JobRecord as Embedded. I've tried using ManyToOne and OneToMany mappings, but that fails because there's no Id columns in JobLabelRecord (and nothing I do with those works correctly).
Am I supposed to be using an ElementCollection here instead? I've tried that as well, but without success.
Any ideas here? Thanks!

You can find a simple example for this by searching for "jpa elementcollection separate table", such as JPA Tutorial - JPA ElementCollection CollectionTable Override Example. Applying this example to your problem gives the following:
#Entity
#Table(name = "job_record")
public class JobRecord {
#Id
#Column(name = "job_id")
private Integer jobId;
#Enumerated(EnumType.STRING)
#Column(name = "state")
private JobState state;
#ElementCollection
#CollectionTable(name="job_label",
joinColumns=#JoinColumn(name="job_id"))
private List<JobLabelRecord> labels;
...
#Embeddable
public class JobLabelRecord {
#Enumerated(EnumType.STRING)
#Column(name = "label_key")
private JobLabelKey key;
#Column(name = "label")
private String label;
...
Note also the JobRecord id should probably be an Integer.

Related

Is there any way to put not Base Type in Entity?

I have an entity like this:
#Entity
public Asset extends BaseEntity {
private String name;
private Localization currentLocalization;
private Localization plannedLocalization;
}
It throws Basic attribute type should not be 'Persistence Entity'.
I know that Entity should have Id etc, but what if I dont want to create another table, service, repository just for Localization who should be just a property, not another table.
Edit:
Localization:
#Embeddable
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
public class Localization {
#OneToOne(targetEntity = Floor.class, fetch = FetchType.LAZY)
#JoinColumn(name = "FLOOR_ID")
private Floor floor;
#Min(0)
#Max(1000)
private int xAxis;
#Min(0)
#Max(2400)
private int yAxis;
#Min(0)
#Max(999)
private int zAxis;
}
Here I am using #Embedded with Attribute Overrides like:
#Embedded
private Localization localization;
#Embedded
#AttributeOverrides({
#AttributeOverride(name="floor.id", column = #Column(name = "floor_plannedId"))
})
private Localization localizationPlanned;
but it throws:
Repeated column in mapping for entity: com.mrfisherman.relice.Entity.Asset.AssetEntity column: floor_id (should be mapped with insert="false" update="false")
No matter how I set name in #AttributeOverride
The error is due that Localization is not of any "basic type" that is directly mappable to any database column type. So it should either be an entity and fields of type Localization mapped with #OneToOne or #ManyToOne.
But you do not want another entity so the other option is to make it #Embeddable.
Assume your Localization is like:
#Getter #Setter
public class Localization {
private String str;
private Integer num;
}
You can flatten fields inLocalization to the containing class by annotating it like:
#Embeddable
public class Localization { ...
and in your Asset tell that this field should be embedded:
#Embedded
private Localization currentLocalization;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "str", column = #Column(name = "str2")),
#AttributeOverride(name = "num", column = #Column(name = "num2"))
})
private Localization plannedLocalization;
This would result into a table like:
Table "public.asset"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | bigint | | not null |
num | integer | | |
str | character varying(255) | | |
name | character varying(255) | | |
num2 | integer | | |
str2 | character varying(255) | | |
As you see in the table, it is now flatten. And you also see that because there is two Localization in your Asset you need to do something with the clashing column names.
currentLocalization can use default naming but plannedLocalization cannot because currentLocalization already reserved those column names. So that is why there is a need for attribute override.
Considering all this you might evaluate again whether you create yet another entity and use #OneTOne or #ManyToOne mappings. It depends how compled your Localization is.

How to create ManyToOne relationship using JPA Hibernate annotation?

I had two POJO class named Task and Team.A team can have many tasks correspondingly but each task can be mapped only one team.
// Task POJO
#Entity
#Table(name = "Task")
public class Task extends Domain implements Serializable {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long taskId;
//Mapping To Team
#ManyToOne
#JoinColumn(name="teamId")
private Team teamId;
Team POJO
#Entity
#Table(name = "Team")
public class Team extends Domain implements Serializable {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long teamId;
#Column(nullable = false)
#OneToMany(mappedBy="teamId")
private List<Task> task = new ArrayList<Task>();
The issue is I couldn't Map/Save/Update and which is not affecting my Team Pojo.
//Task Table
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| taskId | bigint(20) | NO | PRI | NULL | auto_increment |
| taskName | varchar(255) | NO | | NULL | |
| teamId | bigint(20) | YES | MUL | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
The Team Table
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| teamId | bigint(20) | NO | PRI | NULL | auto_increment |
| teamName | varchar(255) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
So How do I map these two Pojo classes and affect when saving/deletion of data in the tables.
Thanks in Advance.
I discovered that you have defined #Column at the one to many association property (tasks), I assume it was a type error.
For bi-directional one to many association, updates one side of the relationship, the other side should also get updated, and be in sync. Below shown how the make both entity in sync:
#Entity
#Table(name = "Task")
public class Task extends Domain implements Serializable {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long taskId;
//Mapping To Team
#ManyToOne
#JoinColumn(name="teamId")
private Team team;
public void setTeam(Team team) {
this.team = team;
if (!team.getTasks().contains(this)) {
team.getTasks().add(this);
}
}
}
#Entity
#Table(name = "Team")
public class Team extends Domain implements Serializable {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long teamId;
#Column(nullable = false)
#OneToMany(mappedBy="team")
private List<Task> tasks = new ArrayList<Task>();
public void addTask(Task task) {
this.tasks.add(task);
if (task.getTeam() != this) {
task.setTeam(this);
}
}
}
#OneToMany(mappedBy="teamId", cascade=CascadeType.ALL), try to add this annotation above the member(attribute) task of the class Team
Complete your #manyToOne mapping in the Task entity by adding the referenced column in the Team entity; and also name the member as 'team', not 'teamId' i.e.:
#ManyToOne
#JoinColumn(name="teamId",
referencedColumnName="teamId")
private Team team;
To understand relationship mapping in JPA in general, see this: https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Nested_Collections.2C_Maps_and_Matrices

Multi valued attribute in Hibernate

I have the following entities:
____________________ ____________________
| Activity | | Benefit |
------------------ | |------------------|
| activityId:long |-------------| benefitId: long |
| activity:varchar | | activityId: long |
| .... | | benefit: varchar |
-------------------- -------------------|
Can I map this into Hibernate so I end up with this:
#Entity
class Activity {
#Id
private Long id;
private String activity;
private List<String> benefits;
}
Yes, you can use the #ElementCollection tag.
Here's what your code would look like:
#Entity
#Table(name = "Activity")
class Activity {
#Id
#Column(name="activity_id")
private Long id;
#Column(name = "name")
private String activity;
#ElementCollection
#CollectionTable(
name = "Benefit",
joinColumns = #JoinColumn(name = "activityId")
)
private List<String> benefits;
}
Though I would call the table ActivitiesBenefits instead of Benefit to make it clear that that table will store pairs of activities and benefits. Also, there is no need for a benefitId, since a benefit is a weak entity (it cannot exist without an activity), so you can drop that too.
Reference: https://en.wikibooks.org/wiki/Java_Persistence/ElementCollection
Hi I could find one way in which you could do it. Below is code for same.
#Entity
#Table(name = "Activity")
class Activity {
#Id
#Column(name="activity_id")
private Long id;
#Column(name = "name")
private String activity;
#OneToMany
#Formula("(select CONCAT(benefitId, activityId, benefit) from Benefit b where b.activityId = activity_id)")
private List<String> benefits;
}
I am using a sql returning list of benefits and then concatenate it and store it in our benefits list.
The string in formula is SQL(not HQL) so its column names and not filed member names.

Hibernate: 'Repeated column mapping in entity' vs 'Mixing updatable and non updatable columns in a property is not allowed'

I have legacy db.
+----+ +------------+ +------------+
|Site| |Content | |Program |
+----+ +------------+ +------------+
|id | |id:PK | |siteId:PK,FK|
|name| |siteId:FK | |code:PK |
+----+ |prog_code:FK| |name |
|prog_param | +------------+
+------------+
Table Content have one PK column. siteId columns is not part of PK.
Table Program have two PK column(siteId, code). It is composite PK. this table work as template. Only variable in program is parameter and it is on table Content's prog_param column.
I want to map this table to object like this.
+-----------+ +------------+ +-----------+ +---------------+
|Site | |Program | |ProgramId | |Content |
+-----------+ +------------+ +-----------+ +---------------+
|id:long | |id:ProgramId| |site:Site | |id:long |
|name:String| |name:String | |code:String| |site:Site |
+-----------+ |param:String| +-----------+ |program:Program|
+------------+ +---------------+
but i don't know how to mapping the 'Content' and 'Program' in this complex situation.
So, I have tried to make situation simple.
I have mapped prog_param to Content
+-----------+ +------------+ +-----------+ +-------------------+
|Site | |Program | |ProgramId | |Content |
+-----------+ +------------+ +-----------+ +-------------------+
|id:long | |id:ProgramId| |site:Site | |id:long |
|name:String| |name:String | |code:String| |site:Site |
+-----------+ +------------+ +-----------+ |program:Program |
|programParam:String|
+-------------------+
as code
#Entity
#Table(name = "SITE")
#Getter #Setter
public class Site {
#Id
#Column(name = "SITE")
protected String id;
#Column(name = "NAME")
protected String name;
}
#Entity
#Table(name = "PROGRAM")
#Getter #Setter
public class Program {
#EmbeddedId
protected ProgramId id;
#Column(name = "NAME")
protected String name;
}
#Embeddable
#Getter #Setter #EqualsAndHashCode
public class ProgramId implements Serializable{
#ManyToOne
#JoinColumn(name = "SITE")
protected Site site;
#Column(name = "code")
protected String code;
}
#Entity
#Table(name="CONTENT")
#Getter
#Setter
public class Content {
#Id
#Column(name = "ID")
protected Long id;
#ManyToOne
#JoinColumn(name = "SITEID",referencedColumnName="SITE")
protected Site site;
#OneToOne
#JoinColumns({
#JoinColumn(name="SITEID",referencedColumnName="SITE"),
#JoinColumn(name="PROG_CODE",referencedColumnName="CODE"),
})
protected Program program;
}
But this is not work. Hibernate throw 'Repeated column in mapping for entity' exception.
So I have research some solutions.
Finally I found this 'Repeated column in mapping for entity' Exception throwed when Hibernated can not determine to use which property.
Stackoverflows's solution is set 'insertable=false, updatable=false' to #JoinColumn, so let hibernate can determine to use which property.
I want use Content's property 'site' because it is used very frequently. And Site and Program property is used separately in my application.
So I try to set 'insertable=false, updatable=false' to Content's property 'program'
#OneToOne
#JoinColumns({
#JoinColumn(name="SITEID",referencedColumnName="SITE", insertable=false, updatable=false),
#JoinColumn(name="PROG_CODE",referencedColumnName="CODE"),
})
protected Program program;
This is not work too. 'Mixing updatable and non updatable columns in a property is not allowed' exception is throwed.
So I have set set 'insertable=false, updatable=false' to 'PROG_CODE', of course, Hibernate do not change PROG_CODE when I set different prog_code.
At this point, I have no idea.
Please let me know some idea to solve this situation.
I am having the same issue, two composite keys sharing an attribute fails with "Repeated column in mapping for entity". I think this might be a bug, ref: https://hibernate.atlassian.net/browse/HHH-4582. I am using Hibernate 3.6.4

Hibernate UnUniqueify a column in table

Hibernate UnUniqueify a column in table(Solved)
I want a field set to be non-unique on itself but to be unique in combination with the other field, I got this table with two columns(composite primary keys); id (primary key) and object_proxy_id (primary key), this is exactly what I need but hibernate sets the object_proxy_id to be unique on itself so that value cant be duplicate in the table, and I need this column to accept duplicate values. Because every user has its own object proxy and these proxy's don't have to be necessarily unique.
This is what I want to achieve:
|-------------------------------|
| tbl_object_proxy |
| ------------------------------|
| Id (pk)| object_proxy_id (pk) |
|-------------------------------|
| 1 | 150 -- |
| 1 | 149 |= must be able to be DUPLICATE which is not the case right now.
| 2 | 150 -- |
| 2 | 151 |
|-------------------------------|
Current code:
#Entity
#Table(name = "tbl_user_settings", uniqueConstraints = {#UniqueConstraint(columnNames={"user_id"})})
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Settings implements Serializable
{
#Id
#SequenceGenerator(name="someSequence", sequenceName="SEQ_SOMENAME", allocationSize =1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="someSequence")
#Column(name="id")
private int setting_id;
#OneToOne
private User user;
#ManyToOne
private SomeObject someobject;
#ElementCollection
#CollectionTable(name="tbl_collection_name", joinColumns=
#JoinColumn(name="id"), uniqueConstraints = {#UniqueConstraint(columnNames={"id", "object_proxy_id"})})
#Column(name="SomeObject")
private Set<SomeObject> objectProxy;
/*...constructors and methods...*/
}
Results in:
-- Table schema
|-------------------|
| tbl_user_settings |
|-------------------|
| id |PK <<Unique>>
| user_id |FK reference tbl_user <<Unique>>
| object_id |FK reference tbl_object
|-------------------|
|------------------|
| tbl_object_proxy |
|------------------|
| id |PK reference tbl_user_settings
| object_proxy_id |PK reference tbl_object <<Unique>> BUT I DON'T WANT THIS TO BE UNIQUE ON ITSELF !!!!
|------------------|
EDIT:
The two primary key's in tbl_object_proxy are composite primary key's
I have tried Xeon's solution but it didn't work.
Short answer: replace the #ElementCollection by a #ManyToMany relation with a #JoinTable like this:
#ManyToMany
#JoinTable(
name="tbl_settings_objecteproxy_v2",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn( name = "objectproxy_id"))
private Set<SomeObject> objectproxy;
See "2.2.5.3.2.1. Definition" in Hibernate Annotation Documentation
This results in a same side table but then without the unique constraint.
So now this is possible:
|-------------------------------|
| tbl_object_proxy |
| ------------------------------|
| Id (pk)| object_proxy_id (pk) |
|-------------------------------|
| 1 | 150 -- |
| 1 | 149 |= It works! The unique constraint is gone!
| 2 | 150 -- |
| 2 | 151 |
|-------------------------------|
Detailed answer and cause description:
Somehow the #ElementCollection created a collectiontable with a one to many relation of the referenced key (collection | inverse join) which adds a unique constraint to the key referencing the other side table to reflect the one to many relationship which I didn't want. So I dropped the #ElementCollection and replaced it by a #ManyToMany relation with a #JoinTable annotation. I have also tried to declare the #ManyToMany relation in the #ElementCollection but it kept adding the Unique constraint to the referenced key.
My Settings class does now look like this:
#Entity
#Table(name = "tbl_user_settings", uniqueConstraints = {#UniqueConstraint(columnNames={"user_id"})})
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Settings
{
#Id
#SequenceGenerator(name="someSequence", sequenceName="SEQ_SOMENAME", allocationSize =1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="someSequence")
#Column(name="id")
private int setting_id;
#OneToOne
private User user;
#ManyToOne
private SomeObject someobject;
#ManyToMany
#JoinTable(
name="tbl_settings_objecteproxy_v2",
joinColumns = #JoinColumn(name = "id"),
inverseJoinColumns = #JoinColumn( name = "objectproxy_id"))
private Set<SomeObject> objectProxy;
/*...the rest...*/
}
I had similar issue with articles and categories:
public class ArticleCategoriesEntity {
#EmbeddedId
public ArticleCategoriesIdPk getArticleCategoriesIdPk() {
return articleCategoriesIdPk;
}
public void setArticleCategoriesIdPk(ArticleCategoriesIdPk articleCategoriesIdPk) {
this.articleCategoriesIdPk = articleCategoriesIdPk;
}
...
#Embeddable
public class ArticleCategoriesIdPk implements Serializable {
public ArticleCategoriesIdPk() {
}
public ArticleCategoriesIdPk(Integer articleCategoryIdPk, Integer articleCategoryVersionFk) {
this.articleCategoryIdPk = articleCategoryIdPk;
this.articleCategoryVersionFk = articleCategoryVersionFk;
}
private Integer articleCategoryIdPk;
#Column(name = "article_category_id_pk")
public Integer getArticleCategoryIdPk() {
return articleCategoryIdPk;
}
public void setArticleCategoryIdPk(Integer articleCategoryIdPk) {
this.articleCategoryIdPk = articleCategoryIdPk;
}
private Integer articleCategoryVersionFk;
#Column(name = "article_cat_version_fk")
public Integer getArticleCategoryVersionFk() {
return articleCategoryVersionFk;
}
public void setArticleCategoryVersionFk(Integer articleCategoryVersionFk) {
this.articleCategoryVersionFk = articleCategoryVersionFk;
}
And you need to set Uniqueness of the two columns of embedded PK class

Categories