Issue in persisting entity with #EmbeddedId and #ElementCollection in Postgres using Hibernate - java

I want to perform CRUD operation over Postgres 9 using Hibernate.
Entity:
#Entity
#Table(name = "MESSAGE_HISTORY_RECORD")
public class MessageHistoryRecord {
#EmbeddedId
private MessageCompoundKey compoundKey;
#Column
private String responseChannel;
#ElementCollection
private List<Trace> traces;
#Column
private byte[] payload;
//getters and setters
}
Composite Id entity:
#Embeddable
public class MessageCompoundKey implements Serializable {
private static final long serialVersionUID = 9084329307727034214L;
#Column
private String correlatedMsgId;
#Column
private String messageId;
#Column
private String endpointId;
//getters and setters
}
ElementCollection Entity:
#Embeddable
public class Trace implements Serializable{
private static final long serialVersionUID = 9084329307727034214L;
private Long timestamp;
private String description;
//getters and setters
}
I am using hibernate.hbm2ddl.auto=update to create schema for me.
It created tables for me:
CREATE TABLE "public"."message_history_record"
(
correlatedmsgid varchar(255) NOT NULL,
endpointid varchar(255) NOT NULL,
messageid varchar(255) NOT NULL,
payload bytea,
responsechannel varchar(255),
CONSTRAINT message_history_record_pkey PRIMARY KEY (correlatedmsgid,endpointid,messageid)
)
;
CREATE UNIQUE INDEX message_history_record_pkey ON "public"."message_history_record"
(
correlatedmsgid,
endpointid,
messageid
)
;
CREATE TABLE "public"."messagehistoryrecord_traces"
(
messagehistoryrecord_correlatedmsgid varchar(255) NOT NULL,
messagehistoryrecord_endpointid varchar(255) NOT NULL,
messagehistoryrecord_messageid varchar(255) NOT NULL,
description varchar(255),
timestamp bigint
)
On persisting any object, I did not find any entry in messagehistoryrecord_traces table.
Hibernate properties:
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.connection.url=jdbc:postgresql://192.xx.xx.xx:5432/testdb
hibernate.connection.username=***
hibernate.connection.password=****
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.connection.pool_size=10
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update
Persist sql :
Hibernate: insert into MESSAGE_HISTORY_RECORD (payload, responseChannel, correlatedMsgId, endpointId, messageId) values (?, ?, ?, ?, ?)

