I'm using JPA with Hibernate 5.2.10.Final (Oracle database), and deploying on Weblogic 12.2.1.
Let's say I have 2 tables: Customer and LastActivity:
Customer {
id int,
name String,
last_activity_id int not null
}
LastActivity {
id int,
customerName String,
date Date
}
There is a One to Many relationship: a Customer has a single Activity and one Activity has many Customers.
I have a functionality of adding a Customer, when it happens the record in LastActivity table must be created if it doesn't exist for that Customer, otherwise the date must be updated.
My code looks like this (simplified for the purpose of the question):
public Response createCustomer(Request request) {
String name = request.getName();
Customer customer = new Customer(name);
LastActivity activity = activityDao.findByCustomerName(name)
.orElseGet(LastActivity.from(name));
activity.setDate(ZonedDateTime.now());
customer.setActivity(activityDao.update(activity));
return Response.of(customer);
}
My update method is straightforward:
return entityManager.merge(entity);
When I add a new Customer and an Activity that doesn't exist yet ― it is created correctly with the date I specified. The problem is when the activity already exists ― the update doesn't happen. In the logs there is just a select query on Activities table, then correct insert on Customers table, but the date is old.
Some things I tried:
public T update(T entity) {
EntityManager manager = getEntityManager();
T updated = manager.contains(entity) ? entity : manager.persist(entity);
manager.flush();
return updated;
}
Same thing, nothing changed. Also:
Without flushing
Doing merge instead of just returning entity when contains returns true
Just a flush by itself
Nothing since the entity is "attached"
Tried adding CascadeType.MERGE...still nothing. Only thing that worked was this:
public T update(T entity) {
EntityManager manager = getEntityManager();
manager.detach(entity);
return manager.merge(entity);
}
It did what I wanted it to do, but it added extra select query on Activity table (simply by ID, but still, I would like to avoid that).
I actually managed to "solve" the problem by using CriteriaUpdate, but I don't like this and it seems like I lack of some fundamental knowledge about JPA/Hibernate so I don't just want to leave it like this.
Related
I have the following code that first check record and if found delete that record and flush changes to the database. However, when I debug, I see that it does not reflect changes to the database when debugger hit the next code block (final Stock stock = new Stock();).
#Transactional
public CommandDTO createOrUpdate(StockRequest request) {
stockRepository.findByBrandUuidAndProductUuid(
request.getBrandUuid(),
request.getProductUuid())
.ifPresent(stock -> {
stockRepository.delete(stock);
stockRepository.flush();
});
final Stock stock = new Stock();
if (request.isOutOfStock()) {
stock.setBrandUuid(request.getBrandUuid());
stock.setProductUuid(request.getProductUuid());
stock.save(stock);
}
return CommandDTO.builder().uuid(stock.getUuid()).build();
}
So, what is the mistake in this approach?
JPA doesn't supports final field.
You can use two alternative solution for immutable class.
use #Immutable at entity class.
change entity class fields having only a getter.
I have a problem which I try to figure out since many hours now.
I must save a model with manual set id in the database using CrudRepository and Hibernate.
But the manual set of the id is ignored always.
Is it somehow possible, to force
CrudRepository.save(Model m)
to persist the given Model with UPDATE?
The queries always results in INSERT statements, without using the id.
The reason I must do this manually is, that the identifier is not the database ID - it is a ID generated outside as UUID which is unique over multiple databases with this model-entry. This model is shared as serialized objects via hazelcast-cluster.
Following an example:
The database already contains a Model-Entry with the id 1:
id identifier_field_with_unique_constraint a_changing_number
1 THIS_IS_THE_UNIQUE_STRING 10
Now I need to update it. I create a new Model version
Model m = new Model();
m.setIdentifierFieldWithUniqueConstraint(THIS_IS_THE_UNIQUE_STRING);
m.setAChangingNumberField(20);
saveMe(m);
void saveMe(Model m) {
Optional<Model> presentModalOpt = modelCrudRepo.findByIdentField(THIS_IS_THE_UNIQUE_STRING)
if(presentModalOpt.isPresent()) {
// The unique value in my identifier field exists in the database already
// so use that id for the new model, so it will be overwritten
m.setId(modalOpt.get().getId());
} else {
m.setId(null);
}
// This call will now do an INSERT, instead of UPDATE,
// even though the id is set in the model AND the id exists in the database!
modelCrudRepo.save(m);
// ConstraintViolationException for the unique identifier field.
// It would be a duplicate now, which is not allowed, because it uses INSERT instead of UPDATE
}
The id Field is tagged with #Id and #GeneratedValue annotation (for the case that the id is null and the id should be generated)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
I even tried to changed this field only to an #Id field without #GeneratedValue and generate the ID always on my own. It had no effect, it always used INSERT statements, never UPDATE.
What am I doing wrong?
Is there another identifier for the CrudRepository that declares the model as an existing one, other than the id?
I'm happy for any help.
CrudRepository has only save method but it acts for both insert as well as update.
When you do save on entity with empty id it will do a save.
When you do save on entity with existing id it will do an update
that means that after you used findById for example and changed
something in your object, you can call save on this object and it
will actually do an update because after findById you get an object
with populated id that exist in your DB.
In your case you are fetching the records based on a field (unique) But records will update only when the model object has a existing primary key value
In your code there should be presentModalOpt instead of modalOpt
void saveMe(Model m) {
Optional<Model> presentModalOpt = modelCrudRepo.findByIdentField(THIS_IS_THE_UNIQUE_STRING)
if(presentModalOpt.isPresent()) { // should be presentModalOpt instead of modalOpt
} else {
m.setId(null);
}
modelCrudRepo.save(m);
}
See the default implementation -
/*
* (non-Javadoc)
* #see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
I am new to spring data jpa. I have a scenario where I have to create an entity if not exists or update based on non primary key name.Below is the code i wrote to create new entity,it is working fine,but if an already exists record ,its creating duplicate.How to write a method to update if exists ,i usually get list of records from client.
#Override
#Transactional
public String createNewEntity(List<Transaction> transaction) {
List<Transaction> transaction= transactionRespository.saveAll(transaction);
}
Add in your Transaction Entity on variable called name this for naming as unique:
#Entity
public class Transaction {
...
#Column(name="name", unique=true)
private String name;
...
}
Then you won't be able to add duplicate values for name column.
First, this is from google composite key means
A composite key is a combination of two or more columns in a table that can be used to uniquely identify each row in the table when the columns are combined uniqueness is guaranteed, but when it taken individually it does not guarantee uniqueness.
A composite key with an unique key is a waste.
if you want to update an entity by jpa, you need to have an key to classify if the entity exist already.
#Transactional
public <S extends T> S save(S entity) {
if(this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
There are two ways to handle your problem.
If you can not get id from client on updating, it means that id has lost its original function. Then remove your the annotation #Id on your id field,set name with #Id. And do not set auto generate for it.
I think what you want is an #Column(unique = true,nullable = false) on your name field.
And that is the order to update something.
Transaction t = transactionRepository.findByName(name);
t.set.... //your update
transactionRepository.save(t);
I have 2 entities with many-to-many relationship. The Movie Entity is the owner of this relation, so when I want to delete an Actor Entity I use a method annotated #PreRemove to delete any occurrences of Actor ID in Movie cast to avoid "Foreign key violation exception".
Movie class
#Entity
public class Movie extends AbstractBusinessObject{
#ManyToMany
private Map<String, Actor> cast;
// setters and getters
public void removeCastMember(Actor actor){
for (Entry<String, Actor> e : cast.entrySet()) {
if(e.getValue().id.equals(actor.id)){
cast.remove(e.getKey());
}
}
} // removeCastMember()
}
Actor class
#Entity
public class Actor extends AbstractBusinessObject{
#ManyToMany(mappedBy = "cast")
private Set<Movie> movies;
// setters and getters
#PreRemove
private void removeActorFromMovies() {
for (Movie m : movies) {
m.removeCastMember(this);
}
}
}
To be clear, from my testing, it works - movie objects are correctly updated in the database. However, I cannot understand how is it possible when there are no calls to saveOrUpdate() or persist/merge those objects.
That's a fundamental feature of JPA/Hibernate. All the changes made to attached entities are automatically made persistent: Hibernate manages them, so it compares their current state with their initial state, and automatically makes all the changes persistent.
This is extremely useful, because you don't have to track all the entities that have been modified in a complex business method modifying lots of entities. And it's also efficient because Hibernate won't execute unnecessary SQL: if an entity hasn't changed during the transaction, no SQL update query will be executed for this entity. And if you modify entities and then throw an exception rollbacking the transaction, Hibernate will skip the updates.
So, typical JPA code would look like this:
void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account from = em.find(Account.class, fromAccountId); // from is managed by JPA
Account to = em.find(Account.class, ftoAccountId); // to is managed by JPA
from.remove(amount);
to.add(amount);
// now the transaction ends, Hibernate sees that the state of from and to
// has changed, and it saves the entities automatically before the commit
}
persist() is used to make a new entity persistent, i.e. to make it managed by Hibernate.
merge() is used to take a detached entity (i.e. an entity which is not managed by Hibernate, but already has an ID and a state) and to copy its state to the attached entity having the same ID.
i've hit a block once again with hibernate.I've posted numerous times on different aspects of the user and contact management that i've been building.
The sad thing is that i didn't really have the time to play with it and understand it better before actually starting working with it. Sorry but English is not my native language, i rather speak french. And again i've started coding in java in an autodidact way.i'm doing all of this by reading books and haven't gone to school for it. with time constraints it's hard to read a book from beginning to the end.
I'm not sure i should put every of my codes dealing with an issue here and from what i've learned from other forum is to post just the necessary and being concise.
So in my User model i have UserAccount class, Profile that holds details like name, preferences etc , AccountSession and Phone.
my contact management model have Contact and Group.
UserAccount has one-to-one association with Profile, one-to-many with AccountSession,contact and group, all bidirectional.the one-to-many association with phone is unidirectional because contact also has and unidirectional with Phone.
Contact has a bidirectional many-o-many with group and one-to-many with phone that i said earlier.
Group also has a many-to-many bedirectional with contact.
here are the mappings
// UserAccount
......
#OneToOne(targetEntity=UserProfileImpl.class,cascade={CascadeType.ALL})
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#JoinColumn(name="USER_PROFILE_ID")
private UserProfile profile;
#OneToMany(targetEntity=ContactImpl.class, cascade={CascadeType.ALL}, mappedBy="userAccount")
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Contact> contacts = new HashSet<Contact>();
#OneToMany(targetEntity=GroupImpl.class, cascade={CascadeType.ALL}, mappedBy="userAccount")
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Group> groups = new HashSet<Group>();
.......
//Group
#ManyToOne(targetEntity=UserAccountImpl.class)
#JoinColumn(name="USER_ACCOUNT_ID",nullable=false)
private UserAccount userAccount;
#ManyToMany(targetEntity=ContactImpl.class,cascade={CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name="GROUP_CONTACT_MAP", joinColumns={#JoinColumn(name="GROUP_ID")},
inverseJoinColumns={#JoinColumn(name="CONTACT_ID")})
private Set<Contact> contacts = new HashSet<Contact>();
//Contact
....
#ManyToOne(targetEntity=UserAccountImpl.class)
#JoinColumn(name="USER_ACCOUNT_ID",nullable=false)
private UserAccount userAccount;
#ManyToMany(targetEntity=GroupImpl.class, mappedBy="contacts")
private Set<Group> groups=new HashSet<Group>();
....
// helper methods from group
public void addContact(Contact contact) {
try{
this.getContacts().add(contact);
contact.getGroups().add(this);
}catch(Exception e) {
}
}
//helper method from group
public void removeContact(Contact contact) {
contact.getGroups().remove(contact);
this.getContacts().remove(contact);
}
//helper method from contact
public void addGroup(Group group) {
try{
this.getGroups().add(group);
group.getContacts().add(this);
} catch(Exception e) {
e.printStackTrace();
}
}
//Helper method from group
public void removeGroup(Group group){
try{
group.getContacts().remove(this);
this.getGroups().remove(group);
} catch(Exception e) {
e.printStackTrace();
}
}
//UserAccount setter from Contact.All the children with many-to-one have the same
/**
* #param userAccount the userAccount to set
*/
public void setUserAccount(UserAccount userAccount) {
this.userAccount = userAccount;
}
I'ld like to pull the UserAccount by its email field which is an unique field in the UserAccount table.
In the UserAccountDAO the method i call to get the UserAccount is getUserAccountByEmail here below.So i expect this method to load all the children collections of the UserAccount namely its Contact collection, group collection.I want it in such a way that when UserAccount is loaded with Contacts collection each of the contact object has its reference with its belonging groups collection if any etc and vice versa.
public UserAccount getUserAccountByEmail(String email) {
// try {
logger.info("inside getUserAccountByEmail");
logger.debug(email);
Session session = (Session) this.getDBSession().getSession();
UserAccount user = (UserAccount) session.createCriteria(this.getPersistentClass())
.setFetchMode("contacts", FetchMode.SELECT) //recently added
.setFetchMode("groups", FetchMode.SELECT) // recently added
.add(Restrictions.eq("email", email))
.uniqueResult();
logger.debug(user);
return user;
// } catch(NonUniqueResultException ne) {
// logger.debug("Exception Occured: getUserAccountByEmail returns more than one result ", ne);
// return null;
// } catch(HibernateException he){
// logger.debug("Exception Occured: Persistence or JDBC exception in method getUserAccountByEmail ",he);
// return null;
// }catch(Exception e) {
// logger.debug("Exception Occured: Exception in method getUserAccountByEmail", e);
// return null;
// }
Since there has to be an UserAccount before any contact and groups, in my unit test when testing the saving of a contact object for which there must be an existing group i do this in order
a create userAccount object ua.
b create group object g1;
c create contact object c1;
d ua.addGroup(g1);
e c1.setUserAccount(ua);
f c1.addGroup(g1);
g uaDao.save(ua); // which saves the group because of the cascade
h cDao.save(c1);
Most of the time i use the session.get() from hibernate to pull c1 by its it id generated by hibernate and do all the assertions which works actually.
but in Integration test when i call getUserAccountByEmail with and without the setFetchMode and it returns the right object but then all the children collections are empty. i've tried the JOIN and the SELECT.the query string changes but then the result set is still the same. So this arises some questions :
1. What should i do to fix this?
2. the helper method works fine but it's on the parent side(i do it in the test).What i've been wondering about is that doing c1.setUserAccount(ua); is enough to create a strong relationship between UserAccount and contact.most of the time there will not be cases where i save the userAccount with contact but yet the helper method that set the association in both side and which is in UserAccount will not been called before i save the contact for a particular userAccount.So i'm little confused about that and suspecting that setting of the association is part of the why something is not working properly.and then calling session.get(UserAccount.class, ua.getID()) i think goes what i want and i'ld like getUserAccountByEmail to do the same.
3. ChssPly76 thinks the mapping has to be rewrite.So i'm willing to let you guide me through this.I really need to know the proper way to do this because we can't lean everything from a good book.So i you think i should change the mapping just show me how.and probable i'm doing things the wrong way without even been aware of that so don't forget i'm still learning java itself.THanks for the advise and remarks and thanks for reading this
I agree with you that it seems likely that the associations between your parent objects and their child collections are not getting persisted properly. I always like to start out by looking at what is in the database to figure out what's going on. After you run your test what do you see in the actual database?
It seems likely that one of two things is happening (using UserAccount as an example):
The items in the child collection are not getting saved to the database at all, in which case you'll be able to see in the database that there are no records associated with your UserAccount. This could be caused by saving the UserAccount object before you've added the child object to the UserAccount's collection.
The items in the child collection are getting saved to the database, but without the needed association to the parent object, in which case you'll see rows for your child items but the join column (ie 'userAccount' will be null). This could be caused by not setting the userAccount() property on the child object.
These are the two scenarios that I've run into where I've seen the problem you describe. Start by taking a look at what goes into your database and see if that leads you farther.