Hibernate annotation many-to-one mapping generating odd schema - java

I have a fairly simple Many to One relationship between two classes:
#Entity
public class Schedule
implements java.io.Serializable {
private String scheduleName;
private HashSet<Step> steps;
#OneToMany(mappedBy="schedule", cascade=CascadeType.ALL,
fetch=FetchType.EAGER)
public HashSet<Step> getSteps() {
return steps;
}
}
#Entity
public class Step implements java.io.Serializable {
private Long id;
private String duration;
private String stepType;
private Schedule schedule;
#ManyToOne(fetch=FetchType.LAZY)
public Schedule getSchedule() {
return schedule;
}
#Id
#GeneratedValue
public Long getId() {
return id;
}
}
Hibernate generates the following tables (in Postgres)
Table "public.schedule"
Column | Type | Modifiers
--------------+------------------------+-----------
uuid | character varying(255) | not null
version | integer |
schedulename | character varying(255) |
steps | bytea |
Table "public.step"
Column | Type | Modifiers
---------------+------------------------+-----------
id | bigint | not null
duration | character varying(255) |
steptype | character varying(255) |
temperature | numeric(19,2) |
schedule_uuid | character varying(255) |
The step table is what I expect, but I don't understand why the steps(bytea) column is there. Am I doing something wrong in my mapping or do I just not understand how hibernate works?

I suspect the problem is that you're using a concrete HashSet instead of the Set interface. Try this (assuming it has an Id somewhere):
#Entity
public class Schedule implements java.io.Serializable {
private String scheduleName;
private Set<Step> steps = new HashSet<Step>();
#OneToMany(mappedBy="schedule", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
public Set<Step> getSteps() {
return steps;
}
// other properties, getters, setters
}
Also note how I initialized the steps property. Let me quote the documentation about this:
6.1. Persistent collections
...
Notice how the instance variable was
initialized with an instance of
HashSet. This is the best way to
initialize collection valued
properties of newly instantiated
(non-persistent) instances. When you
make the instance persistent, by
calling persist() for example,
Hibernate will actually replace the
HashSet with an instance of
Hibernate's own implementation of Set.
And make sure that:
both entities have an #Id property (the part you're showing is not enough to confirm that).
Step is implementing equals/hashCode correctly (see the references below).
References
Hibernate Core Reference Guide
4.3. Implementing equals() and hashCode()
6.1. Persistent collections
Update: Can't reproduce (I don't have PostgreSQL installed by I don't think it is that relevant). I used the following entities:
#Entity
public class Step implements java.io.Serializable {
private Long id;
private String duration;
private String stepType;
private Schedule schedule;
#ManyToOne(fetch = FetchType.LAZY)
public Schedule getSchedule() { return schedule; }
#Id #GeneratedValue
public Long getId() { return id; }
// getters, setters, equals, hashCode
}
And:
#Entity
public class Schedule implements java.io.Serializable {
private Long id;
private String scheduleName;
private Set<Step> steps = new HashSet<Step>();
#OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Set<Step> getSteps() { return steps; }
#Id #GeneratedValue
public Long getId() { return id; }
// getters, setters
}
Here is the generated DDL:
create table Schedule (
id bigint generated by default as identity (start with 1),
scheduleName varchar(255),
primary key (id)
)
create table Step (
id bigint generated by default as identity (start with 1),
duration varchar(255),
stepType varchar(255),
schedule_id bigint,
primary key (id)
)
alter table Step
add constraint FK277AEC7B775928
foreign key (schedule_id)
references Schedule
I don't even understand how you could use a HashSet in your OneToMany, Hibernate complained (as expected to be honest) when I tried:
Caused by: org.hibernate.AnnotationException: Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: com.stackoverflow.q4083744.Schedule.steps

Related

Hibernate is not mapping object correctly ("Bad value for type" exception) when using compound primary keys in a junction table

