How to persist two entities with JPA - java

I am using the JPA in my webapp and I can't figure out how to persist two new entities that relate to each other. Here an example:
These are the two entities
+-----------------+ +--------------------+
| Consumer | | ProfilePicture |
+-----------------+ +--------------------+
| id (PK) |---| consumerId (PPK+FK)|
| userName | | url |
+-----------------+ +--------------------+
The Consumer has an id and some other values. The ProfilePicture uses the Consumer's id as it's own primary key and as foreign key. (Since a ProfilePicture will not exist without a Consumer and not every Consumer has a ProfilePicture)
I used NetBeans to generate the entity classes and the session beans (facades).
This is how they look like in short
Consumer.java
#Entity
#Table(name = "Consumer")
#NamedQueries({...})
public class Consumer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(name = "userName")
private String userName;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "consumer")
private ProfilePicture profilePicture;
/* and all the basic getters and setters */
(...)
}
ProfilePicture.java
#Entity
#Table(name = "ProfilePicture")
#XmlRootElement
#NamedQueries({...})
public class ProfilePicture implements Serializable {
#Id
#Basic(optional = false)
#NotNull
#Column(name = "consumerId")
private Integer consumerId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 255)
#Column(name = "url")
private String url;
#JoinColumn(name = "consumerId", referencedColumnName = "id", insertable = false, updatable = false)
#OneToOne(optional = false)
private Consumer consumer;
/* and all the basic getters and setters */
(...)
}
So when I want to create a Consumer with his ProfilePicture I thought I would do it like this:
ProfilePicture profilePicture = new ProfilePicture("http://www.url.to/picture.jpg"); // create the picture object
Consumer consumer = new Consumer("John Doe"); // create the consumer object
profilePicture.setConsumer(consumer); // set the consumer in the picture (so JPA can take care about the relation
consumerFacade.create(consumer); // the facade classes to persist the consumer
profilePictureFacade.create(profilePicture); // and when the consumer is persisted (and has an id) persist the picture
My Problem
I tried almost everything in every combination but JPA doesn't seem to be able to link the two entities on it's own. Most of the time I am getting errors like this:
EJB5184:A system exception occurred during an invocation on EJB ConsumerFacade, method: public void com.me.db.resources.bean.ConsumerFacade.create(com.mintano.backendclientserver.db.resources.entity.Consumer)
(...)
Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'prePersist'. Please refer to embedded ConstraintViolations for details.
As far as I understand the problem, it is because the ProfilePicture doesn't know the id of the Consumer and thus, the entities cannot persist.
The only way it ever worked, was when persisting the Consumer first, setting it's id to the ProfilePicture and then persisting the picture:
ProfilePicture profilePicture = new ProfilePicture("http://www.url.to/picture.jpg"); // create the picture object
Consumer consumer = new Consumer("John Doe"); // create the consumer object
consumerFacade.create(consumer); // the facade classes to persist the consumer
profilePicture.setConsumerId(consumer.getId()); // set the consumer's new id in the picture
profilePictureFacade.create(profilePicture); // and when the consumer is persisted (and has an id) persist the picture
However these two tables are just an example and naturally the database is much more complex and setting the ids manually like this seems very inflexible and I am afraid of over complicating things. Especially because I can't persist all entities in one transaction (which seems very inefficient).
Am I doing it right? Or is there another, more standard way?
Edit: my solution
As FTR suggested, one problem was the missing id for the ProfilePicture table (I used the Consumer.id as foreign and primary)..
The tables look like this now:
+-----------------+ +--------------------+
| Consumer | | ProfilePicture |
+-----------------+ +--------------------+
| id (PK) |_ | id (PK) |
| userName | \_| consumerId (FK) |
+-----------------+ | url |
+--------------------+
Then Alan Hay told me to Always encapsulate add/remove to relationships and then you can ensure correctness, which I did:
Consumer.java
public void addProfilePicture(ProfilePicture profilePicture) {
profilePicture.setConsumerId(this);
if (profilePictureCollection == null) {
this.profilePictureCollection = new ArrayList<>();
}
this.profilePictureCollection.add(profilePicture);
}
Since ProfilePicture has it's own id now, it became a OneToMany relationship, so each Consumer can now have many profile pictures. That's not what I intended at first, but I can life with it :) Therefore I can't just set a ProfilePicture to the Consumer but have to add it to a collection of Pictures (as above).
This was the only additional method I implemented and now it works. Thanks again for all your help!