According to your configuration the defaults should apply for the table name, column names and join column names of the collection table. These defaults are constructed as follows:
Table name: name of the referencing entity, appended with an underscore and the name of the entity attribute that contains the element colletion ( MessageHistoryRecord_traces)
Join column: name of the referencing entity, appended
with an underscore and the name of the primary key column of the entity table.
This second case is only allowed if you have a single primary key field in the parent entity which is not the case in your case. So you have specify the join column yourself as follows (I renamed the collection table name and foreign key column names because they are too long for my database system):
#ElementCollection
#CollectionTable(name = "mhr_traces",
joinColumns={#JoinColumn(name="mhr_correlatedmsgid", referencedColumnName="correlatedmsgid"),
#JoinColumn(name="mhr_endpointid", referencedColumnName="endpointid"),
#JoinColumn(name = "mhr_messageid", referencedColumnName = "messageid")})
private List<Trace> traces = new ArrayList<>();
And one more thing: you have to implement the equals() and hashCode() methods for the primary key class if you haven't done yet (they are not visible in your post).
Your table creation script is also missing the foreign key definitino (add them manually if they are not generated automatically):
CONSTRAINT mrFK FOREIGN KEY (mhr_correlatedmsgid, mhr_endpointid, mhr_messageid) REFERENCES MESSAGE_HISTORY_RECORD (correlatedmsgid,endpointid,messageid)
Adjust it matching to your database syntax (I don't know PostgreSQL)
With these adjustments everything works for me; indeed on an Oracle database system and EclipseLink as persistence provider. I think it is not implementation specific

Did you add anything to your traces list or it was empty??
It is working for me with postgresql without any tweaks. With hbm2ddl.auto set to update, hibernate created the tables and foreign key relationship between them as well. Here is the sample code I used :
public class App
{
public static void main( String[] args )
{
System.out.println("Maven + Hibernate + Postgresql");
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
MessageCompoundKey cKey = new MessageCompoundKey();
cKey.setCorrelatedMsgId("correlatedMsgId_2");
cKey.setEndpointId("endpointId_2");
cKey.setMessageId("messageId_2");
MessageHistoryRecord record = new MessageHistoryRecord();
record.setResponseChannel("ArsenalFanTv");
List<Trace> traces = new ArrayList<>();
Trace t1 = new Trace();
t1.setDescription("description_1");
t1.setTimestamp(System.currentTimeMillis());
traces.add(t1);
Trace t2 = new Trace();
t2.setDescription("description_2");
t2.setTimestamp(System.currentTimeMillis());
traces.add(t2);
record.setCompoundKey(cKey);
record.setTraces(traces);
session.save(record);
session.getTransaction().commit();
}
}
and my configuration file (hibernate.cfg.xml) is as follows :
<hibernate-configuration>
<session-factory>
<!-- <property name="hibernate.bytecode.use_reflection_optimizer">false</property> -->
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
<property name="hibernate.connection.password">****</property>
<property name="hibernate.connection.url">jdbc:postgresql://127.0.0.1:5432/testdb</property>
<property name="hibernate.connection.username">****</property>
<property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping class="com.skm.schema.MessageHistoryRecord"></mapping>
</session-factory>
</hibernate-configuration>

I was using StatelessSession instead of session. As per the documentation:
A stateless session does not implement a first-level cache nor interact with any second-level cache, nor does it implement transactional write-behind or automatic dirty checking, nor do operations cascade to associated instances. Collections are ignored by a stateless session. Operations performed via a stateless session bypass Hibernate's event model and interceptors. Stateless sessions are vulnerable to data aliasing effects, due to the lack of a first-level cache.
More details can be found on this thread.
After using Session instead of StatelessSession, it worked.

Related

JPA: How can an #Embeddable object get a reference back to its owner, but the #Embeddable is in a lazy collection?

I know that if you want to reference back from #Embeddable to its parent you can set the parent "manually" in the setter and use #Access(AccessType.PROPERTY) for this embedded field as stated in this answer, but what if this embedded element is mapped in a collection, which is lazy loaded?
Actually not sure whether this is an issue, if not "manually" reference back from #embeddable to its parent, everything is fine.
#CollectionTable.JoinColumns() is used to set the foreign key columns of the collection table which reference the primary table of the entity, which means that once set this optional property, there is no necessary to "manually" reference back from #embeddable to its parent.
Use your case as example:
#Entity
public class Image {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
....
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "COMPUTERS", joinColumns = #JoinColumn(name = "ID_IMAGE"))
private List<Computer> computers;
}
#Embeddable
public class Computer {
#Column
private String ipAddress;
*****//This idImage field is not necessary
#Column(name = "ID_IMAGE", insertable = false, updatable = false)
private Long idImage;*****
}
Once comment out the field idImage and its #Column annotation, the generated SQL is:
create table IMAGES (
id bigint not null,
Name_Image varchar(255),
primary key (id)
)
create table COMPUTERS (
ID_IMAGE bigint not null,
ipAddress varchar(255)
)
alter table COMPUTERS
add constraint FKl1ucm93ttye8p8i9s5cgrurh
foreign key (ID_IMAGE)
references IMAGES
If "manually" declare the join column in the embeddable class, although the DDL are the same, the embeddable object will contain one extra field "imageId", which will cause the JDBC call parameter out of index when executing the INSERT operation.

One to one relationship select query fetches data once first time only

I'm trying to apply one to one relationship between two entities
first entity:
Video and OrganzationVideo every OrganizationVideo has one video entity
So I did the following
first organization_video table
CREATE TABLE `organization_video` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
video table
CREATE TABLE `video` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT NULL,
// rest of table contents
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
then I added constraint in organization_video table
CONSTRAINT `FK_organization_video` FOREIGN KEY (`id`) REFERENCES `video` (`id`)
Then generated entities
Video.java
#Entity
#Table(name = "video")
public class Video extends Persistable<Long> {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "video")
private OrganizationVideo organizationVideo;
\\ rest of video contetns
}
OrganizationVideo.java
#Entity
#Table(name = "organization_video")
public class OrganizationVideo extends Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#JoinColumn(name = "id", referencedColumnName = "id")
#OneToOne(optional = false)
private Video video;
\\ rest of organziation video contents
}
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="Default_Persistence_Unit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<non-jta-data-source>jdbc/defaultDataSource</non-jta-data-source>
<properties>
<property name="hibernate.transaction.auto_close_session" value="false"/>
<property name="hibernate.max_fetch_depth" value="0"/>
<property name="hibernate.connection.release_mode" value="auto"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.cglib.use_reflection_optimizer" value="true"/>
</properties>
</persistence-unit>
</persistence>
everything worked perfectly when persisting objects
the issue is with the fetching query
StringBuilder queryStr = new StringBuilder("select v from Video v where v.organizationVideo is null");
Query query = getEntityManager().createQuery(queryStr.toString());
return query.getResultList();
The weird behavior is that this query fetches data once then doesn't fetch any data tried to use #PrimaryKeyJoinColumn and changed id generation type with no luck but indeed there is something wrong it's strange to see data once first time when I tried to remove is null from query it fetches data but with null value assigned to OrganizationVideo then why the query doesn't work except at first time.
You will have to check the SQL that is generated for the query, but I'm guessing that since the ID is the foreign key and it is autogenerated, inserting entities causes problems with your query. Try fixing your model first:
Either the OrganizationVideo ID needs to be set by the video relationship (if using JPA 2.0):
#Entity
#Table(name = "organization_video")
public class OrganizationVideo extends Persistable<Long> {
#Id
#JoinColumn(name = "id", referencedColumnName = "id")
#OneToOne(optional = false)
private Video video;
\\ rest of organziation video contents
}
That, or you make one of the ID mappings within OrganizationVideo insertable=false, updatable=false. if you do this, then you must set the other fields with in OrganizationVideo within the application yourself, and only after the Video instance has an ID value assigned. Since you are using identity, this means you would have to persist the vidoe, flush, and then use that value within the OrganizationVideo before it can be persisted.
It would be easier for your model if you could just add a foreign key to the OrganizationVideo table and use that to reference the Video ID.

