#ManyToOne mapping fails to save parent ID - java

I'm using JPA2 with EclipseLink implementation
![Simple table structure][1]
Here are the two tables which I try to map and the JPA annotations.
public class Story implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
Integer id;
#Temporal(TemporalType.TIMESTAMP)
#Column (name="DATE_CREATED")
Date dateCreated;
String title;
String description;
#Column(name="AUTHOR_ID")
Integer authorId;
#Column(name="COUNTRY_ID")
Integer countryId;
private String reviews;
#OneToMany(mappedBy = "story", cascade=CascadeType.ALL)
private List<Tip> tipList;
}
public class Tip implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String description;
private Integer vote;
#ManyToOne (cascade=CascadeType.ALL)
#JoinColumn(name="STORY_ID", referencedColumnName="ID")
private Story story;
}
As a simple example I would like to persist a story and some story related tips in the same transaction.
Here is the section of code which does that:
Story newStory = new Story(title, body, ...);
EntityTransaction transaction = em.getTransaction().begin();
boolean completed = storyService.create(newStory);
//The tips are saved as a List<String>. This methods creates the needed List<Tip> from the Strings
List<Tip> tips = TipUtil.getTipList(tipList);
newStory.setTipList(tips)
transaction.commit();
I have no errors and all the entities are persisted in the database. The problem is that in the tip table the story_id field is always NULL. I can imagine that JPA is unable to get the new id from the story table. What's the correct approach here?
LE
In the current state of the code, the Tip entities are persisted but the country ID remains null.

With JPA, it is always recommended to update the relationship on both the sides in a bi-directional relationship. This is to ensure that the data is consistent in your application layer and nothing to do with the database.
However it is mandatory that you update the owning side of the relationship in a bidirectional relationship.
So, setting/not setting
story.setTipList(tips)
is up to you. But if you want the changes to reflect properly in DB then you mush call
tip.setStory(story)
as Tip is the owning side here, as per your code.
Also your code looks incomplete to me. Reasons is,
the entity returned by storyService.create(newStory) is managed but not the newStory. So just setting newStory.setTipList(tips) will not updated the db

Because you need to update the parent link story in each of your child.
The way its is done is to create a addTip(Tip tip) method in your Story class.
This method does :
tip.setStory(this);
tipList.add(tip);
If you don't need bedirectional approach, you can remove the story field in Tip and it will resolve your problem

Remove the
#Column(name = "STORY_ID")
private Integer storyId;
You are already declaring it in #JoinColumn(name="STORY_ID", referencedColumnName="ID")
That is why you are getting the error Multiple writable mappings exist for the field [tip.STORY_ID]

You should not be using PrimaryKeyJoinColumn, just JoinColumn, but having your complete class would help giving a certain answer.
PrimaryKeyJoinColumn would only be used if the story_id was also the id of the Tip (no id in Tip) and there was a duplicate basic mapping for it. It should rarely be used, and is not required in JPA 2.0 anymore as duplicate id mappings are no longer required.

Related

How to properly delete a record from a database in one query? JPA

How to properly delete a record from a database in one query. For example, when an entity uses the primary key of the parent entity using the #MapsId annotation, if the parent entry is deleted, it will swear that the parent's id is used in the child entity.
Code example :
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
}
#Entity
public class UserDetails {
#Id
private long id;
private String phone;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User user;
}
Here, when deleting a User using the JpaRepository delete method, an error will occur that the UserDetail uses the primary key User
First, are you sure the direction of the relation makes sense? I would have expected it to be the other way around, because the user ID and name seem to be the more basic info that you need more often.
Second, what you're doing seems like an attempt to optimize performance, because you could just as well store all the data in a single entity. Are you sure the optimization pays off? (I would guess not.) See Premature Optimization.
Third, if the relation was the other way around, you could modify the annotation to #OneToOne(cascade=CascadeType.DELETE) or #OneToOne(cascade=CascadeType.ALL) to let JPA automatically delete the other entity when the first is deleted.
For that you need to delete all foreign keys with used by primary key
or by using cascade
After that use below
In JPA we can use deleteById
or by named query
DELETE FROM EMPLOYEE WHERE ID = ?1
or my native query
delete from employee where id = ?1