When persisting an instance of the non-owning side of the relationship (that which contains the 'mappedBy' and in your case Consumer) then you must always ensure both sides of the relationship are set to have cascading work as expected.
You should of course always do this anyway to ensure your domain model is correct.
Consumer c = new Consumer();
ProfilePicure p = new ProfilePicture();
c.setProfilePicture(p);//see implementation
//persist c
Consumer.java
#Entity
#Table(name = "Consumer")
#NamedQueries({...})
public class Consumer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 50)
#Column(name = "userName")
private String userName;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "consumer")
private ProfilePicture profilePicture;
public void setProfilePicture(ProfilePicture profilePicture){
//SET BOTH SIDES OF THE RELATIONSHIP
this.profilePicture = profilePicture;
profilePicture.setConsumer(this);
}
}
Always encapsulate add/remove to relationships and then you can ensure correctness:
public class Parent{
private Set<Child> children;
public Set<Child> getChildren(){
return Collections.unmodifiableSet(children); //no direct access:force clients to use add/remove methods
}
public void addChild(Child child){
child.setParent(this);
children.add(child);
}
public class Child(){
private Parent parent;
}

You can persist one object and its child at a time. So I think this should work:
ProfilePicture profilePicture = new ProfilePicture("http://www.url.to/picture.jpg"); // create the picture object
Consumer consumer = new Consumer("John Doe"); // create the consumer object
consumer.setProfilePicture(profilePicture);
consumerFacade.create(consumer);

Related

Foreign key is being updated as null

I am trying to implement a bi-directional relationship using #OneToMany and #ManyToOne JPA annotation. My foreign key is being updated as NULL which is not correct. I need some input to resolve this.
I have created User and CardInfo class. I am trying to add a relationship where User can have more than one card. When I am trying to persist in database foreign key is being inserted as null.
#Entity
#Table(name = "customer_info")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
//#GeneratedValue
private String userId;
private String userName;
private Date dateOfBirth;
private boolean primeMember;
#OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
private Set<CardInfo> paymentDetails;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name="card_info")
public class CardInfo implements Serializable {
#Id
private String cardNumber;
#Id
private String cardType; // Debit, Credit
private String cardCategory; // Visa, mastercard
private Date expiryDate;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="user_id")
#EqualsAndHashCode.Include private User user;
public class DAOImpl {
#Transactional
public String addCustomer(User user) {
// User _user=new User();
// Set<CardInfo> cardData=new HashSet<>();
//
String userId=String.valueOf(Instant.now().toEpochMilli());
user.setUserId(userId);
Session session=sessionFactory.getCurrentSession();
session.persist(user);
return userId;
}
mysql> select * from card_info;
+----------+-------------+--------------+---------------------+---------+
| cardType | cardNumber | cardCategory | expiryDate | user_id |
+----------+-------------+--------------+---------------------+---------+
| CREDIT | 74959454959 | VISA | 2020-04-23 00:00:00 | NULL |
+----------+-------------+--------------+---------------------+---------+
1 row in set (0.00 sec)
user_id column should not be updated as NULL. Please correct me if understanding is not correct.
Although Cascade.PERSIST ensures that CardInfo objects will be persist together with their parent User, it is the responsibility of the application, or the object model to maintain relationships[1].
As the foreign key is in CardInfo, you have to ensure that every CardInfo is associated to the User that you are persisting. A common pattern is to add extra logic to handle both sides of the relationship in the domain object, e.g.:
public class User {
// fields, accessors and mutators
public void addPaymentDetails(CardInfo cardInfo) {
if (paymentDetails == null) {
paymentDetails = new LinkedHashSet<>();
}
if (cardInfo.getUser() != this) {
cardInfo.setUser(this);
}
paymentDetails.add(cardInfo);
}
}
The above code ensures that both sides of the relationship are in sync (i.e., if a user adds a card to its paymental details, then the card info is "owned" by the user).
Finally, while not directly related to your problem, my advice would be to make the relationship between CardInfo and User mandatory and its respective join column NOT NULL so that queries are properly optimised and no CardInfo can exist in the database without an association to its owning User:
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="user_id", nullable = false)

Hibernate Mapping: referencing a set of Entities with OneToMany and one Entity of that set with OneToOne

