JPA with Hibernate 4.x, #ElementCollection, Primary Key - java

I am using JPA with Hibernate 4.x., and postgresql 9.x, but I wonder some problem.
When using #ElementCollection annotation, and I configured Set type embedded field, I expect to generate Primary key of embeddable Collection Table, but it's not working. I can find just one foreign key against owner of relationship. i wonder why do not it generate primary key. This is my test code.
A.java
#Entity
public class A {
#Id
private Long id;
#Column(name = "\"as\"")
private String as;
public String getAs() {
return as;
}
public void setAs(String as) {
this.as = as;
}
#ElementCollection
private Set<B> bs = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<B> getBs() {
return bs;
}
public void setBs(Set<B> bs) {
this.bs = bs;
}
}
B.java
#Embeddable
public class B {
private String col1;
private String col2;
public String getCol1() {
return col1;
}
public void setCol1(String col1) {
this.col1 = col1;
}
public String getCol2() {
return col2;
}
public void setCol2(String col2) {
this.col2 = col2;
}
}
the result is
Hibernate:
create table A (
id int8 not null,
"as" varchar(255),
primary key (id)
)
Hibernate:
create table A_bs (
A_id int8 not null,
col1 varchar(255),
col2 varchar(255)
)
Hibernate:
alter table A_bs
add constraint FK_rcecll1ao3brmwwnsep3iqq3p
foreign key (A_id)
references A
Let me know why it did not generate primary key?
Thanks in advance~

Because #ElementCollection is used for mapping collections of simple elements, which is not the same as entities. The only thing they need is a reference to owning entity which is properly generated by Hibernate (column A_id and foreign key (A_id) references A). Take a look at this post for more information.
If you really need a primary key in the embeddable object, consider making it a proper entity.

To generate a primary key for an embeddable collection like Set need to mark all columns of embeddable class with annotation #Column(nullable = false), due to requirement that nullable fields could not be a part of composite primary key

Related

How to avoid duplicate insertions of "many" entity in one-to-many relationship when using a unique constraint in eclipselink/JPA

I am working on a large codebase using Spring MVC with EclipseLink 2.5.2 on a mysql database. The database and its structure are created directly, not through any code-first approach. My problem concerns 2 tables in a one-to-many relationship.
CREATE TABLE ROLE (
ID BIGINT(20) PRIMARY KEY,
-- OTHER FIELDS --
);
CREATE TABLE ROLE_DOMAIN (
ID BIGINT(20) PRIMARY KEY,
ROLE_ID BIGINT(20) NOT NULL,
DOMAIN VARCHAR(255) NOT NULL
-- OTHER FIELDS --
);
ALTER TABLE ROLE_DOMAIN ADD CONSTRAINT FK_ROLE_DOMAIN_ROLE_ID FOREIGN KEY (ROLE_ID) REFERENCES ROLE_BASE (ID) ON DELETE CASCADE;
ALTER TABLE ROLE_DOMAIN ADD CONSTRAINT UQ_ROLE_DOMAIN_ROLE_ID_DOMAIN UNIQUE (ROLE_ID, DOMAIN);
And in java, this is how I've got the two entities configured.
#Entity
public class Role {
private Long id;
private Set<RoleDomain> roleDomains = new HashSet<>();
#Id
#TableGenerator(name = "ROLE.ID", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "ROLE.ID")
public Long getID() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "ROLE_ID", referencedColumnName = "ID", insertable = false, updatable = false)
public Set<RoleDomain> getRoleDomains() {
return roleDomains;
}
public void setRoleDomains(Set<RoleDomain> roleDomains) {
this.roleDomains = roleDomains;
}
}
#Entity
#Table(name = "ROLE_DOMAIN")
public class RoleDomain {
private Long id;
private Long roleId;
private String domain;
#Id
#TableGenerator(name = "ROLE_DOMAIN.ID", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "ROLE_DOMAIN.ID")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "ROLE_ID", nullable = false)
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
#Column(name = "DOMAIN", length = 255)
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
}
Say that in this table structure, I already have a record in ROLE and a record in ROLE_DOMAIN that references it, translating to a Role object named myRole containing the RoleDomain in roleDomains.
Now, when I add a new RoleDomain and save using a spring data repository like this:
myRole.add(new RoleDomain("some string"));
roleRepository.save(myRole);
I get an exception for a duplicate insert violating my unique constraint on ROLE_ID and DOMAIN in the database.
[EL Warning]: 2020-10-22 14:53:22.405--UnitOfWork(994047815)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.8.v20190620-d6443d8be7): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '198732-some string' for key 'UQ_ROLE_DOMAIN_ROLE_ID_DOMAIN'
Error Code: 1062
Call: INSERT INTO ROLE_DOMAIN (ID, DOMAIN, ROLE_ID) VALUES (?, ?, ?)
bind => [27, some other string, 198732]
The weirdest thing about this problem is that if I remove the unique constraint from the database (Note: keeping the java annotation configuration EXACTLY the same. Literally just "DROP CONSTRAINT..." in the db) then the save call works just fine. It doesn't create duplicates in ROLE_DOMAIN. It does exactly what it's supposed to, just adds the new record to ROLE_DOMAIN.
I don't understand how a unique constraint in the db would cause eclipselink to act this inconsistently. Do I have something configured wrongly? Thanks.
EDIT:
I have just now tried replacing the #Table annotation on the RoleDomain class with this:
#Table(name = "ROLE_DOMAIN", uniqueConstraints =
#UniqueConstraint(columnNames = {"ROLE_ID", "DOMAIN"}))
It didn't change anything.
The issue with your constraint is that EclipseLink orders statements for batching, putting deletes last - this is to give you a chance to clean up other constraints, to modify existing rows before rows get deleted. This can be changed so that deletes are issued first using the setShouldPerformDeletesFirst method on the UnitOfWork. As this is native api, you will have to unwrap the EntityManager to get at it, using
em.unwrap(org.eclipse.persistence.sessions.UnitOfWork.class)
if you are in a transaction. This will only be set for the UnitOfWork within this EntityManager, so if you need it everywhere always, you will want to have a session listener with your own session adaptor class to listen for postAcquireUnitOfWork and call setShouldPerformDeletesFirst on it.