I am getting the exception o.h.e.j.s.SqlExceptionHelper | Bad value for type int : 9dac4fd2-a04c-4be7-976b-d880a43ea25a. It seems to want to put a UUID in an Integer field here.
I have the following tables, which admittedly are a bit complex in terms of compound keys:
CREATE TABLE public.event (
id uuid NOT NULL,
...
CONSTRAINT event_pkey PRIMARY KEY (id)
);
CREATE TABLE public.condition_set (
api_id uuid NOT NULL,
version integer NOT NULL,
...,
CONSTRAINT condition_set_pkey PRIMARY KEY (api_id, version)
);
CREATE TABLE public.condition_set_event (
condition_set_api_id uuid NOT NULL,
condition_set_version integer NOT NULL,
event_id uuid NOT NULL,
CONSTRAINT condition_set_event_pkey PRIMARY KEY (condition_set_api_id, condition_set_version, event_id),
CONSTRAINT fk_condition_set FOREIGN KEY (condition_set_api_id, condition_set_version) REFERENCES public.condition_set(api_id, version) ON DELETE CASCADE,
CONSTRAINT fk_event FOREIGN KEY (event_id) REFERENCES public.event(id) ON DELETE CASCADE
);
In my model I have the Event class which is fairly straightforward. The ConditionSet class has a compound primary key matching the database structure, as follows:
#Entity
public class ConditionSet {
#EmbeddedId
private ConditionSetId id;
}
which looks like:
#Embeddable
public class ConditionSetId implements Serializable {
private static final long serialVersionUID = 8110138933878596476L;
private UUID apiId;
private Integer version;
}
The tricky part is the ConditionSetEvent junction table which ALSO consists of a compound key, of which one is the compound key of ConditionSet
#Entity
public class ConditionSetEvent {
#EmbeddedId
private ConditionSetEventId id;
#ManyToOne(fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("conditionSetId")
#JoinColumns(foreignKey = #ForeignKey(name = "fk_condition_set"), value = {
#JoinColumn(nullable = false, name = "conditionSetApiId"),
#JoinColumn(nullable = false, name = "conditionSetVersion")
})
private ConditionSet conditionSet;
#ManyToOne(fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("eventId")
#JoinColumn(foreignKey = #ForeignKey(name = "fk_event"))
private Event event;
public ConditionSetEvent(ConditionSet conditionSet, Event event) {
this.conditionSet = conditionSet;
this.event = event;
this.id = new ConditionSetEventId(conditionSet.getId(), event.getId());
}
}
with its EmbeddedId:
#Embeddable
public class ConditionSetEventId implements Serializable {
private static final long serialVersionUID = -6269791751266804667L;
private ConditionSetId conditionSetId;
private UUID eventId;
}
However, if I try to query this junction table with this repository method:
public interface ConditionSetEventRepository extends JpaRepository<ConditionSetEvent, ConditionSetEventId> {
#Query("select cse from ConditionSetEvent cse where cse.id.eventId = :eventId")
List<ConditionSetEvent> findByEventId(UUID eventId);
}
then I get the error as mentioned on top (where the uuid in the exception is a valid ConditionSet.apiId, but that somehow seems to be re-used.
With trace logging:
DEBUG | org.hibernate.SQL | select conditions0_.condition_set_api_id as conditio0_8_, conditions0_.event_id as event_id1_8_, conditions0_.condition_set_api_id as conditio2_8_, conditions0_.condition_set_version as conditio3_8_ from condition_set_event conditions0_ where conditions0_.event_id=?
TRACE | o.h.t.d.sql.BasicBinder | binding parameter [1] as [OTHER] - [be1ec45d-6533-4e77-98b7-f9a357cda052]
TRACE | o.h.t.d.s.BasicExtractor | extracted value ([conditio0_8_] : [OTHER]) - [9dac4fd2-a04c-4be7-976b-d880a43ea25a]
WARN | o.h.e.j.s.SqlExceptionHelper | SQL Error: 0, SQLState: 22003
ERROR | o.h.e.j.s.SqlExceptionHelper | Bad value for type int : 9dac4fd2-a04c-4be7-976b-d880a43ea25a
So it does manage to extract that UUID value initially (the last trace line), but on the next step (for the Integer) it still is trying to use the UUID instead of the Integer.
Am I doing something wrong here?
I don't think you need the #JoinColumns and it is messing up which id column maps to which id field in ConditionSetEventId
#Entity
public class ConditionSetEvent {
#EmbeddedId
private ConditionSetEventId id;
#ManyToOne(fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
#MapsId("conditionSetId")
private ConditionSet conditionSet;
....

Relating two tables with composite keys when one column is a derived value

I have two existing, highly normalized, tables (Activity and Status)
Create table Activity (
id Number(10,0) not null,
description varchar2(4000) not null,
create_date date not null
);
Create table Status (
table_name varchar2(20) not null,
record_id number(10,0) not null,
status_description varchar2(4000)
);
The Status table relates, in this case, to the Activity table by the following:
STATUS.TABLE_NAME = 'Activity' and STATUS.RECORD_ID = ACTIVITY.ID
It can relate to many other tables as well (besides ACTIVITY):
STATUS.TABLE_NAME = 'Scores' and STATUS.RECORD_ID = SCORE.ID
STATUS.TABLE_NAME = 'Submissions' and STATUS.RECORD_ID = submission.ID
STATUS.TABLE_NAME = 'Tickets' and STATUS.RECORD_ID = TICKET.ID
STATUS.TABLE_NAME = 'Profiles' and STATUS.RECORD_ID = Profile.ID
- STATUS TABLE -
table_name | record_id | status_decription
----------- | ----------- | -----------
'Activity' | **12** | 'Finished'
'Profiles' | 100 | 'Completed'
'Scores' | 200 | 'Calculated'
'Tickets' | 1000 | 'Paid'
- ACTIVITY TABLE -
id | description
----------- | -------------
10 | blah, blah
11 | hey there..
**12** | order pizza
13 | pick up icecream
So given the previous example tables, there was an activity where an individual "'Finished' ordering his pizza"
I am trying to create this relationship with Hibernate, however I cannot seem to figure out the mapping between these two classes.
#Entity(name="status")
#Table(name="Status")
public class StatusDb {
#Column(name="table_name")
private String tableName;
#Column(name="record_id")
private String recordId;
#Column(name="status_desc")
private String description;
// setters/getters
// equals/hashCode
}
#Entity(name="actvity")
#Table(name="Activity")
public class ActivityDb {
#Column(name="id")
#Id
private Long id;
#Column(name="description")
private String description;
// setters/getters
// equals/hashCode
}
How can I relate this #OneToOne mapping between the Status table and the Activity,Profiles,Tickets,Workflow tables?
I assume that the ActivityDb is the 'parent' of the relationship.
I would map my entities as follows:
StatusDb
#Entity(name="status")
#Table(name="Status")
public class StatusDb {
#Column(name="table_name")
private String tableName;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula=#JoinFormula(value="tableName = 'Activity'"),
#JoinColumnOrFormula(column = #JoinColumn("record_id",
referencedColumnName="id"))
})
private ActivityDb activityDb;
#Id
#Column(name="record_id")
private String recordId;
#Column(name="status_desc")
private String description;
// setters/getters
// equals/hashCode
}
ActivityDb
#Entity(name="actvity")
#Table(name="Activity")
public class ActivityDb {
#Column(name="id")
#Id
private Long id;
#Column(name="description")
private String description;
#OneToOne(mappedBy = "activityDb")
private StatusDb statusDb;
}
Now when the id is determined for the ActivityDb entity, upon commit of the transaction, the related StatusDb entity will automatically have the record_id populated with that ActivityDb's id.
Here's the solution that worked for me
#Entity(name="status")
#Table(name="Status")
public class StatusDb {
#Column(name="table_name")
private String tableName;
#Column(name="record_id")
private String recordId;
#Column(name="status_desc")
private String description;
// setters/getters
// equals/hashCode
}
#Entity(name="actvity")
#Table(name="Activity")
public class ActivityDb {
#Column(name="id")
#Id
private Long id;
#Column(name="description")
private String description;
#OneToMany
#Where(value="table_name='Activity'")
#JoinTable(name="STATUS", joinColumn=#JoinColumn(name="id"),
reverseJoinColumn=#JoinColumn(name="record_id")
private Set<StatusDb> status; // Had to to make 1:M to use where clause
// setters/getters
// equals/hashCode
}