Java #Column-Annotation change the name of columns in Code

I have an Entity-Class (#Entity) where I have specified Columns(#Column) for OracleDB. It is possible to change the names of columns in code? And has it any affects to the existing database? Because the database contains actually lot of data.
I can't test it by myself now, because I do not have access to database actually.
Can anyone help me resp. can you give me some tips where can I search for a answer for my question?
Entity-Class like this:
#Entity
public class Example {
#Id
#GeneratedValue
private long id;
#Column
private String firstName;
#Column
private Date dateTime;
//Setter / Getter
}
If column in you db is as
#Column(name = "name_you_have_in_db")
If you will do
#Column(name="name_you_want")
then it is not possible because I guess hibernate doesn't track renames. It will create new field into the DB.
So solution is stop your app, alter your table and start your app again.

Need Design help with POJO's used for JPA services and REST Client's

I have a set of JPA POJO's that contain annotations required for mapping to my domain. I also want to expose some REST services that will interact with those domain objects.
My current task is to create an android application to access these REST services. I am not able to use the domain object due to the JPA annotations they contain. The Dalvik compiler complains.
So I am looking for a strategy to be able to leverage these domain objects in a way that an Android project can also use those objects and not have to duplicate those POJO's.
Victor's suggestion to externalise the JPA mappings to XML rather than use annotations would surely work, but might be inconvenient if you're getting your JPA objects from tooling that only generates annotations.
I assume that you need on the client side Java classes that match the objects you will serialise in your REST services.
It is possible, but very tedious, to create DTO objects - POJOs exactly matching the JPA objects with suitable constructors from the JPA objects. This seems like an undue amount of effort.
It must be possible to write a source-code processor to strip the annotations from the Java. I don't think a simple regex scripting solution will work, I guess that truly parsing the source is necessary, so I hesitate to guess how much work this would be. However according to this question's answers the basic set of tools is available. I would start with this approach.
I could work out with following strategy.
This strategy works very well when you dont want fetch whole collection , or fetch with some addition criteria,
, you may retrieve it(collection relation) with named query.
use separate DAO for CRUD operation on JOIN table of many to many relation
e.g.
User can have many accounts and account can be shared by many users.
create domain models/ DAO for all the three tables,
use relation mapping for just retrieval and for DDL use individual properties.
#Entity
#Table(name="account" )
public class Account {
#Id (name="accountid")
private Long accountId;
#Column
private String type;
// removed #OneToMany as it causes issue while serializing to xml
#Transient
private Collection accountUsers;
//rest of the properties n geter setter goes here
}
#Entity
#Table(name="user")
public class User {
#Id(name="userid")
private Long userId;
#Column
private String name;
// by making transient jpa / hibernate does not initialize it with proxy.. so it remains null
/* you can populate this property using named query whenever required .*/
#Transient
private Collection userAccounts;
// rest of the properties n getter setter goes here
}
#Entity
#Table(name="AccountUser")
public class AccountUser {
// whatever your strategy to implement primary key here ,
#Id (name="accountuserid")
private Long accountUserId;
/* please note this annotation , made insertable/updatable false , relation defined just for fetching relation
*/
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "accountid", referencedColumnName = "accountid", insertable = false, updatable = false)
private Account account;
// look at insertable / updatable properties its turned off
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "userid", referencedColumnName = "userid", insertable = false, updatable = false)
private User user;
//
#Column ( name = "userid" )
private Long userId;
#Column ( name = "accountid" )
private Long accountId;
#Column ( name="opendate")
private Date opendate;
}
/* use separate dao to save above beans */
// somthing like this
public class AccountDAOImpl extends GenericDAOImpl implements AccountDAO {
}
public class UserDAOImpl extends GenericDAOImpl implements UserDAO {
}
public class AccountUserDAOImpl extends GenericDAOImpl implements AccountUserDAO {
}
I tried to explain if need any clarification kindly revert back. thanks

EJB3/JPA entity with an aggregated attribute

