Im currently building the following scenario:
I have an Action which holds a list of Parameters. Those can be in other actions as well, so I have a ManyToMany relationship.
The Parameter is an abstract class, one implementation is a TextParameter.
So now I have the following code:
#Data
#Entity
public class Action {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "Action2ParameterMapping",
joinColumns = #JoinColumn(name = "actionId"),
inverseJoinColumns = #JoinColumn(name = "parameterId"))
private List<Parameter> parameters;
}
with Parameter as
#Data
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class ProductSample {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
And TextParameter:
#Data
#Entity
#PrimaryKeyJoinColumn(name = "parameterId")
public class TextParameter extends Parameter {
...
}
I now created the Tables as follows (I don't want to generate since we use Flyway migration):
CREATE TABLE Action
(
id BIGINT NOT NULL PRIMARY KEY IDENTITY
)
CREATE TABLE Parameter
(
id BIGINT NOT NULL PRIMARY KEY IDENTITY
)
CREATE TABLE TextParameter
(
parameterId BIGINT NOT NULL FOREIGN KEY REFERENCES Parameter (id)
)
-- Many-To-Many MappingTable
CREATE TABLE Action2ParameterMapping
(
actionId BIGINT NOT NULL FOREIGN KEY REFERENCES Action (id),
parameterId BIGINT NOT NULL FOREIGN KEY REFERENCES Parameter (id),
PRIMARY KEY (actionId, parameterId)
)
I use Quarkus and have the simple PanacheRepository
#ApplicationScoped
public class ActionRepository implements PanacheRepository<Action> {
}
So now, when I now create an Action-Object holding Parameter-Objects and persist it using actionRepository.persist(action), I get an SQLServerException The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Action2Pa__actio__4242D080 and I don't understand why.
I understand that it tries to tell me, that it wanted to persist an entry in the MappingTable but the actionId did not belong to any Action, but how can that be?
I don't understand, why this won't work.
After having the problem for over 3 days, I've solved almost right after asking the question...
The problem was within the DB-Test-Suite.
The #AfterEachmethod tried to delete parameters, which violated the Contraint...
Related
I have two entities with one to one relationship, I want parent entity primary as a child entity foreign key along with child entity primary key
I have been trying with JPA #MapsId() but I could not succeed
my parent entity
#Id
#SequenceGenerator(initialValue=10000, name = "parent_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator="parent_seq")
#Column(name = "parent_id")
private long parentid;
#OneToOne(mappedBy="parentEntity", cascade = CascadeType.ALL)
private ChildEntity childEntity;
and in my child entity
#SequenceGenerator(initialValue=10000, name = "child_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator="child_seq")
#Column(name = "child_id")
private long childid;
#MapsId("parent_id")
#OneToOne
private ParentEntity parentEntity;
here I would like to create tables using JPA for that I have given
spring.jpa.hibernate.ddl-auto=create
this is working fine but I am expecting that parent_id column should be created inside my childEntity table but it is not creating and the parent_id should be inserted into child tables parent_id column.
When you are going to use #MapsId feature then your 'child' entity should have the 'simple' identifier without any generation. For example:
#Entity
#Table(name = "parents")
public class Parent {
#Id
#GeneratedValue(...)
private Long id;
// other stuff...
}
#Entity
#Table(name = "children")
public class Child {
#Id
private Long id;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
private Parent parent;
// other stuff...
}
In this case, the children table will be like the following:
create table children (
parent_id bigint not null constraint children_pkey primary key,
-- other stuff...
constraint fk_children_parent_id foreign key (parent_id) references parents(id)
);
More info: The best way to map a #OneToOne relationship with JPA and Hibernate
These are two related entities in Eclipselink JPA:
#Entity
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#ManyToOne(cascade={CascadeType.ALL})
private Department department;
}
and this is the generated DDL:
CREATE TABLE PERSON (ID BIGINT IDENTITY NOT NULL, FIRSTNAME VARCHAR, LASTNAME VARCHAR, DEPARTMENT_ID BIGINT, PRIMARY KEY (ID))
CREATE TABLE DEPARTMENT (ID BIGINT IDENTITY NOT NULL, NAME VARCHAR, PRIMARY KEY (ID))
ALTER TABLE PERSON ADD CONSTRAINT FK_PERSON_DEPARTMENT_ID FOREIGN KEY (DEPARTMENT_ID) REFERENCES DEPARTMENT (ID)
The environment is:
- eclipselink 2.5.2
- mysql-connector-java 5.1.6
I would expect to have at least a ON DELETE CASCADE clause on the foreign key definition.
What is the cascade option intended for, in the #ManyToOne relationship?
Do I really have to delete the children records manually before deleting the parent record?
The CascadeType you mention in your example is one value of those: ALL, PERSIST, MERGE, REMOVE, REFRESH, DETACH. This is ORM, but not SQL DDL related.
I guess You are searching for a SQL DDL foreign key constraint definition ...on delete cascade. To geht this SQL DDL generated, You need an #CascadeOnDelete annotation, as shown in this example:
...
#OneToMany(mappedBy="abc", orphanRemoval=true, cascade={CascadeType.ALL})
#CascadeOnDelete
private List<MobilPhoneNumer> mobilePhonesNumbers;
...
I do have a DB table with unidirectional trees. Leafs of these trees can have several children/parents.
Cycles are restricted.
Here is my DB table definition:
CREATE MULTISET TABLE WORKFLOW_SEQ_REL ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
WORKFLOW_SEQ_ID INTEGER NOT NULL,
REL_WORKFLOW_SEQ_ID INTEGER NOT NULL,
JOB_ID BIGINT)
PRIMARY INDEX ( WORKFLOW_SEQ_ID );
As you can see it doesn't have a primary key right now. But it would appear later :) Really PK is: JOB_ID+PARENT_ID+CHILD_ID.
The idea is:
REL_WORKFLOW_SEQ_ID = PARENT
WORKFLOW_SEQ_ID = CHILD
JOB_ID = TREE_IDENTIFICATOR (a determinant to separate different trees stored
in one table).
I'm trying to declare a JPA entity:
#Entity
#Table(name="WORKFLOW_SEQ_REL")
public class EtlWorkflowSeqNode {
#EmbeddedId
public EtlWorkflowSeqNodeId etlWorkflowSeqNodeId;
//#Column(name="JOB_ID")
//public Integer jobId;
#Embeddable
class EtlWorkflowSeqNodeId{
#Column(name="JOB_ID")
public Integer jobId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="REL_WORKFLOW_SEQ_ID")
//EtlWorkflowSeq.id = PK of EtlWorkflowSeq entity
public EtlWorkflowSeq parent;
#OneToMany(fetch=FetchType.EAGER /*, mappedBy="parent"*/)
#JoinColumn(name="WORKFLOW_SEQ_ID")
public Set<EtlWorkflowSeq> children;
}
}
And I gen an error:
Caused by: org.hibernate.AnnotationException: A Foreign key refering models.EtlWorkflowSeqNode from models.EtlWorkflowSeq has the wrong number of column.
should be 2
Here is EtlWorkflowSeq entity:
#Entity
#Table(name="WORKFLOW_SEQ")
public class EtlWorkflowSeq {
#Id
#Column(name="WORKFLOW_SEQ_ID")
public Integer id;
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name="WORKFLOW_ID")
public EtlWorkflow etlWorkflow;
}
What do I do wrong?
UPD:
Here are table definitions:
--a bad design. PK should be: WORKFLOW_SEQ_ID + REL_WORKFLOW_SEQ_ID + JOB_ID
CREATE MULTISET TABLE WORKFLOW_SEQ_REL ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
WORKFLOW_SEQ_ID INTEGER NOT NULL, --a ref to child
REL_WORKFLOW_SEQ_ID INTEGER NOT NULL, -- a ref to parent
START_TYPE_ID SMALLINT NOT NULL, -- a type of connection
DISABLE_START_TYPE_ID SMALLINT, -- other type of connection
JOB_ID BIGINT) -- a tree determinant,
PRIMARY INDEX ( WORKFLOW_SEQ_ID );
CREATE MULTISET TABLE WORKFLOW_SEQ ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
WORKFLOW_SEQ_ID INTEGER NOT NULL, -- an id
WORKFLOW_ID BIGINT NOT NULL, -- a ref to original workflow, not interesting
IS_NAME VARCHAR(255) CHARACTER SET UNICODE NOT CASESPECIFIC, -- some name
INFO_SYSTEM_INST_CD VARCHAR(255) CHARACTER SET UNICODE NOT CASESPECIFIC, -- other name
DISABLE BYTEINT) -- so garbage
UNIQUE PRIMARY INDEX ( WORKFLOW_SEQ_ID ); -- it should also be a PK
The Idea is that several trees are stored in WORKFLOW_SEQ_REL
JOB_ID is a determinant for trees.
WORKFLOW_SEQ_ID, REL_WORKFLOW_SEQ_ID refer some cutomized template from REL_WORKFLOW_SEQ table.
I cannot help noticing that there is an inconsistency in your question.
You first state that:
Leafs of these trees can have several children/parents.
This in my believe makes the relationship between leafs many to many.
As I make of your question that EtlWorkflowSeq represent leafs, I think EtlWorkflowSeqNode represents the relationship between EtlWorkflowSeq objects?
However, the nodes point to one parent and many children.
You can use something like this to create something similar:
#Entity
#Table(name="WORKFLOW_SEQ")
public class EtlWorkflowSeq
{
#Id
#GeneratedValue
#Column(name="WORKFLOW_SEQ_ID")
public Integer id;
#ManyToOne
#JoinColumn(name="WORKFLOW_ID")
public EtlWorkflow etlWorkflow;
#ManyToMany
#JoinTable(name = "WORKFLOW_SEQ_REL")
private Set<EtlWorkflowSeq> children;
#ManyToMany(mappedBy = "children")
private Set<EtlWorkflowSeq> parents;
#ManyToOne
#JoinColumn(name = "JOB_ID", referencedColumnName = "id")
private Job job;
}
This would make EtlWorkflowSeqNode and EtlWorkflowSeqNodeId obsolete.
I also would like to state that when using an #Embeddable you should only use base types in them. Using other than base types is not possible/causes problems/is not standard (correct me if I'm wrong).
If you would like to use foreign keys in a composite primary key you can use this:
#Entity
public class Foo
{
#Id
private Long id;
}
#Entity
public class Bar
{
#EmbeddedId
private BarPK key;
#MapsId(value = "fooId")
#ManyToOne
#JoinColumns({
#JoinColumn(name = "foo_id", referencedColumnName = "id")
})
private Foo foo;
}
#Embeddable
public class BarPK
{
#Column(name = "id")
private Long id;
#Column(name = "foo_id")
private Long fooId;
}
I have created two table using JPA. i need to give 1-1 relationship between these tables. Can any one tell me how to give relationship between these tables.
Simply add a column in the table "owning" the relation with a FK constraint. For example:
CREATE TABLE MYENTITYA (
ID BIGINT NOT NULL,
MYENTITYB_ID BIGINT
);
CREATE TABLE MYENTITYB (
ID BIGINT NOT NULL
);
ALTER TABLE MYENTITYA ADD CONSTRAINT SQL100326144838300 PRIMARY KEY (ID);
ALTER TABLE MYENTITYB ADD CONSTRAINT SQL100326144838430 PRIMARY KEY (ID);
ALTER TABLE MYENTITYA ADD CONSTRAINT FKB65AC952578E2EA3 FOREIGN KEY (MYENTITYB_ID)
REFERENCES MYENTITYB (ID);
That would be mapped like this:
#Entity
public class MyEntityA implements Serializable {
private Long id;
private MyEntityB myEntityB;
#Id
#GeneratedValue
public Long getId() {
return this.id;
}
#OneToOne(optional = true, cascade = CascadeType.ALL)
public MyEntityB getEntityB() {
return this.myEntityB;
}
//...
}
#Entity
public class MyEntityB implements Serializable {
private Long id;
#Id
#GeneratedValue
public Long getId() {
return id;
}
//...
}
If the relation between EntityA and EntityB is not optional, then add a NOT NULL constraint.
We have a pojo that needs to have a list of integers. As an example, I've created a Message pojo and would like to associate a list of groupIds (these ids need to be queried and displayed in the UI). So ideally, we would like to be able to do something like this:
Message msg = em.find(Message.class, 101);
List<Integer> groupIds = msg.getGroupIds();
I was under the impression that this would require only one pojo with JPA, but according to the discussion here, I need to create a second pojo because JPA works in terms of objects instead of primitive types.
From that discussion I've tried the following example code, but I get the error openjpa-1.2.3-SNAPSHOT-r422266:907835 fatal user error: org.apache.openjpa.util.MetaDataException: The type of field "pojo.Group.messageId" isn't supported by declared persistence strategy "ManyToOne". Please choose a different strategy.
DDL:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
"AUTHOR" CHAR(20) NOT NULL
);
ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");
CREATE TABLE "APP"."GROUP_ASSOC" (
"GROUP_ID" INTEGER NOT NULL,
"MESSAGE_ID" INTEGER NOT NULL
);
ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");
ALTER TABLE "APP"."GROUP_ASSOC" ADD CONSTRAINT "GROUP_ASSOC_FK" FOREIGN KEY ("MESSAGE_ID")
REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");
POJOs:
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#OneToMany
private List<Group> groups = new ArrayList<Group>();
#Column(name = "AUTHOR")
private String author;
// getters/setters ommitted
}
#Entity
#IdClass(pojo.Group.GroupKey.class)
#Table(name = "GROUP_ASSOC")
public class Group {
#Id
#Column(name = "GROUP_ID")
private Long groupId;
#Id
#Column(name = "MESSAGE_ID")
#ManyToOne
private Long messageId;
public static class GroupKey {
public Long groupId;
public Long messageId;
public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof Group)) return false;
Group g = (Group) obj;
return g.getGroupId() == groupId && g.getMessageId() == messageId;
}
public int hashCode() {
return ((groupId == null) ? 0 : groupId.hashCode())
^ ((messageId == null) ? 0 : messageId.hashCode());
}
}
// getters/setters ommitted
}
Test Code:
EntityManager em = Persistence.createEntityManagerFactory("JPATest").createEntityManager();
em.getTransaction().begin();
Message msg = new Message();
msg.setAuthor("Paul");
em.persist(msg);
List<Group> groups = new ArrayList<Group>();
Group g1 = new Group();
g1.setMessageId(msg.getMessageId());
Group g2 = new Group();
g2.setMessageId(msg.getMessageId());
msg.setGroups(groups);
em.getTransaction().commit();
This all seems ridiculous -- 3 classes (if you include the GroupKey composite identity class) to model a list of integers -- isn't there a more elegant solution?
This is an old topic but things have changed since OpenJPA2, now you can directly persist primitive types or String object. Use ElementCollection annotation to use simple one-to-many linking, no need to intermediate object or link tables. This is how most of us probably create SQL schemas.
#Entity #Table(name="user") #Access(AccessType.FIELD)
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private long id; // primary key (autogen surrogate)
private String name;
// ElementCollection provides simple OneToMany linking.
// joinColumn.name=foreign key in child table. Column.name=value in child table
#ElementCollection(fetch=FetchType.LAZY)
#CollectionTable(name="user_role", joinColumns={#JoinColumn(name="user_id")})
#Column(name="role")
private List<String> roles;
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name=name; }
public List<String> getRoles() { return roles; }
public void setRoles(List<String> roles) { this.roles=roles; }
}
- - -
CREATE TABLE user (
id bigint NOT NULL auto_increment,
name varchar(64) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY USERNAME (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
CREATE TABLE user_role (
user_id bigint NOT NULL,
role varchar(64) NOT NULL default '',
PRIMARY KEY (user_id, role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
I really think that what you have is in fact a many-to-many association between two Entities (let's call them Message and Group).
The DDL to represent this would be:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
"AUTHOR" CHAR(20) NOT NULL
);
ALTER TABLE "APP"."MESSAGE" ADD CONSTRAINT "MESSAGE_PK" PRIMARY KEY ("MESSAGE_ID");
CREATE TABLE "APP"."GROUP" (
"GROUP_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)
);
ALTER TABLE "APP"."GROUP" ADD CONSTRAINT "GROUP_PK" PRIMARY KEY ("GROUP_ID");
CREATE TABLE "APP"."MESSAGE_GROUP" (
"GROUP_ID" INTEGER NOT NULL,
"MESSAGE_ID" INTEGER NOT NULL
);
ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_PK" PRIMARY KEY ("MESSAGE_ID", "GROUP_ID");
ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_FK1" FOREIGN KEY ("MESSAGE_ID")
REFERENCES "APP"."MESSAGE" ("MESSAGE_ID");
ALTER TABLE "APP"."MESSAGE_GROUP" ADD CONSTRAINT "MESSAGE_GROUP_FK2" FOREIGN KEY ("GROUP_ID")
REFERENCES "APP"."MESSAGE" ("GROUP_ID");
And the annotated classes:
#Entity
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#ManyToMany
#JoinTable(
name = "MESSAGE_GROUP",
joinColumns = #JoinColumn(name = "MESSAGE_ID"),
inverseJoinColumns = #JoinColumn(name = "GROUP_ID")
)
private List<Group> groups = new ArrayList<Group>();
private String author;
//...
}
#Entity
public class Group {
#Id
#GeneratedValue
#Column(name = "GROUP_ID")
private Long groupId;
#ManyToMany(mappedBy = "groups")
private List<Message> messages = new ArrayList<Message>();
//...
}
I'm not sure you need a bi-directional association though. But you definitely need to start to think object if you want to use JPA (in you're example, you're still setting ids, you should set Entities). Or maybe JPA is not what you need.
isn't there a more elegant solution?
I'm not sure "elegant" is appropriate but JPA 2.0 defines an ElementCollection mapping (as I said in my previous answer):
It is meant to handle several non-standard relationship mappings. An ElementCollection can be used to define a one-to-many relationship to an Embeddable object, or a Basic value (such as a collection of Strings).
But that's in JPA 2.0. In JPA 1.0, you would have to use a provider specific equivalent, if your provider does offer such an extension. It appears that OpenJPA does with #PersistentCollection.
Based on your schema you have a ManyToOne relationship between Group and Message. Which means that a single Message can belong to multiple groups, but each group can have a single message.
The entities would look something like this.
#Entity
#Table(name = "GROUP_ASSOC")
public class Group {
#Id
#Column(name="GROUP_ID")
private int id;
#ManyToOne
#Column(name="MESSAGE_ID")
#ForeignKey
private Message message;
// . . .
}
#Entity
public class Message {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "MESSAGE_ID")
private int id;
#Column(length=20)
private String author;
#OneToMany(mappedBy="message")
private Collection<Group> groups;
}
There's no need for an IDClass in your app (you only need one if your ID is contains multiple columns).
To get the groupIds for a given message you could write a query like this one
Query q = em.createQuery("Select g.id from Group g where g.message.id = :messageId");
q.setParameter("messageId", 1);
List results = q.getResultList();
Or just iterate over Message.getGroups() :
Message m = em.find(Message.class, 1);
for(Group g : m.getGroups()) {
// create a list, process the group whatever fits.
}