Mapping JPA Composite Foreign Key

I'm new with JPA, and want to create a Database with this relation :
|Participant|
|id : INT (PK) | id_event : INT (PK, FK) |
|Event|
|id : INT (PK) |
I'm totally lost and barely figure the syntax of the examples I found :/
But I understood I need to create an other class to contain the two pieces of the PK, which leads to another question : can this class be an inner-class (for optimisation purposes) ?
I hope I'm not asking too much but I really want to get it.
Your entities might be like this:
#Entity
public class Participant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY) // or any other relation
private List<Event> events;
// fields, constructors, getters, setters
}
#Entity
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// fields, constructors, getters, setters
}
In this case JPA will create 3 tables with the following queries (SQL dialect will vary from DB to DB, in this case I used H2 database):
CREATE TABLE Event (
id bigint GENERATED BY DEFAULT AS IDENTITY,
PRIMARY KEY (id)
);
CREATE TABLE Participant (
id bigint GENERATED BY DEFAULT AS IDENTITY,
PRIMARY KEY (id)
);
CREATE TABLE Participant_Event (
Participant_id bigint NOT NULL,
events_id bigint NOT NULL
)
Participant_Event is automatically created join table to link participants and events.
Here is a good example of understanding JPA entity relations.
For a OneToMany relation you need the below entities and tables:
Participant
Event
The Event entity is simple:
#Entity
public class Event {
#Id
private Long id;
// fields, constructors, getters, setters
}
The entity Participant has to hold the composite key (aka two pieces of the PK), so, every Participant is only linked once with an Event.
#Entity
public class Participant {
#EmbeddedId
private EventParticipantPK id;
#OneToMany(fetch = FetchType.LAZY)
private List<Event> events;
// fields, constructors, getters, setters
}
The composite key is declared as an EmbeddedId.
The EventParticipantPK should be like:
#Embeddable
public class EventParticipantPK {
#Column (name = "PARTICIPANT_ID")
private Long participantId;
#Column (name = "EVENT_ID")
private Long eventId;
// fields, constructors, getters, setters
}
I hope this helps.

