When I retrieve an object using its id and I change its properties and update it, everything work fine but when I get my objects using their name + version and update them, none of the changes save in the database. Could you please someone let me know what is the problem?!
// Get by id
public PdfDocument get(Long id) {
return (PdfDocument) session().get(PdfDocument.class, id);
}
// Get by name + version
public PdfDocument get(String name, int version) {
Criteria criteria = session().createCriteria(PdfDocument.class);
criteria.add(Restrictions.eq("name", name));
criteria.add(Restrictions.eq("version", version));
return ((PdfDocument) criteria.uniqueResult()) ;
}
// update
public void update(PdfDocument PdfDocument) {
session().saveOrUpdate(PdfDocument);
}
May be criteria returning a different entity than your expecting one. Check the id of returned entity and the expected one.
Related
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 have implemented by project using Spring-Data-Rest. I am trying to do an update on an existing record in a table. But when I try to send only a few fields instead of all the fields(present in Entity class) through my request, Spring-Data-Rest thinking I am sending null/empty values. Finally when I go and see the database the fields which I am not sending through my request are overridden with null/empty values. So my understanding is that even though I am not sending these values, spring data rest sees them in the Entity class and sending these values as null/empty. My question here is, is there a way to disable the fields when doing UPDATE that I am not sending through the request. Appreciate you are any help.
Update: I was using PUT method. After reading the comments, I changed it to PATCH and its working perfectly now. Appreciate all the help
Before update, load object from database, using jpa method findById return object call target.
Then copy all fields that not null/empty from object-want-to-update to target, finally save the target object.
This is code example:
public void update(Object objectWantToUpdate) {
Object target = repository.findById(objectWantToUpdate.getId());
copyNonNullProperties(objectWantToUpdate, target);
repository.save(target);
}
public void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] propDesList = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(PropertyDescriptor propDesc : propDesList) {
Object srcValue = src.getPropertyValue(propDesc.getName());
if (srcValue == null) {
emptyNames.add(propDesc.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
You can write custom update query which updates only particular fields:
#Override
public void saveManager(Manager manager) {
Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
query.setParameter("username", manager.getUsername());
query.setParameter("password", manager.getPassword());
query.setParameter("id", manager.getId());
query.executeUpdate();
}
As some of the comments pointed out using PATCH instead of PUT resolved the issue. Appreciate all the inputs. The following is from Spring Data Rest Documentation:
"The PUT method replaces the state of the target resource with the supplied request body.
The PATCH method is similar to the PUT method but partially updates the resources state."
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repository-crud-methods
Also, I like #Tran Quoc Vu answer but not implementing it for now since I dont have to use custom controller. If there is some logic(ex: validation) involved when updating the entity, I am in favor of using the custom controller.
I have one data already saved in my databse based on my repository and service.i want to save another data with postman by changing only the player id.But it is not create a new entity data.it update the existing entity data.My question is how to update a data by my service when it finds a existing id.But when it finds a new id it will save a new data into databse.
This is my repo:
#Repository
#Transactional
public interface CricketPlayerRepository extends CrudRepository<CricketPlayer,String> {
Optional<CricketPlayer> findCricketPlayerByName(String name);
}
This is my service:
#Service
public class CricketPlayerService {
private CricketPlayerRepository cricketPlayerRepository;
public CricketPlayerService(CricketPlayerRepository cricketPlayerRepository) {
super();
this.cricketPlayerRepository = cricketPlayerRepository;
}
public CricketPlayerService() {
}
public Optional<CricketPlayer> getPlayerByName(String name){
return cricketPlayerRepository.findCricketPlayerByName(name);
}
public CricketPlayer save(CricketPlayer cricketPlayer){
Optional<CricketPlayer> id = cricketPlayerRepository.findById(cricketPlayer.getPlayerId());
if (id.isPresent()){
//code here
}
// if (entityManager.isNew(cricketPlayer)) {
// em.persist(cricketPlayer);
// return cricketPlayer;
// } else {
// return em.merge(cricketPlayer);
// }
return cricketPlayerRepository.save(cricketPlayer);
}
public Iterable<CricketPlayer> findAllPlayers() {
return cricketPlayerRepository.findAll();
}
public Optional<CricketPlayer> findPlayersById(String id) {
return cricketPlayerRepository.findById(id);
}
}
save and update operations in hibernate ( and other frameworks) are based on id value. if an id exists merge (update) entity and otherwise save new instance. So it cannot be done in this context.
1)If PlayerId is primary key id, then you would have called merge(entity). If PlayerId is present it will update else it will create new record.
2)If PlayrerId is not primary key id. Best practice is to avoid PlayerId as primary key.
In postman you should pass database table primary key id along with PlayerId.
Then you call merge(entity). It will take care of create or update based on primary key
id is null or not.
for example below if you have passed primary key id in request.
Entity e = new Entity();
if (entityFromRequest.getId() != null){ //this is the db id from request
//get the entity from db and then change the state
e = entity from db by using id
}
e.setPlayerId = entityFromRequest.getPlayerId
merge(e); // it will create or update record
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.
For work with database, my class extends HibernateDaoSupport class and inside the methods I'm using Spring HibernateTemplate.
So, for delete a row in database I use this method:
public void delete(MyObject obj) {
getHibernateTemplate().delete(obj);
}
all ok!
But, at this moment I'm trying to implement a method that can delete a row based on id:
public void delete(final long id) {
// some code here
}
And I can't find some HibernateTemplate method like this:
getHibernateTemplate().remove(id)
What is a good solution for me in this case?
delete using particular id,
public void delete(long id)
{
Session session ;
MyObject myObject ;
session = sessionFactory.getCurrentSession();
myObject = (MyObject)session.load(MyObject.class,id);
session.delete(myObject);
//This makes the pending delete to be done
session.flush() ;
}
Also consider encapuslate this methods in try/catch/finally and log the error as needed
Another alternative is:
public void deleteById(Class clazz,Integer id) {
String hql = "delete " + clazz.getName() + " where id = :id";
Query q = session.createQuery(hql).setParameter("id", id);
q.executeUpdate();
}
As you mentioned, there s not such method in HibernateTemplate. You can do the following,
hibernateTemplate.delete(hibernateTemplate.get(Class,Id));
You can also use below method:
public void deleteById(Class clazz,Integer id) {
hibernateTemplate.bulkUpdate("delete from "+clazz.getName()+" where id="+id);
}
There is a simple solution by creating an object and setting only the ID:
Product product = new Product();
product.setId(37);
session.delete(product);
The drawback of this simple solution is that it doesn’t remove the associated instances.
If you have some attribute (another entity related) of the product to be deleted, you will need to load the product before.
Serializable id = new Long(17);
Object persistentInstance = session.load(Product.class, id);
if (persistentInstance != null)
{
session.delete(persistentInstance);
}
This will issue (if you have an attribute table in cascade) a delete on the children attributes.