JPA Add or update object - jpa

I'm trying to save an object called Stat using EclipseLink. If the id exists in the db, update that object. If not, create a new object. Here is my object:
#Entity
public class Stat {
#Column
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long id;
#JoinColumn
#ManyToOne
public User user;
#Column
public boolean accepted;
#Column
public boolean finished;
public Stat() {
}
}
And here is my method for adding/updating the object.
public long addReplaceStat(Stat stat) {
em.getTransaction().begin();
Stat oldStat = em.find(Stat.class, stat.id);
if (oldStat == null)
em.persist(stat);
else
em.merge(stat);
em.getTransaction().commit();
em.getTransaction().begin();
Stat newStat = em.find(Stat.class, stat.id);
if (newStat != null)
em.refresh(newStat);
em.getTransaction().commit();
return stat.id;
}
My problem is that I get exception at em.refresh() saying "The attribute [id] of class [User] is mapped to a primary key column in the database. Updates are not allowed." And I don't understand why. Shouldn't refresh just update the values of my managed object without problem?
What is the best idiom to do what I want (add or update)?

The JPA spec says:
The semantics of the merge operation applied to an entity X are as
follows:
If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new
managed copy X' of X is created.
If X is a new entity instance, a new managed entity instance X' is created and the state of X is copied into the new managed entity
instance X'.
[...]
So to add if not existing or update is it exists, you just need to do:
Stat attachedStat = em.merge(stat);
Regarding your problem with refresh, I don't know why it happens. But why are you refreshing an entity that you just loaded from the database? There is nothing to refresh.

Related

Why does JPA call sql update on delete?

Let´s assume these two entities:
#Entity
public class MyEntity {
#Id private String id;
#OneToMany(mappedBy = "myEntity", cascade = ALL) private Set<MyEntityPredecessor> predecessors;
}
#Entity
public class MyEntityPredecessor{
#Id private String id;
#ManyToOne(name = "entityID", nullable = false) private MyEntity myEntity;
#ManyToOne(name = "entityPre", nullable = false) private MyEntity predecessor;
}
When I try to call a delete with Spring Boot Data (JPA) with a MyEntity Instance, it will work some times (I see the select and then the delete statements in correct order), but sometimes it will try to run an update on the second entity trying to set the "entityPre" Field to null (even thoug it is set to nullable=falsE), causing the DB to send an error (null not allowed!! from DB constraint).
Strangely, this will happen at "random" calls to the delete...
I just call "myEntityRepository.getOne(id)", and then myEntityRepository.delete() with the result... There is no data difference in the DB between calls, the data structure has no null values when calling the delete method, so that should not be the reason.
Why is JPA sometimes trying to call updates on the Predecessor Table, and sometimes directly deleting the values? Am I missing something?
Add a similar ManyToOne annotated set to MyEntity which refers to the other non-nullable property, like:
#OneToMany(mappedBy = "predecessor", cascade = ALL) private Set<MyEntityPredecessor> other;
some explanation:
The issue doesn't happen randomly, but happen when you try to delete an entity which is linked to one (or more) MyEntityPredecessor via the predecessor property (which is mapped to the entityPre field)
Only the other field (entityID) is mapped back to the MyEntity object, so the deletion-cascade only happens via by that field.

Spring Boot transactional test allows to violate unique constraint on update

