one-to-one relationship using JPA - jpa

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.

Related

Foreign Key violation on ManyToMany with inheritance

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...

Why doesn't Eclipselink generate the ON DELETE CASCADE clause in a unidirectional #ManyToOne relationship?

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;
...

JPA: create constraint between primary key column in one table and a non-primary column in another?

I’m using JPA and trying to figure out how to create a unique constraint between a primary key column in one table and a non-primary key column in another. I have two tables:
Customer (
id character varying(32) NOT NULL,
customer_name character varying(50)
)
and
Account (
id character varying(32) NOT NULL,
account_name character varying(50)
)
There is a unidirectional, one-to-many relationship between Customer and Account with a constraint that Account.account_name is unique per Customer. The code looks like so:
#Entity
public class Customer {
#Id
#Column(length=32)
private String id;
#Column(unique=true, length=50)
private String customer_name;
#OneToMany
private List<Account> accounts;
...
}
and
#Entity
public class Account {
#Id
#Column(length=32)
private String id;
#Column(length=50)
private String account_name;
...
}
A join table is created by default:
CUSTOMER_ACCOUNTS (
customer_id character varying(32),
accounts_id character varying(32)
)
How do I create the unique constraint to ensure an Account.account_name is unique per Customer?
Add a JoinColumn annotation to the Customer class::
#Entity
public class Customer {
#Id
#Column(length=32)
private String id;
#Column(unique=true, length=50)
private String customer_name;
#OneToMany(mappedBy="customer")
#JoinColumn(name="CUSTOMER_ID", referencedColumnName="ID")
private List<Account> accounts;
...
}
Referenced column name points to the primary key of the Customer table, while name points to the foreign key field of the Account table.
You should also add a CUSTOMER_ID field and a composite unique constraint to the account entity:
#Entity
#Table(
uniqueConstraints=
#UniqueConstraint(columnNames={"CUSTOMER_ID", "ACCOUNT_NAME"})
)
public class Account {
#Id
#Column(length=32)
private String id;
#Column(length=50)
private String account_name;
#Column(name="CUSTOMER_ID")
private String customer_id;
...
}

The type of field isn't supported by declared persistence strategy "OneToMany"

We are new to JPA and trying to setup a very simple one to many relationship where a pojo called Message can have a list of integer group id's defined by a join table called GROUP_ASSOC. Here is the DDL:
CREATE TABLE "APP"."MESSAGE" (
"MESSAGE_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)
);
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");
Here is the pojo:
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int messageId;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
private List groupIds;
public int getMessageId() {
return messageId;
}
public void setMessageId(int messageId) {
this.messageId = messageId;
}
public List getGroupIds() {
return groupIds;
}
public void setGroupIds(List groupIds) {
this.groupIds = groupIds;
}
}
I know this is wrong as there is no #Column mapping to GROUP_ASSOC.GROUP_ID for the groupIds property, but hopefully this illustrates what we are trying to do. When we run the following test code we get <openjpa-1.2.3-SNAPSHOT-r422266:907835 fatal user error> org.apache.openjpa.util.MetaDataException: The type of field "pojo.Message.groupIds" isn't supported by declared persistence strategy "OneToMany". Please choose a different strategy.
Message msg = new Message();
List groups = new ArrayList();
groups.add(101);
groups.add(102);
EntityManager em = Persistence.createEntityManagerFactory("TestDBWeb").createEntityManager();
em.getTransaction().begin();
em.persist(msg);
em.getTransaction().commit();
Help!
When you are working with JPA, you should think Object and relations between Objects and you should map your Object model, not ids, to your relational model (it is possible to map a List of basic values with #ElementCollection in JPA 2.0 though but what I said just before still applies).
Here, (assuming this really is a one-to-many relation between Message and GroupAssoc and not a many-to-many relation between Message and Group entities) you should have something like this:
#Entity
#Table(name = "MESSAGE")
public class Message implements Serializable {
#Id
#Column(name = "MESSAGE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long messageId;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST)
private List<GroupAssoc> groupAssocs = new ArrayList<GroupAssoc>();
public Long getMessageId() {
return messageId;
}
public void setMessageId(Long messageId) {
this.messageId = messageId;
}
public List<GroupAssoc> getGroupAssocs() {
return groupAssocs;
}
public void setGroupAssocs(List<GroupAssoc> groupAssocs) {
this.groupAssocs = groupAssocs;
}
// equals() and hashCode()
}
And another entity for GroupAssoc.
PS: Your DDL really looks like a (M:N) relation between MESSAGE and GROUP (or I don't understand the PK constraint of GROUP_ASSOC) but you didn't show any FK constraint on GROUP_ID so I'm not 100% sure. But if that's the case, then you should use an #ManyToMany instead of #OneToMany.

Persisting a List of Integers with JPA?

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.
}