Short version: I'm trying to create an:
#ElementCollection
private Map<String, Item> items;
, where Item is #Embeddable, and be able to define the column name created in Item table for the map's key. Hibernate version: 5.0.12.Final
Full explanation:
Following is a simplified example that reproduces my problem.
My starting point is a List of #Embeddable.
#Entity
public class Root {
#Id
private Long code;
private String value;
#ElementCollection
private Set<Item> items;
}
#Embeddable
public class Item {
private String keyValue;
private String otherValue;
}
with this mappings hibernate creates the following tables:
create table root (
code int8 not null,
value varchar(255),
primary key (code)
);
create table root_items (
root_code int8 not null,
key_value varchar(255),
other_value varchar(255)
);
alter table root_items
add constraint FK1o7au7f9ccr8144vyfgsr194v
foreign key (root_code)
references root;
So far so good. Next, I'm trying to convert the List to a Map and use Item.keyValue as the map's key:
#Entity
public class Root {
#Id
private Long code;
private String value;
#ElementCollection
private Map<String, Item> items;
}
#Embeddable
public class Item {
private String otherValue;
}
It works, but in root_items table the map's key column is always called items_key and I'm unable to find how to force this name to key_value.
I've tried the following alternatives:
#ElementCollection
#MapKeyJoinColumn(name = "keyValue")
private Map<String, Item> items;
/* #MapKey doesn't work at all.
org.hibernate.AnnotationException: Associated class not found: com.demo.Item
at org.hibernate.cfg.annotations.MapBinder.bindKeyFromAssociationTable(MapBinder.java:116)
*/
#ElementCollection
#MapKey(name = "keyValue")
private Map<String, Item> items;
#ElementCollection
#CollectionTable(joinColumns = #JoinColumn(name = "code"))
#MapKeyJoinColumn(name = "keyValue")
private Map<String, Item> items;
#ElementCollection
#JoinColumn(name = "keyValue")
private Map<String, Item> items;
#ElementCollection
#CollectionTable(joinColumns = #JoinColumn(name = "code"))
#MapKeyJoinColumn(name = "keyValue")
#Column(name = "keyValue")
private Map<String, Item> items;
but the column name is always generated as items_key:
create table root_items (
code int8 not null,
other_value varchar(255),
items_key varchar(255) not null,
primary key (code, items_key)
);
I've also tried all the above combinations keeping keyValue in Item but then I only get and extra column:
create table root_items (
code int8 not null,
key_value varchar(255),
other_value varchar(255),
items_key varchar(255) not null,
primary key (code, items_key)
);
Similar questions I've found but doesn't solve my problem:
#ElementCollection with Map<Entity, Embeddable> where Entity is a field of the Embeddable
Key & value column name overrides when mapping java.util.Map with JPA annotations
Is there anyway to force the column name to some custom value?
EDIT 1:
I've found that if the map key is #Embeddable then I can achieve it.
But I don't think it's a valid solution to achieve this. I would like to keep the map key as String
#Entity
public class Root {
#Id
private Long code;
private String value;
#ElementCollection
private Map<ItemKey, Item> items;
}
#Embeddable
public class ItemKey {
private String keyValue;
}
Is there anyway to force the column name to some custom value when the key is String?
Finally I found it. It's as simple as that:
#Entity
public class Root {
#Id
private Long code;
private String value;
#ElementCollection
#MapKeyColumn(name = "keyValue")
private Map<String, Item> items;
}
I leave the answer in case it may help another one as lost as I. :P
Related
I've got two tables:
CREATE TABLE Checkin (
ID int primary key,
foo varchar(100),
bar varchar(100),
FK_type int foreign key references Type(ID)
)
CREATE TABLE Type (
ID int primary key,
type varchar(100)
)
Since the secondary table only stores labels, I'd like to map the values directly into my entity. I figured it should be possible using #SecondaryTable...
#Table(name = "Checkin")
#SecondaryTable(name = "Type",
pkJoinColumns = #PrimaryKeyJoinColumn(name="FK_type", referencedColumnName = "ID")
)
class Checkin {
#Id
private Integer id;
private String foo;
private String bar;
#Column(name="FK_type", table="Type")
private String type;
}
Unforunately, it would seem that the #SecondaryTable annotation works the other way around, meaning it wants my actual primary data table with the most columns to be the one joining. So I get thrown the error
Invalid column name 'FK_type'.
Is there a way to solve that through different annotations or do I really need to build the whole construct the other way round and have the main entity refer to "Type" and "Chekin" be the secondary table?
You should join Type entity in Checkin:
class Checkin {
#Id
private Integer id;
private String foo;
private String bar;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "type_id", referencedColumnName = "id")
private Type type;
}
Try to correct this:
#Column(name="FK_type", table="Type")
private String type;
to this:
#Column(name="type", table="Type")
private String type;
The table Type just do not have the FK_type column, as I understand you want to use Type.type here.
P.S. You can omit to use referencedColumnName if this is a reference to the PK.
I'm having a hard time trying to figure out how to properly do ORM on a certain database design.
My schema consists of three tables: a user table, a review table, and a vote table. Users can publish reviews for albums, and they can also assign a positive or negative rating to any review. Albums are provided from an external API, so their table is missing from the schema, but their IDs are referenced.
A user primary key simply consists of their username. A review primary key is composed of the reviewer's username, which is a foreign key, and the reviewed album ID. Finally, a vote primary key is composed of the voter's username, again a foreign key, and the voted review's primary key, consisting, as said earlier, of the reviewer's username and the reviewed album ID.
A user can publish a review for each individual album, and also can assign a vote for each individual review.
This is the ER model representing the schema:
To map the entities IDs, I'm using the #IdClass annotation, but I'm not sure I'm headed in the right direction. I also tried using the #EmbeddedId annotation, but the result is the same.
This is what my entities classes look like so far:
#Entity
public class User implements Serializable {
private static final long serialVersionUID = 1;
#Id #Column(name = "username")
private String username;
#Column(unique = true, nullable = false)
private String email;
#Column(name = "password", nullable = false)
private String password;
#Temporal(TemporalType.TIMESTAMP) #Column(name="signUpDate", nullable = false)
private Date signUpDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(ReviewId.class)
public class Review implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "reviewerUsername", referencedColumnName = "username")
private User reviewer;
#Id #Column(name = "reviewedAlbumId")
private Long reviewedAlbumId;
#Column(name = "content", nullable = false, length = 2500)
private String content;
#Column(name = "rating", nullable = false)
private Integer rating;
#Temporal(TemporalType.TIMESTAMP) #Column(name = "publicationDate", nullable = false)
private Date publicationDate;
// empty constructor, getters, setters, equals and hashCode implementations
}
#Entity #IdClass(VoteId.class)
public class Vote implements Serializable {
private static final long serialVersionUID = 1;
#Id #ManyToOne #JoinColumn(name = "voterUsername", referencedColumnName = "username")
private User voter;
#Id #ManyToOne #JoinColumns({
#JoinColumn(name = "reviewerUsername", referencedColumnName = "reviewerUsername"),
#JoinColumn(name = "reviewedAlbumId", referencedColumnName = "reviewedAlbumId")
})
private Review review;
#Column(name = "vote") // #todo add attribute nullable = false
private Boolean vote;
// empty constructor, getters, setters, equals and hashCode implementations
}
These are my ID classes:
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private User reviewer;
private Long reviewedAlbumId;
// empty constructor, getters, setters, equals and hashCode implementations
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private User voter;
private Review review;
// empty constructor, getters, setters, equals and hashCode implementations
}
And here is the content of the MySQL script used to generate the schema:
DROP SCHEMA IF EXISTS albumReviewsDatabase;
CREATE SCHEMA albumReviewsDatabase;
USE albumReviewsDatabase;
CREATE TABLE user (
username VARCHAR(20) PRIMARY KEY,
email VARCHAR(254) NOT NULL UNIQUE,
password CHAR(60) NOT NULL,
signUpDate TIMESTAMP NOT NULL DEFAULT now()
) ENGINE = INNODB;
CREATE TABLE review (
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
content TEXT NOT NULL,
rating SMALLINT UNSIGNED NOT NULL,
publicationDate TIMESTAMP NOT NULL DEFAULT now(),
CHECK (rating >= 0 AND rating <= 10),
PRIMARY KEY (reviewerUsername, reviewedAlbumId),
FOREIGN KEY (reviewerUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
CREATE TABLE vote (
voterUsername VARCHAR(20) NOT NULL,
reviewerUsername VARCHAR(20) NOT NULL,
reviewedAlbumId BIGINT(20) NOT NULL,
vote BOOLEAN NOT NULL,
PRIMARY KEY (voterUsername, reviewerUsername, reviewedAlbumId),
FOREIGN KEY (voterUsername) REFERENCES user(username)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (reviewerUsername, reviewedAlbumId) REFERENCES review(reviewerUsername, reviewedAlbumId)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE = INNODB;
I'm currently using OpenJPA as the persistence provider on a TomEE webprofile instance, and the used JPA version is 2.0.
Clearly I am misunderstating something about JPA's ORM, because when I deploy my application containing those entities I get the following exception:
<openjpa-2.4.2-r422266:1777108 fatal user error> org.apache.openjpa.util.MetaDataException: The id class specified by type "class application.model.Review" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "reviewer"
The exception is thrown because of the Review class mapping, and not the Vote class; however, I am sure that by solving the issue on the Review class, the same will reappear for Vote.
I'd prefer to get away with using the #IdClass annotation instead of #EmbeddedId, but whichever of the two I will end up using is not an issue.
These relationships are "derived identities"; so your ID classes should look like this (note the types of the foreign key fields differ from the types of their corresponding entity fields):
public class ReviewId implements Serializable {
private static final long serialVersionUID = 1L;
private String reviewer; // matches name of #Id attribute and type of User PK
private Long reviewedAlbumId;
// ...
}
public static class VoteId implements Serializable {
private static final long serialVersionUID = 1L;
private String voter; // matches name of #Id attribute and type of User PK
private ReviewId review; // matches name of #Id attribute and type of Review PK
// ...
}
Derived identities are discussed (with examples) in the JPA 2.2 spec in section 2.4.1.
Also, as a side note, #IdClass is a bit Old School while #EmbeddedId is cleaner, eliminating the code duplicated across the entity and its key.
I don't see an example anywhere so I am not sure this is possible. But basically, I am trying to see if I can bind a field in an entity to
Map<Skill,Set<Rating>> ratings;
CREATE TABLE Worker (
ID BIGINT PRIMARY KEY,
);
CREATE TABLE Skill (
ID BIGINT PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE (name)
);
CREATE TABLE WorkerSkillRating (
ID BIGINT PRIMARY KEY,
WorkerID BIGINT NOT NULL,
SkillID BIGINT NOT NULL,
Rating INT,
FOREIGN KEY (WorkerID) REFERENCES Worker (ID),
FOREIGN KEY (SkillID) REFERENCES Skill (ID),
FOREIGN KEY (Rating) REFERENCES Rating (ID)
);
CREATE TABLE Rating (
ID BIGINT PRIMARY KEY,
score TINYINT NOT NULL,
comments VARCHAR(256)
);
Entities
#Entity
public class Skill {
#Id
private Long id;
private String name;
public Skill(String name) {
this();
this.name = name;
}
public Skill() {
this.id = Math.abs( new Random().nextLong());
}
}
#Entity
public class Worker {
#Id
private Long id;
// The open question
public Map<Skill, Set<Rating>> ratings;
}
#Entity
public class Rating {
#Id
private Long id;
private Byte score;
private String comments;
}
According to the JSR-0038 the JPA spec. When using Map, the following combination are just allowed: Basic Type, Entities and Embeddables.
Map<Basic,Basic>
Map<Basic, Embeddable>
Map<Basic, Entity>
Map<Embeddable, Basic>
Map<Embeddable,Embeddable>
Map<Embeddable,Entity>
Map<Entity, Basic>
Map<Entity,Embeddable>
Map<Entity, Entity>
I don’t think there is pretty much deal to have a possible mapping in the way that you want but that is out of the specs and most of the providers follow them, I think that mapping is not very common at all.
"worker has many skills and he may have been given many ratings on a
single skill. "
Then add to the skill class a Set<Ratings>, instead of nested directly in the map as the value of it.
It might not answer your question with the map but...
It looks like your rating table is unnecessary.
You could instead have
CREATE TABLE Worker (
ID BIGINT PRIMARY KEY,
);
CREATE TABLE Skill (
ID BIGINT PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE (name)
);
CREATE TABLE WorkerSkill (
ID BIGINT PRIMARY KEY,
WorkerID BIGINT NOT NULL,
SkillID BIGINT NOT NULL,
score TINYINT NOT NULL,
comments VARCHAR(256)
FOREIGN KEY (WorkerID) REFERENCES Worker (ID),
FOREIGN KEY (SkillID) REFERENCES Skill (ID)
);
Note I moved the rating information to WorkerSkill table.
Then you can map your entities per below
#Entity
public class Skill {
#Id
private Long id;
private String name;
// Getter setters const etc
}
#Entity
public class WorkerSkill {
#Id
private Long id;
private int score;
private String comments;
#ManyToOne
private Skill skill;
#ManyToOne
private Worker worker;
// Getter setters const etc
}
#Entity
public class Worker {
#Id
private Long id;
#OneToMany
public List<WorkerSkill> workerSkills = new ArrayList<>();
// Getter setters const etc
}
Then you can access all worker's skill using worker.getWorkerSkill();
I am new to JPA and Hibernate and I am trying to create an assignment table with an additional column position which is part of the PK which also contains a FK reference.
#Entity
#IdClass(ComponentAssignmentEntityPK.class)
public class ComponentAssignmentEntity implements Serializable {
#Id
private Integer containerID;
private Integer elementID;
#Id
private Integer position;
....
#ManyToOne
#PrimaryKeyJoinColumn(name="CONTAINERID", referencedColumnName = "ID")
public ComponentEntity getContainer() {
return container;
}
}
My Key class looks basically like this
public class ComponentAssignmentEntityPK implements Serializable {
private Integer containerID;
private Integer position;
....
}
However, if I now generate the init script using Hibernate it contains a duplicate definition for the containerid
create table ComponentAssignment (
containerID integer not null,
position integer not null,
...
elementID integer,
container_id integer not null, <===
primary key (containerID, position)
);
What am I doing wrong? I am using Hibernate 4.3.5.Final.
In your entity, you have defined two columns with same name CONTAINERID, one of them should be with different name.
As a result, when table creation script is generated, container_id is defined to make different
create table ComponentAssignment (
containerID integer not null, //refer to PK column name with #Id
position integer not null,
...
elementID integer,
container_id integer not null, //refer to column for ComponentEntity with #ManyToOne
primary key (containerID, position)
);
Assuming that it can be solved by renaming the column name for ComponentEntity like this;
#ManyToOne
#PrimaryKeyJoinColumn(name="container_id", referencedColumnName = "ID")
I have the following database structure:
Table 1 Table 2
Table 3
tid_1 ----(many-to-one)---- tid_1
.... tid_2 ----(one-to-many)---- tid_2
tkey
tvalue
Is there a way to create a class, defined by Table 1, with java.util.Map, associating tkey with tvalue from Table 3? I'm pretty new to Hibernate, and, before asking, I've tried to search and experiment, but got nothing. Any help will be appreciated.
P.S.
If this will not obstruct you, I'd prefer using .hbm.xml style.
You can declare a map with tkey as a key and an entity mapped to Table 3 as a value:
#Entity
public class Table1 {
#ManyToMany
#JoinTable(name = "Table2")
#MapKey(name = "key")
private Map<String, Table3> table3s;
...
}
#Entity
public class Table3 {
#Column(name = "tkey")
private String key;
#Column(name = "tvalue")
private String value;
...
}