I have a DB (Postgres) table with a unique constraint for one column. I have a test marked with #Transactional annotation, that updates that unique column value to a not unique value. I expect that the update operation should fail, but it executes successfully. Moreover, when I get updated object from the database (inside the same transaction), the column value is updated there.
The simplified version of JPA entity:
#Entity
#Table(name = "entities")
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
// The unique column
#Column(name = "name", unique = true)
#NotNull
private String name;
...
}
The simplified version for the test:
#Test
#Transactional
public void test() {
Entity firstEntity = new Entity();
firstEntity.setName("First Entity Name");
// This just calls corresponding JPA repository .save method
entityService.create(firstEntity);
Entity secondEntity = new Entity();
secondEntity.setName("Second Entity Name");
entityService.create(secondEntity);
// Update name to a not unique value
secondEntity.setName(firstEntity.getName);
// This calls corresponding JPA repository .save method.
// It also catches DataIntegrityViolationException and throws
// a more user friendly exception instead
entityService.update(secondEntity);
}
This code works as I expect, if #Transactional annotation is removed or transaction is committed. I also tried to call EntityManager.flush(), as advised here, but this code throws ConstraintViolationException after resulting data is flushed, so I can't test that my entityService.update method works correctly and throws proper exception.
Please also note that if I try to create a new entry with not unique data in transactional test (not update), then test works as expected -
DataIntegrityViolationException is thrown when not unique entity is created.
Could somebody clarify if it is possible to make update scenario work as expected keeping test transactional so I don't need to care about data clean up?

jpa: when merging many to many previous record gets deleted

i have a Users and Tags table,and also a user_tag_xref table that holds the many to many relationship.now netbeans generates the entity classes for me (using eclipselink) below is the entity mapping relationship
on User class
#ManyToMany(mappedBy = "usersList")
private List<Tags> tagsList;
on Tags class
#JoinTable(name = "USERS_TAG_XREF", joinColumns = {
#JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID")}, inverseJoinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")})
#ManyToMany
private List<Users> usersList;
Now im my business logic RESTfull service,a json client consumes this method
#POST
#Path("/registration2/tag")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response registration2_3(List<Tags>tagList,#Context HttpServletRequest req){
Profile p =(Profile) registerMap.get(req.getSession().getId());
Users u = em.find(Users.class,p.getUserId());
for(Tags t : tagList){
t.getUsersList().add(u);
u.getTagsList().add(t);
em.merge(t);
em.merge(u);
}
logger.log(Level.INFO, "the taglist created for user");
return Response.ok(u,MediaType.APPLICATION_JSON).build();
}
The problem is each time i merge a new user to create a many to many relationship, if an existing userid=6201 has a tag with 2,3,4 ,and the new user is try to use to tag id 2,3,4,
the existing user is deleted and the new user merges to the tags. i have read several articles on overriding hash and equals to method in my entity classes,those methods are overridden by default in eclipselink ,also i cannot change my collection type to Set or Collection since List<> type works perfectly well for a json array. i v been having a hard time right now,its been 24hours,could it be the default mapping strategy is wrong? do i need to cascasde?
You have to be extra cautious while using merge as its semantics ( as explained here) is bit different from just update.
As your relationship is bidirectional and users is the inverse side, so all of relationship persistence will be handled by
tags side. Assuming that you tag lists contains detached tags, meaning all Tags have their id set, then you need to iterate over tagList
Tag managedTag = em.merge(t);
This takes care that if t is new instance (unmanaged) then a persistent representation of it will be returned
which has to be used there after or if the instances were having their id set, then the ORM will create a managed instance with data from database ( or from first/second level cache if it exists there). The returned instance is the one managed
for(Tags t : tagList){
Tag managedTag = em.merge(t);
managedTag.getUsersList().add(u);
u.getTagsList().add(t);
User managedUser = em.merge(u);
}
Also you can set the Merge cascade option on the Tag side to save you the second merge call and let the ORM manage relationship automatically.
Here is how merge behaves with detached entities and relations.
#Entity
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(cascade=CascadeType.ALL)
private List<User> users = new ArrayList();
.......................
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(mappedBy="users",cascade=CascadeType.ALL)
private List<Tag> tags = new ArrayList();
........................
}
TestProgram
EntityManagerFactory emf = Persistence.createEntityManagerFactory("Test");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
User user = new User();
user.setName("User");
User managedUser = em.merge(user);
int userId = managedUser.getId();
//create a tag
Tag tag = new Tag();
tag.setName("Tag");
tag.addUser(managedUser);
Tag managedTag = em.merge(tag);
//save the id locally
int savedId = managedTag.getId();
//managed tag was sent to UI where its name will be changed to "Changed Tag"
em.getTransaction().commit();
em.close();
//create another transaction
EntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();
//simulate a tag sent form UI
Tag tagFromUI = new Tag();
tagFromUI.setId(savedId);
tagFromUI.setName("Changed Tag");
// I want to associate a new user to this tag
// so create a new user
User newUser = new User();
newUser.setName("newUser");
tagFromUI.addUser(newUser);
Tag managedTagFromUI = em1.merge(tagFromUI);
em1.getTransaction().commit();
em1.close();
emf.close();
Here is the SQL generated and corresponding explanation
//First transaction begins
insert into User (name) values ('User');
insert into Tag (name) values ('Tag');
insert into Tag_User (tags_id, users_id) values (1, 1);
//First transaction ends
//Second transaction begins
// Since a detached tag is merged, hibernate queries for tag id 1 to load it in persistent context
//This tag is associated with a user
select tag0_.id as id1_3_1_, tag0_.name as name2_3_1_, users1_.tags_id as tags_id1_3_3_, user2_.id as users_id2_4_3_, user2_.id as id1_5_0_, user2_.name as name2_5_0_ from Tag tag0_ left outer join Tag_User users1_ on tag0_.id=users1_.tags_id left outer join User user2_ on users1_.users_id=user2_.id where tag0_.id=1;
//copies the state of detached tag from UI to the managed tag and sends update
update Tag set name='Changed Tag' where id=1;
//since merge is cascaded, hibernate looks for the user list of supplied tag and sees an transient User
// the transient instance is merged (created new in database as it is not yet persisted)
insert into User (name) values ('newUser');
// merge is called on this new managed instance and the resulted instance is set in the managed Tag instance automatically
//but for this the old relation has to be broken
delete from Tag_User where tags_id=1;
// and the new relation has to be added in database
insert into Tag_User (tags_id, users_id) values (1, 2);
//second transaction ends
while Adding new entries you need to validate that the mappings are not duplicate or already present in the db.For more clarity on the solution plz take a look into my soultion provided in the link
Relationship table data overwritten in many-to-many hibernate