Atypic JPA OneToOne relation

I have a problem using JPA.
I have to tables:
-----------------
| TableA |
|---------------|
| ID: INT |
| ... |
| ESTATUS1: INT |
| ESTATUS2: INT |
-----------------
-----------------
| EstatusTags |
|---------------|
| COD: VARCHAR |---> COD and VALUE are a concatenated PK
| VALUE: INT |
| DESC: VARCHAR |
-----------------
EstatusTags is a table to store sets of pairs [VALUE, DESC], given a COD.
Before I use JPA, I used to query this kind of data in something like this:
SELECT ID, ESTATUS1, ESTATUS2, E1.DESC DESC1, E2.DESC DESC2
FROM TABLEA A
INNER JOIN ESTATUSTAGS E1 ON E1.COD = "a_estatus1"
AND E1.VALUE = A.ESTATUS1
INNER JOIN ESTATUSTAGS E2 ON E2.COD = "a_estatus2"
AND E2.VALUE = A.ESTATUS2
I'm trying to use JPA to model this using two entity classes:
#Entity
#Table(name = "EstatusTags")
public class EstatusTags implements Serializable {
#EmbeddedId
private ValueTagPK id;
#Column(name="VVA_DESC")
private String desc;
#Column(name="VVA_ORDEN")
private Integer orden;
}
#Entity
#Table(name = "TableA")
public class A implements Serializable {
#Column(name="ID")
private String desc;
#OneToOne(???)
private EstatusTag estatus1;
#OneToOne(???)
private EstatusTag estatus2;
}
I have strong doubts in how to model the relations. Can it be done with annotations? There is necesary the JPQL use to fit this structure?
I hope somebody could help me with this.
Thanks a lot.
The problem is that your entity model does not match the table structure.
In your entity model you have a one to one relation ship between A and EstatusTag whereas in your table model you have a relationship of one A and multiple Estatustags (for one value there may exist multiple Etatustags entries)
You overcome the problem that Table A does not have a cod column by adding something like a virtual cod column E1.COD = "a_estatus1" to your SQL Query.
What you can do is you map the value column of to two properties of EstatusTag one time to the composite pk and the other time to a single property in the following way . The simple value is made accessible via property access but marked as not updatable not insertable also the setter does not really work and is made private.
Remark: I don't know if that works with all JPA implementations - Tested with hibernate 4.3.8.
#Entity
#Table(name = "EstatusTags" )
#Access(AccessType.FIELD)
public class EstatusTag implements Serializable{
private #EmbeddedId ValueTagPK id;
#Column(name="VVA_DESC")
private String desc;
#Column(name="VVA_ORDEN")
private Integer orden;
#Column(name="value", updatable=false, insertable=false)
#Access(AccessType.PROPERTY)
public int getValue() {
return id.value;
}
private void setValue(int value) {
// only because otherwise hibernate complains about a missing setter.
}
}
#Entity
#Table(name = "TableA")
public class A implements Serializable{
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.TABLE)
private int id;
#OneToOne()
#JoinColumn(name="estatus1",referencedColumnName="value")
public EstatusTag estatus1;
#OneToOne()
#JoinColumn(name="estatus2",referencedColumnName="value")
public EstatusTag estatus2;
}

#OneToMany relationship using a single #JoinColumn?