I wanted to know if there is a way to get in a One2Many relationship a field of the One side that is an aggregate of the Many side.
Let's take the following example:
#Entity
public class A {
#Id
private Long id;
#OneToMany (mappedBy="parentA")
private Collection<B> allBs;
// Here I don't know how to Map the latest B by date
private B latestB;
// Acceptable would be to have : private Date latestBDate;
}
#Entity
public class B {
#Id
private Long id;
private Date date;
#ManyToOne (targetEntity=A.class)
private A parentA;
}
My question is how can I make the mapping of the field latestB in the A entity object without doing any de-normalization (not keeping in sync the field with triggers/listeners)?
Perhaps this question gives some answers, but really I don't understand how it can work since I still want to be able to fetch all childs objects.
Thanks for reading/helping.
PS: I use hibernate as ORM/JPA provider, so an Hibernate solution can be provided if no JPA solution exists.
PS2: Or just tell me that I should not do this (with arguments of course) ;-)
I use hibernate as ORM/JPA provider, so an Hibernate solution can be provided if no JPA solution exists.
Implementing the acceptable solution (i.e. fetching a Date for the latest B) would be possible using a #Formula.
#Entity
public class A {
#Id
private Long id;
#OneToMany (mappedBy="parentA")
private Collection<B> allBs;
#Formula("(select max(b.some_date) from B b where b.a_id = id)")
private Date latestBDate;
}
References
Hibernate Annotations Reference Guide
2.4.3.1. Formula
Resources
Hibernate Derived Properties - Performance and Portability
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Filtering.2C_Complex_Joins
Basically JPA does not support this, but some JPA providers do.
You could also,
- Make the variable transient and lazy initialize it from the OneToMany, or just provide a get method that searches the OneToMany.
- Define another foreign key to the latest.
- Remove the relationship and just query for the latest.

JPA Cascading Delete: Setting child FK to NULL on a NOT NULL column

I have two tables: t_promo_program and t_promo_program_param.
They are represented by the following JPA entities:
#Entity
#Table(name = "t_promo_program")
public class PromoProgram {
#Id
#Column(name = "promo_program_id")
private Long id;
#OneToMany(cascade = {CascadeType.REMOVE})
#JoinColumn(name = "promo_program_id")
private List<PromoProgramParam> params;
}
#Entity
#Table(name = "t_promo_program_param")
public class PromoProgramParam {
#Id
#Column(name = "promo_program_param_id")
private Long id;
//#NotNull // This is a Hibernate annotation so that my test db gets created with the NOT NULL attribute, I'm not married to this annotation.
#ManyToOne
#JoinColumn(name = "PROMO_PROGRAM_ID", referencedColumnName = "promo_program_id")
private PromoProgram promoProgram;
}
When I delete a PromoProgram, Hibernate hits my database with:
update
T_PROMO_PROGRAM_PARAM
set
promo_program_id=null
where
promo_program_id=?
delete
from
t_promo_program
where
promo_program_id=?
and last_change=?
I'm at a loss for where to start looking for the source of the problem.
Oh crud, it was a missing "mappedBy" field in PromoProgram.
Double-check whether you're maintaining bidirectional association consistency. That is; make sure that all PromoProgramParam entities that link to a PromoProgram as its parent are also contained in said parent's params list. It's a good idea to make sure this happens regardless of which side "initiates" the association if you will; if setPromoProgram is called on a PromoProgramParam, have the setter automatically add itself to the PromoProgram's params list. Vice versa, when calling addPromoProgramParam on a PromoProgram, have it set itself as the param's parent.
I've encountered this problem before as well, and it was due to not maintaining bidirectional consistency. I debugged around into Hibernate and found that it was unable to cascade the delete operation to the children because they weren't in the list. However, they most certainly were present in the database, and caused FK exceptions as Hibernate tried to delete only the parent without first deleting its children (which you've likely also encountered with the #NonNull in place).
FYI, I believe the proper "EJB 3.0"-way of making the PromoProgramParam.promoProgram field (say that a 100 times) non-nullable is to set the optional=false attribute on the #ManyToOne annotation.

Categories