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.
Related
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 am trying to update the existing rows in database table using JPA #Query Annotation. I want to perform Soft delete by updating the Deleted_Flag to YES from NO.
Here is my Code snippet:
#Modifying
#Query("UPDATE TBL_NAME SET DELETE_FLAG = 'YES' WHERE DELETE_FLAG = 'NO'
AND FILE_NM = :FILE_NM")
public void softDelete(#Param("FILE_NM") String fileName)
{
}
I am not getting any error, but data is not being updated in database.
Actual result must be like all the existing rows must be updated with DELETE_FLAG to YES.
Make sure you invoke the repository method with an active transaction.
Actually, in my last project I use the following idiom for updating a flag :
Entity is annotated with Hibernetish:
#Entity
#Table(name="myTable")
#Where(clause = "is_deleted = 0")
#Cacheable
public class MyTable {}
Actual update comes with a trivial find method:
#Transactional
public void deleteById(#NonNull final Long themeId) {
themeRepository.findById(themeId).orElseThrow(() -> new EntityNotFoundException(THEME_NOT_FOUND + themeId))
.setDeleted(true);
}
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.
I have got a Springboot Application and a Oracle DB with lots of PL/SQL Procedures and these change the state of the DB all the Time.
So now I want to change a loaded entity an want to save it. If the entitystate of the entitymanager and the state of the db is equal everything works fine. But in some cases they are not equal. So if I load an entity and make some changes an druring this a PL/SQL Procedure changes the DB Table. If I save the Entity I will get an Execption of course. So I tried to catch the Exception and then in the catch block I want to refresh the Entity before saving it. But I still get an Exception. Is the Transaction not jet finished? How can I handle this Problem?
I hope the example code explains a little bit.
#RestController
#RequestMapping("/*")
public class FacadeController {
...
#ResponseStatus(HttpStatus.OK)
#RequestMapping( value= "/test4" , method=RequestMethod.GET)
public String test4(){
Unit unit = unitSvice.loadUnit(346497519L);
List<UnitEntry> entries = unit.getEntries();
for (UnitEntry g : entries) {
if (g.getUnitEntryId == 993610345L) {
g.setTag("AA");
g.setVersion(g.getVersion() + 1);
g.setstatus("SaveOrUpdate");
}
}
//<-- DB Table changed entity managed by entitymanger and DB Table
// are no langer equal.
try {
unitSvice.updateUnit(unit , false);
}catch(DataAccessException | IllegalArgumentException e) {
unitSvice.updateUnit(unit , true);
}
...
}
}
#Service("unitSvice")
public class UnitSvice {
#Autowired
private UnitDao repoUnit;
#Transactional
public Unit loadUnit(Long _id) {
Unit unit = repoUnit.findOne(_id);
return unit;
}
#Transactional
public void updateUnit(Unit unit, boolean _withrefrsh ) {
if(_withrefrsh) {
getEntityManager().refresh(unit.getId());
}
repoUnit.save(unit);
}
}
I hope, anyone can help me.
Thanks
yes the problem is ..when you call load all method which is transactional method where entities became detached from session/entitymanager when you are returning from that method.. so,next you are trying to persist detached object. That's why you get exception.
so probably you can use session.update() or session.merge() to save the new update into database.
I try to use objectify transaction, but I have some issues when I need to reload an object created in the same transaction.
Take this sample code
#Entity
public class MyObject
{
#Parent
Key<ParentClass> parent;
#Index
String foo;
}
ofy().transact(new VoidWork()
{
#Override
public void vrun()
{
ParentClass parent = load();// load the parent
String fooValue = "bar";
Key<ParentClass> parentKey = Key.create(ParentClass.class, parent.getId())
MyObject myObject = new MyObject(parentKey);
myObject.setFoo(fooValue);
ofy().save().entity(myObject).now();
MyObject reloaded = ofy().load().type(MyObject.class).ancestor(parentKey).filter("foo", fooValue).first().now();
if(reloaded == null)
{
throw new RuntimeException("error");
}
}
});
My object reloaded is always null, maybe I miss something, but logically within a transaction I can query an object which was created in the same transaction?
Thanks
Cloud Datastore differs from relational databases in this particular case. The documentation states that -
Unlike with most databases, queries and gets inside a Cloud Datastore
transaction do not see the results of previous writes inside that
transaction. Specifically, if an entity is modified or deleted within
a transaction, a query or lookup returns the original version of the
entity as of the beginning of the transaction, or nothing if the
entity did not exist then.
https://cloud.google.com/datastore/docs/concepts/transactions#isolation_and_consistency