hibernate: objects created in wrong order?

I just switched from XML mapping to annotations and had to realize that my serialization class does not work any more!
I hope you can help me to find out the reason :)
I have a School class that contains an Address
#Entity
#Table(name="schools")
public class School {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name="address_id", nullable=true)
private Address address;
...
}
the Address class:
#Entity
#Table(name="addresses")
public class Address {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name="street", nullable=false, updatable=true)
private String street; // with nr
...
}
I try to serialize like this:
sessionFactory = MyFactory.getSessionFactory();
session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
School mbs = new School("interesting school");
Address a = new Address("garden street 5","12345", "somewhere");
mbs.setAddress(a);
session.save(mbs);
tx.commit();
This worked before with XML, but now doesn't..
The first Hibernate query action visible on the console is:
Hibernate: insert into schools (address_id, layout_id, name) values (?, ?, ?)
Therefore an error occurs: ERROR: Column 'address_id' cannot be null
How can I fix this?
.
Those are the tables:
CREATE TABLE schools(
id INTEGER NOT NULL AUTO_INCREMENT,
name CHAR(50),
address_id INTEGER NOT NULL,
layout_id INTEGER NOT NULL,
CONSTRAINT fk_address FOREIGN KEY (address_id) REFERENCES addresses(id),
CONSTRAINT fk_layout FOREIGN KEY (layout_id) REFERENCES layout_headers(id),
PRIMARY KEY (id)
);
CREATE TABLE addresses(
id INTEGER NOT NULL AUTO_INCREMENT,
street CHAR(55),
zip CHAR(6),
city CHAR(60),
CONSTRAINT addr_pk PRIMARY KEY (id)
);
In my hibernte.cfg.xml I have the following:
<hibernate-configuration>
<session-factory>
...
<mapping class="creator.models.school.Address" />
<mapping class="creator.models.school.Report" />
<mapping class="creator.models.school.School" />
<mapping class="creator.models.school.SchoolClass" />
...
</session-factory>
</hibernate-configuration>
You should specify cascade property of #ManyToOne annotation on address field of School class to save address before school. For example, CascadeType.ALL