I have the following tables (most essential columns shown only, A & B are not the real names btw):
table A {
...
}
table B {
...
}
table METADATA {
KEY
VALUE
REF_A
REF_B
}
METADATA holds additional key/value meta data for both table A & B. The key/value is needed as we have to handle dynamic data for which we cannot up front create columns for in A and B.
The entities are setup as (JPA using hibernate as provider):
interface Entity {
...
getId()
...
}
class A implements Entity {
...
#OneToMany(cascade = {ALL}, mappedBy = "a", orphanRemoval = true, fetch = LAZY)
private List<MetaData> metaData;
...
#Override
public List<MetaData> getMetaData() {
return metaData;
}
...
}
class B implements Entity {
...
#OneToMany(cascade = {ALL}, mappedBy = "b", orphanRemoval = true, fetch = LAZY)
private List<MetaData> metaData;
...
#Override
public List<MetaData> getMetaData() {
return metaData;
}
...
}
class MetaData implements Entity {
...
#ManyToOne
#JoinColumn(name = "REF_A", nullable = true)
private A a;
#ManyToOne
#JoinColumn(name = "REF_B", nullable = true)
private B b;
...
}
This setup works fine. However we have run into issues on some databases (for instance DB2) with a unique index we create (to ensure a meta key is only used once for a given row in A or B):
CREATE UNIQUE INDEX METADATA_UNIQUE_KEY ON METADATA (METAKEY, REF_A, REF_B)
as creating the index requires requires that all columns are non-null. This is not the case for use with the above design as the domain logic will either be that the meta data is set on A or B, hence one of these will always be null.
Possible solutions of course are to split the METADATA into two tables, one for A and one for B. However I would prefer to keep one table and instead just have one "REF" column which would either be an A or B as well as a TYPE column to say whether it's a meta data for an A or B. The TYPE would be needed as we have separate sequences for id for each table and a A and B could get the same technical id and hence get mixed up data otherwise.
My question is - is there any way to set this up with JPA?
For one-table based inheritance there is a #DiscriminatorValue which can be used to distinguish the specific stored sub-class, can this be used here as well? I am looking for something like:
table A {
...
}
table B {
...
}
table METADATA {
KEY
VALUE
REF
TYPE
}
#DiscriminatorValue("A")
class A implements Entity {
...
#OneToMany(cascade = {ALL}, mappedBy = "entity", orphanRemoval = true, fetch = LAZY)
private List<MetaData> metaData;
...
#Override
public List<MetaData> getMetaData() {
return metaData;
}
...
}
#DiscriminatorValue("B")
class B implements Entity {
...
#OneToMany(cascade = {ALL}, mappedBy = "entity", orphanRemoval = true, fetch = LAZY)
private List<MetaData> metaData;
...
#Override
public List<MetaData> getMetaData() {
return metaData;
}
...
}
class MetaData implements Entity {
...
#ManyToOne
#JoinColumn(name = "REF", nullable = true)
private Entity entity;
#DiscriminatorColumn(name="TYPE", discriminatorType=STRING, length=20)
private String type;
...
}
so basically when a meta data is inserted for A this SQL would be used:
INSERT INTO METADATA (KEY, VALUE, REF, TYPE) VALUES ("metaKey", "metaValue", 1, "A")
Any suggestion are welcomed.
Rgs,
-Martin
I'm not sure why you need to create a key (metakey) in the metadata table, given thet the rows are already tied either to Table A or Table B.
However I think the problem is in considering the MetaData table an entity, given that its only purpose is to save some extra information of an existing entity, it means that you cannot have a row in MetaData without a row in TableA or TableB.
Instead of using relationship mapping one option is to use element collections to directly have the Map of key/value pairs in the corresponding entities:
#Entity
#Table(name="TableA")
public class TableA
{
#Id
#GeneratedValue(strategy= GenerationType.TABLE)
private int id;
#ElementCollection
#CollectionTable(name="MetaData", joinColumns={#JoinColumn(name="TableA_id")})
#MapKeyColumn(name="metaKey")
#Column(name="metaValue")
private Map<String, String> metadata;
}
#Entity
#Table(name="TableB")
public class TableB
{
#Id
#GeneratedValue(strategy= GenerationType.TABLE)
private int id;
#ElementCollection
#CollectionTable(name="MetaData", joinColumns={#JoinColumn(name="TableB_id")})
#MapKeyColumn(name="metaKey")
#Column(name="metaValue")
private Map<String, String> metadata;
}
Note that there is no java class for a "MetaData" table or entity, the table is automatically mapped from the #ElementCollection and #CollectionTable annotations.
The above mappings correspond to the following MetaData table:
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| TableA_id | int(11) | YES | MUL | NULL | |
| metaValue | varchar(255) | YES | | NULL | |
| metaKey | varchar(255) | YES | | NULL | |
| TableB_id | int(11) | YES | MUL | NULL | |
+-----------+--------------+------+-----+---------+-------+
If you prefer to keep a separate java class for MetaData to keep using a List instead of a Map, it can also be done with #ElementCollection, you just need to annotate the MetaData class with #Embeddable instead of #Entity. In that way it doesn't need an Id column like a regular entity.

Categories