I'm trying to implement a Hibernate-relation, where an Entity A references a set of Entites B via #OneToMany. One of these said Entites B might have a special role and should therefore (optionally) be also referenced by A via #OneToOne.
Entity A:
public class Group {
#Id
#GeneratedValue
#Column(name = "id", updatable = false)
private int id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "group")
private List<GroupMember> members = new ArrayList<>();
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "groupSpokesman")
private GroupMember spokesman;
}
Entity B:
public class GroupMember {
#ManyToOne
#JoinColumn(name = "groupId")
private Group group;
}
Table group:
id | groupSpokesman | ...
The column groupSpokesman references the ID of that GroupMember, which is elected as a spokesman. If one of the GroupMembers is a Spokesman, he should remain in the set of GroupMembers. The column might be NULL, if no spokesman is elected and all GroupMembers are treated equally.
Table groupMember:
id | groupId | ...
The column groupId references the ID of the group, to which the GroupMember belongs.
Hibernate successfully saves the Entities. If none of the GroupMembers is a Spokesman, everything works fine. But as soon as one of the Entites in Group.members is also assigned to Group.spokesman, Hibernate sets the foreign key GroupMember.group for that particular GroupMember to NULL, which means that the Spokesman is no longer member of the Group. The foreign key Group.spokesman is correctly set to the ID of the Spokesman.
The mapping as shown in my initial post seems to be fine. The problem only occurs, if Entity A and all mapped Entities B are saved at once, e.g.
Group g = new Group();
GroupMember gm1 = new GroupMember();
GroupMember gm2 = new GroupMember();
g.addGroupMember(gm1); // gm1.group is null
g.addGroupMember(gm2);
g.setGroupSpokesman(gm1);
repository.save(g);
As soon, as I save the entity twice (first time before setting the group-spokesman; second time after that), everything works as expected:
Group g = new Group();
GroupMember gm1 = new GroupMember(); // gm1.group is set to ID of g
GroupMember gm2 = new GroupMember();
g.addGroupMember(gm1);
g.addGroupMember(gm2);
repository.save(g);
g.setGroupSpokesman(gm1);
repository.save(g);
Still, this looks like a workaround to me.

Multi valued attribute in Hibernate

I have the following entities:
____________________ ____________________
| Activity | | Benefit |
------------------ | |------------------|
| activityId:long |-------------| benefitId: long |
| activity:varchar | | activityId: long |
| .... | | benefit: varchar |
-------------------- -------------------|
Can I map this into Hibernate so I end up with this:
#Entity
class Activity {
#Id
private Long id;
private String activity;
private List<String> benefits;
}
Yes, you can use the #ElementCollection tag.
Here's what your code would look like:
#Entity
#Table(name = "Activity")
class Activity {
#Id
#Column(name="activity_id")
private Long id;
#Column(name = "name")
private String activity;
#ElementCollection
#CollectionTable(
name = "Benefit",
joinColumns = #JoinColumn(name = "activityId")
)
private List<String> benefits;
}
Though I would call the table ActivitiesBenefits instead of Benefit to make it clear that that table will store pairs of activities and benefits. Also, there is no need for a benefitId, since a benefit is a weak entity (it cannot exist without an activity), so you can drop that too.
Reference: https://en.wikibooks.org/wiki/Java_Persistence/ElementCollection
Hi I could find one way in which you could do it. Below is code for same.
#Entity
#Table(name = "Activity")
class Activity {
#Id
#Column(name="activity_id")
private Long id;
#Column(name = "name")
private String activity;
#OneToMany
#Formula("(select CONCAT(benefitId, activityId, benefit) from Benefit b where b.activityId = activity_id)")
private List<String> benefits;
}
I am using a sql returning list of benefits and then concatenate it and store it in our benefits list.
The string in formula is SQL(not HQL) so its column names and not filed member names.

Jpa - Two #OneToOne relations with same 'mappedBy'

I have two java-classes / db-tables: 'message' and 'thirdparty'
#Entity
public class Message {
#OneToOne(mappedBy = "message")
private ThirdParty source = null;
#OneToOne(mappedBy = "message")
private ThirdParty target = null;
....
}
#Entity
public class ThirdParty {
#OneToOne(targetEntity = Message.class)
#JoinColumn(name = "Message", referencedColumnName = "mess_id", nullable = false)
private Message message = null;
#Column(name = "isSource", nullable = false)
private Boolean isSource = null;
}
Message has two references to ThirdParty, which could be differenced by isSource (if they are source or target).
This cannot be resolved by jpa they way it is designed / annotiated. But is there a way to to this by adding some annotiation or some kind of special sql-statement?
This is conceptually wrong. You cannot do this. OneToOne mapping occurs when there are two entities mapped in the following way :
Entity1 : Has a primary key(PK1) and others along with a foreign key(FK)
Entity2 : Has a primary key (PK2).
Now the FK is mapped to PK2 in such a way that for each occurence of PK there must be a one and only one matching occurence of FK.

#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