Invalid insert order when use #Inheritance JPA attribute

I have Eclipselink persistence provider tuned on DB2 DB. Where is 3 tables which simplified definition are listed below:
CREATE TABLE root
(
id CHAR(32) NOT NULL PRIMARY KEY,
rec_type VARCHAR(20)
);
CREATE TABLE derived
(
id CHAR(32) NOT NULL PRIMARY KEY,
...
);
ALTER TABLE derived ADD CONSTRAINT fk_derived_to_root FOREIGN KEY (id) REFERENCES root(id);
CREATE TABLE secondary
(
derived_id NOT NULL PRIMARY KEY,
...
);
ALTER TABLE secondary ADD CONSTRAINT fk_secondary_to_derived FOREIGN KEY (derived_id) REFERENCES derived(id);
Java entity classes for these entities are listed below,
RootEntity:
#javax.persistence.Table(name = "ROOT")
#Entity
#DiscriminatorColumn(name = "REC_TYPE")
#Inheritance(strategy = InheritanceType.JOINED)
public class RootEntity {
private String id;
#javax.persistence.Column(name = "ID")
#Id
#GeneratedValue(generator = "system-uuid")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
private String principalType;
#Column(name = "PRINCIPAL_TYPE")
public String getPrincipalType() {
return principalType;
}
public void setPrincipalType(String principalType) {
this.principalType = principalType;
}
...
}
DerivedEntity:
#javax.persistence.Table(name = "DERIVED")
#Entity
#DescriminatorValue("DERIVED")
public class DerivedEntity extends RootEntity {
private SecondaryEntity secondaryEntity;
#OneToOne(mappedBy = "derived_id")
public SecondaryEntity getSecondaryEntity() {
return secondaryEntity;
}
public void setSecondaryEntity(SecondaryEntity secondaryEntity) {
this.secondaryEntity = secondaryEntity;
}
...
}
I see no derived table insertion in the test logs:
--INSERT INTO ROOT (ID, REC_TYPE) VALUES (?, ?)
bind => [241153d01c204ed79109ce658c066f4c, Derived]
--INSERT INTO SECONDARY (DERIVED_ID, ...) VALUES (?, ...)
bind => [241153d01c204ed79109ce658c066f4c, ...]
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.ibm.db2.jcc.am.fo: DB2 SQL Error: SQLCODE=-530, SQLSTATE=23503, SQLERRMC=SCHEM.SECONDARY.FK_SECONDARY_TO_DERIVED, DRIVER=3.57.82
So question is: why Eclipselink don't insert new record into DERIVED table prior to insertion to SECONDARY table?
P.S. Everything is working fine when no SECONDARY table (ROOT and DERIVED tables only) or no inheritance used (DERIVED tables generates id).
For inheritance JPA assumes the foreign key constraints in related table refer to the root table.
You can change your constraint to refer to the root table, or,
use a DescriptorCustomizer to set,
descriptor.setHasMultipleTableConstraintDependecy(true);
or,
customizer the OneToOneMapping to have its foreign key refer to the secondary table (JPA annotation always make it refer to the root table).
Please log a bug though, as JPA join columns should allow you to define a foreign key to the secondary table.
The reason that EclipseLink does defer the insert into the secondary table is to allow inserts to be grouped by tables to allow batch writing and avoid database deadlocks.