JPA Entitties, how to join tables

I have three tables
CREATE TABLE "ingredient" (
"id" INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY,
"ingredient" VARCHAR(50) NOT NULL
);
CREATE TABLE "pizza" (
"id" INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY,
"pizza" VARCHAR(50) NOT NULL
);
CREATE TABLE "pizza_structure" (
"pizza_id" INT NOT NULL,
"ingredient_id" INT NOT NULL,
"amount" INT NOT NULL
);
how to join them, to get Pizzas structure as a Map
#Entity
#Table(name = "ingredient")
public class Ingredient{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Ingredient() {
}
}
#Entity
#Table(name = "pizza")
public class Pizza {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany ????
private Map<Ingredient, Integer> pizzaStructure;
public Pizza() {
}
public Pizza(String name, Map<Long, Integer> pizzaStructure) {
this.name = name;
this.pizzaStructure = pizzaStructure;
}
}
do I need to create #Embeddable class PizzaStructure, if yes when how to use it?
now I'm getting an error
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class:
how to join them, to get Pizzas structure as a Map
It seems to look like this:
#ElementCollection
#CollectionTable(name = "pizza_structure", joinColumns = {#JoinColumn(name = "pizza_id")})
#Column(name = "amount")
#MapKeyJoinColumn(name = "ingredient_id")
private Map<Ingredient, Integer> pizzaStructure;
do I need to create #Embeddable class PizzaStructure
No.
More info is here: Hibernate User Guide - Maps.
Note that table pizza_structure should have foreign keys to pizza and ingredient tables and also unique constrain of pizza_id and ingredient_id, like this (it's postgresql dialect):
create table pizza_structure
(
pizza_id ... constraint fk_structure_pizza references pizza,
ingredient_id ... constraint fk_structure_ingredient references ingredient,
amount ...,
constraint pizza_structure_pkey primary key (pizza_id, ingredient_id)
);
You have a manyToMany relationship between pizza and ingredient and an additional column in your relationship.
I found a similar question here: JPA 2.0 many-to-many with extra column
(I would comment, but i do not have enough reputation.)

hibernate mapping 3 entities to one join table

I have 3 tables skill_checklist, skill_group, skill_master and a mapping table skill_checklist_group_master
The association is as
checklist <-- many to many --> Group
Group <-- many to many --> skills
Can some one tell me how to manage this 3 table association in hibernate with annotations?
Below are my table structures
CREATE TABLE public.skill_checklist
(
id bigint NOT NULL DEFAULT nextval('skill_checklist_id_seq'::regclass),
name character varying(256) NOT NULL,
description character varying(500) NOT NULL,
CONSTRAINT "PK_SKILL_CHECKLIST_ID" PRIMARY KEY (id)
)
CREATE TABLE public.skill_group
(
id bigint NOT NULL DEFAULT nextval('skill_group_id_seq'::regclass),
name character varying(256) NOT NULL,
description character varying(500) NOT NULL,
CONSTRAINT "PK_SKILL_GROUP_ID" PRIMARY KEY (id)
)
CREATE TABLE public.skill_master
(
id bigint NOT NULL DEFAULT nextval('skill_master_id_seq'::regclass),
name character varying(256) NOT NULL,
description character varying(500) NOT NULL,
CONSTRAINT "PK_SKILL_MASTER_ID" PRIMARY KEY (id)
)
CREATE TABLE public.skill_checklist_group_master
(
checklist_id bigint NOT NULL,
group_id bigint NOT NULL,
skill_id bigint NOT NULL,
group_order bigint NOT NULL,
skill_order bigint NOT NULL,
CONSTRAINT "FK_MAP_CHECKLIST_CHECKLIST_ID" FOREIGN KEY (checklist_id)
REFERENCES public.skill_checklist (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "FK_MAP_GROUP_GROUP_ID" FOREIGN KEY (group_id)
REFERENCES public.skill_group (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "FK_MAP_SKILL_SKILL_ID" FOREIGN KEY (skill_id)
REFERENCES public.skill_master (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I don't have experience with hibernate so coding examples will definately help me here. Using latest version of hibernate.
With Hibernate you can define every datatable in a entity. Every entity has his own annotations for column.
Here is a example..
import javax.persistence.*;
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id #GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "salary")
private int salary;
public Employee() {}
public int getId() {
return id;
}
public void setId( int id ) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName( String first_name ) {
this.firstName = first_name;
}
public String getLastName() {
return lastName;
}
public void setLastName( String last_name ) {
this.lastName = last_name;
}
public int getSalary() {
return salary;
}
public void setSalary( int salary ) {
this.salary = salary;
}
}
How you can see every entity has his owns annotations and his owns methods for get and set data in his columns..
If you use eclipse or netbeans, they has his own plugins for convert datatables in entities automatically..

null not allowed for column in join table with one entity extending another

I have a situation where I have an entity VCenterDistributedVirtualPortgroup which extends VCenterNetwork and both entities are in a OneToMany relationship inside the entity VCenterFolder. I'm getting the following error:
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "NETWORK_TYPE"; SQL statement:
insert into folder_network (folder_type, folder_val, distributedVirtualPortgroups_type, distributedVirtualPortgroups_val) values (?, ?, ?, ?) [23502-182]
VCenterNetwork:
#Entity
#Embeddable
#Table(name="network")
#DiscriminatorColumn(name="discriminator")
#DiscriminatorValue("Network")
public class VCenterNetwork
{
#Transient
private Network network;
#Transient
private static Map<MOR, VCenterNetwork> networkMap = new TreeMap<MOR, VCenterNetwork>();
#EmbeddedId
private MOR id;
public MOR getId() {return this.id;}
public void setId(MOR id) {this.id = id;}
...
}
VCenterDistributedVirtualPortgroup:
#Entity
#Table(name="distributedvirtualportgroup")
#DiscriminatorColumn(name="discriminator")
#DiscriminatorValue("DistributedVirtualPortgroup")
public class VCenterDistributedVirtualPortgroup extends VCenterNetwork
{
#Transient
private DistributedVirtualPortgroup distributedVirtualPortgroup;
#Transient
private static Map<MOR, VCenterDistributedVirtualPortgroup> distributedVirtualPortgroupMap = new TreeMap<MOR, VCenterDistributedVirtualPortgroup>();
...
}
VCenterFolder:
#Entity
#Table(name="folder")
public class VCenterFolder
{
#Transient
private Folder folder;
#Transient
private static Map<MOR, VCenterFolder> folderMap = new TreeMap<MOR, VCenterFolder>();
#EmbeddedId
private MOR id;
public MOR getId() {return this.id;}
public void setId(MOR id) {this.id = id;}
#Embedded
#OneToMany(cascade=CascadeType.ALL)
private List<VCenterNetwork> network = new ArrayList<VCenterNetwork>();
public List<VCenterNetwork> getNetwork() {return this.network;}
public void getvirtualNetwork(List<VCenterNetwork> network) {this.network = network;}
#Embedded
#OneToMany(cascade=CascadeType.ALL)
private List<VCenterDistributedVirtualPortgroup> distributedVirtualPortgroups = new ArrayList<VCenterDistributedVirtualPortgroup>();
public List<VCenterDistributedVirtualPortgroup> getDistributedVirtualPortgroups() {return this.distributedVirtualPortgroups;}
public void setDistributedVirtualPortgroups(List<VCenterDistributedVirtualPortgroup> distributedVirtualPortgroups){this.distributedVirtualPortgroups = distributedVirtualPortgroups;}
....
}
This results in a join table that looks like the following:
FOLDER_NETWORK
FOLDER_TYPE - VARCHAR(255) NOT NULL
FOLDER_VAL - VARCHAR(255) NOT NULL
NETWORK_TYPE - VARCHAR(255) NOT NULL
NETWORK_VAL - VARCHAR(255) NOT NULL
DISTRIBUTEDVIRTUALPORTGROUP_TYPE - VARCHAR(255) NOT NULL
DISTRIBUTEDVIRTUALPORTGROUP_TYPE - VARCHAR(255) NOT NULL
I'm new to JPA, but my guess is that a join table is needed for both VCenterFolder-VCenterNetwork and VCenterFolder-VCenterDistributedVirtualPortgroup, but since VCenterDistributedVirtualPortgroup extends VCenterNetwork, it's building one join table with both relations but using a different type/val pair for each relation. I would assume that only one pair will be used at a time with the other being null. That seems to be a problem. It would seem to me that the fields should either be nullable or the two sets of type/val pairs should be merged into one.
I assume there is some way round this, but I sure don't know what it is.
MOR happens to have the members type & val, thus the names in the join table.
I solved the problem by adding the folloing JoinTable on both the network and distributedVirtualPortgroups fields:
#JoinTable
(
name="FOLDER_NETWORK",
joinColumns= {#JoinColumn(name="TYPE", referencedColumnName="TYPE"), #JoinColumn(name="VAL", referencedColumnName="VAL")},
inverseJoinColumns= {#JoinColumn(name="NETWORK_TYPE", referencedColumnName="TYPE"), #JoinColumn(name="NETWORK_VAL", referencedColumnName="VAL")}
)

Hibernate Foreign Key Syntax

I am trying to learn hibernate. I have a movie table with a foreign key to a genre table. Each movie is assigned to a single genre. Each genre may be assigned to many movies.
Here are the table definitions:
CREATE TABLE `movie` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(64) NOT NULL,
`genre_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `fk_genre` (`genre_id`),
CONSTRAINT `fk_genre` FOREIGN KEY (`genre_id`) REFERENCES `genre` (`id`) ON UPDATE CASCADE,
)
CREATE TABLE IF NOT EXISTS `genre` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
)
For code I have
#Entity
public class Movie implements java.io.Serializable {
private static final long serialVersionUID = 1;
private Integer id;
private String title;
private Genre genre;
...
#ManyToOne
public Genre getGenre() {
return genre;
}
Also
#Entity
public class Genre implements java.io.Serializable {
private static final long serialVersionUID = 1;
private Integer id;
private String name;
#Id
#GeneratedValue
#Column(name = "id")
public Integer getId() {
return this.id;
}
Then the select generated by hibernate looks like
select
movie0_.id as column1_4_,
movie0_.genre_id as genre11_4_,
movie0_.title as title4_
from
Movie movie0_
And this not right as there's no reference to the genre table. The correct query should have a join with the genre table. More like
select
movie0_.id as column1_4_,
genre.name as genre11_4_,
movie0_.title as title4_
from
Movie movie0_, Genre genre
where
movie0_.genre_id = genre.id;
I'm a little bit of a loss as to what I'm doing wrong. Should the many to one annotation be in the Genre class instead of the Movie class? Or do you see anything else that I'm doing wrong?
Based on the advise below, Movie now has
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(id).append(" ");
sb.append(title).append(" ");
this.getGenre(); //new
sb.append(genre.getName());
return sb.toString();
}
#ManyToOne(fetch=FetchType.EAGER) //new
public Genre getGenre() {
return genre;
}
And the way I'm loading Movie is through
public static void main(String[] args) {
SessionFactory sf = HibernateUtil.getSessionFactory();
Session session = sf.openSession();
List<Movie> movies = session.createQuery("from Movie").list();
for (Movie movie : movies) {
System.out.println(movie.toString());
}
session.close();
}
What I'm seeing is that even though I have the eager load and I'm explicitly saying getGenre in toString, no query is generated and I'm just getting a null back.
When you use HQL syntax (e.g. createQuery("from Movie")), then Hibernate/JPA will only fetch the Genre entity when you call getGenre() on your Movie object. This is called "lazy fetching". When the method is called, Hibernate will issue another query to fetch the Genre.
Note that HQL queries ignore the FetchType on your annotations - HQL is used to tell Hibernate exactly what to do, rather than using the hints in the annotations.
To make it fetch the Genre in the same query as the Movie, you need to tell it to:
createQuery("from Movie m join fetch m.genre")
try this:
Moive side:
#ManyToOne
#JoinColumn(name = "genre_id")
public Genre getGenre() {..}
and on the other side (genre):
#OneToMany(mappedBy="genre")
List/Set getMovies(){..}
then you can from a movie object movie.getGenre() get Genre.

Categories