JPA not updating ManyToMany relationship in returning result

Here are my entities:
#Entity
public class Actor {
private List<Film> films;
#ManyToMany
#JoinTable(name="film_actor",
joinColumns =#JoinColumn(name="actor_id"),
inverseJoinColumns = #JoinColumn(name="film_id"))
public List<Film> getFilms(){
return films;
}
//... more in here
Moving on:
#Entity
public class Film {
private List actors;
#ManyToMany
#JoinTable(name="film_actor",
joinColumns =#JoinColumn(name="film_id"),
inverseJoinColumns = #JoinColumn(name="actor_id"))
public List<Actor> getActors(){
return actors;
}
//... more in here
And the join table:
#javax.persistence.IdClass(com.tugay.sakkillaa.model.FilmActorPK.class)
#javax.persistence.Table(name = "film_actor", schema = "", catalog = "sakila")
#Entity
public class FilmActor {
private short actorId;
private short filmId;
private Timestamp lastUpdate;
So my problem is:
When I remove a Film from an Actor and merge that Actor, and check the database, I see that everything is fine. Say the actor id is 5 and the film id is 3, I see that these id 's are removed from film_actor table..
The problem is, in my JSF project, altough my beans are request scoped and they are supposed to be fetching the new information, for the Film part, they do not. They still bring me Actor with id = 3 for Film with id = 5. Here is a sample code:
#RequestScoped
#Named
public class FilmTableBackingBean {
#Inject
FilmDao filmDao;
List<Film> allFilms;
public List<Film> getAllFilms(){
if(allFilms == null || allFilms.isEmpty()){
allFilms = filmDao.getAll();
}
return allFilms;
}
}
So as you can see this is a request scoped bean. And everytime I access this bean, allFilms is initially is null. So new data is fetched from the database. However, this fetched data does not match with the data in the database. It still brings the Actor.
So I am guessing this is something like a cache issue.
Any help?
Edit: Only after I restart the Server, the fetched information by JPA is correct.
Edit: This does not help either:
#Entity
public class Film {
private short filmId;
#ManyToMany(mappedBy = "films", fetch = FetchType.EAGER)
public List<Actor> getActors(){
return actors;
}
The mapping is wrong.
The join table is mapped twice: once as the join table of the many-to-many association, and once as an entity. It's one or the other, but not both.
And the many-to-many is wrong as well. One side MUST be the inverse side and use the mappedBy attribute (and thus not define a join table, which is already defined at the other, owning side of the association). See example 7.24, and its preceeding text, in the Hibernate documentation (which also applies to other JPA implementations)
Side note: why use a short for an ID? A Long would be a wiser choice.
JB Nizet is correct, but you also need to maintain both sides of relationships as there is caching in JPA. The EntityManager itself caches managed entities, so make sure your JSF project is closing and re obtaining EntityManagers, clearing them if they are long lived or refreshing entities that might be stale. Providers like EclipseLink also have a second level cache http://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching

Eclipselink performs an unexpected insert in a many-to-one relationship

I have a very basic relationship between two objects:
#Entity
public class A {
#ManyToOne(optional = false)
#JoinColumn(name="B_ID", insertable=false, updatable=true)
private StatusOfA sa;
getter+setter
}
#Entity
public class StatusOfA {
#Id
private long id;
#Column
private String status;
getter+setter
}
There's only a limited set of StatusOfA in DB.
I perform an update on A in a transaction:
#TransactionalAttribute
public void updateStatusOfA(long id) {
A a = aDao.getAById(123);
if(a != null) {
a.getStatusOfA().getId(); //just to ensure that the object is loaded from DB
StatusOfA anotherStatusOfA = statusOfADao.getStatusOfAById(456);
a.setStatusOfA(aontherStatusOfA);
aDao.saveOrPersistA(a);
}
}
The saveOrPersistA method is here merging 'a'.
I expect Eclipselink to perform only an update on 'a' to update the StatusOfA but it's executing a new insert on StatusOfA table. Oracle is then complaining due to a unique contraint violation (the StatusOfA that Eclipselink tries to persist already exists...).
There is no Cascading here so the problem is not there and Hibernate (in JPA2) is behaving as excepted.
In the same project, I already made some more complex relationships and I'm really surprised to see that the relation here in not working.
Thanks in advance for your help.
What does, statusOfADao.getStatusOfAById() do?
Does it use the same persistence context (same transaction and EntityManager)?
You need to use the same EntityManager, as you should not mix objects from different persistence contexts.
What does saveOrPersistA do exactly? The merge() call should resolve everything correctly, but if you have really messed up objects, it may be difficult to merge everything as you expect.
Are you merging just A, or its status as well? Try also setting the status to the merged result of the status.
Assumptions: #Id#GeneratedValue(strategy = GenerationType.IDENTITY)
Let's consider the following implementations of statusOfADao.getStatusOfAById(456) :
1. returns "proxy" object with just id set:
return new StatusOfA(456);
2. returns entity in new transaction:
EntityManager em = emf.createEntityManager();em.getTransaction().begin();
StatusOfA o = em.find(StatusOfA.class,456);//em.getReference(StatusOfA.class,456);
em.getTransaction().commit();
return o;
3. returns detached entity:
StatusOfA o = em.find(StatusOfA.class,456);//em.getReference(StatusOfA.class,456);
em.detached(o);
return o;
4. returns deserialized-serialized entity:
return ObjectCloner.deepCopy(em.find(StatusOfA.class,456));
5. returns attached entity:
return em.find(StatusOfA.class,456);
Conclusions:
Eclipselink handles only implementation N5 as "expected".
Hibernate handles all five implementations as "expected".
No analisys of what behaviour is jpa spec compliant