Inheritance in hibernate

I would like to implement inheritance in Hibernate.
I created ObjectClass object:
#Entity
#Table(name = "object")
#Inheritance(strategy = InheritanceType.JOINED)
public class ObjectClass {
private id;
}
and CodeTable object that inhertance Object class:
#Entity
#ForeignKey(name = "id")
#Table(name = "code_table")
public class CodeTable extends ObjectClass{
private String description;
}
in the database
object table is:
CREATE TABLE `object` (
`id` bigint(11) NOT NULL auto_increment,
PRIMARY KEY (`id`),
)
code_table table is:
-
CREATE TABLE `code_table` (
`id` bigint(11) NOT NULL auto_increment,
`description` varchar(45) character set latin1 default NULL,
PRIMARY KEY (`id`),
KEY `FK_object` (`id`),
CONSTRAINT `FK_object` FOREIGN KEY (`id`) REFERENCES `object` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
)
I wrote the following code to retreive data from codeTable:
#SuppressWarnings( "unchecked" )
#Transactional( readOnly = true, propagation = Propagation.REQUIRED )
public Collection<CodeTable> findAll() {
Session session = getSessionFactory().getCurrentSession();
return
session.createCriteria( persistentClass
).setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY
).list();
}
I gets empty list although there is one record in codetable table.
When I write the following SQL in my database:
SELECT * FROM `code_table`
I get:
id= 1,
description = company.
What went wrong in my Hibernate definition? How can I retrieve the object?
EDITED:
My hibernate.cfg.xml file looks like this:
<hibernate-configuration>
<session-factory>
<mapping class="com.mycompany.model.CodeTable" />
<mapping class="com.mycompany.model.ObjectClass" />
</session-factory>
</hibernate-configuration>
Your mappings and table structure are (roughly) correct for a JOINED inheritance strategy and I cannot reproduce your problem.
I use the following mappings (which are basically the one you provided):
#Entity
#Table(name = "object")
#Inheritance(strategy = InheritanceType.JOINED)
public class ObjectClass {
#Id #GeneratedValue
private Long id;
public ObjectClass() { }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
And
#Entity
#ForeignKey(name = "id")
#Table(name = "code_table")
public class CodeTable extends ObjectClass{
private String description;
public CodeTable() { }
public String getDescription() { return description; }
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return "CodeTable [getDescription()=" + getDescription() + ", getId()="
+ getId() + "]";
}
}
The following tables:
create table code_table (
description varchar(255),
id bigint not null,
primary key (id)
)
create table object (
id bigint not null,
primary key (id)
)
alter table code_table
add constraint id
foreign key (id)
references object
And the following parent/child records:
insert into object values (1);
insert into code_table(id, description) values (1, 'foo');
And running your criteria query:
session.createCriteria(CodeTable.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
Returns:
CodeTable [getDescription()=foo, getId()=1]
Everything works as expected.
References
JPA 1.0 Specification
2.1.10 Inheritance Mapping Strategies
Hibernate Annotations Reference Guide
2.2.4. Mapping inheritance
How does your mapping looks like ?
Have you read this section in the Hibernate doc ?
Inheritance mapping in Hibernate
As you can read in the link I provided above, your mapping is not correct. You have to let Hibernate know that the code_table class inherits from the object class, and you 'll have to let Hibernate know how this link exists in the database.

Categories

Resources