The NodeEntity caches the value of the Set - java

I'm using the Spring Data Neo4j 4. It seems the "PersistenceContext" of Neo4j cache the values of the "Set" value.
The Entity
#NodeEntity
public class ServiceStatus implements java.io.Serializable {
#GraphId Long id;
private Set<String> owners = new HashSet<String>();
}
First, I put a value "ROLE_ADMIN" in the owners and save it.
Then I edit the value to "ROLE_SYSTEM_OWNER" and called save() again.
In the Neo4j query browser, it only show the "ROLE_SYSTEM_OWNER", which is all correct for now.
However, when I called the findAll(), the owners has two values ["ROLE_ADMIN","ROLE_SYSTEM_OWNER"]
It will work fine when I restart my web server.
[The way to change value]
#Test
public void testSaveServiceStatus() throws OSPException {
//1. save
ServiceStatus serviceStatus = new ServiceStatus();
serviceStatus.setServiceName("My Name");
Set<String> owners = new HashSet<String>();
owners.add("ROLE_SITE_ADMIN");
serviceStatus.setOwners(owners);
serviceStatusRepository.save(serviceStatus);
System.out.println(serviceStatus.getId()); //262
}
#Test
public void testEditServiceStatus() throws OSPException{
//1. to find all , it seems cache the set value
serviceStatusRepository.findAll();
//2. simulate the web process behavior
ServiceStatus serviceStatus = new ServiceStatus();
serviceStatus.setId(new Long(262));
serviceStatus.setServiceName("My Name");
Set<String> owners = new HashSet<String>();
//change the owner to Requestor
owners.add("Requestor");
serviceStatus.setOwners(owners);
//3. save the "changed" value
// In the cypher query browser, it show "Requestor" only
serviceStatusRepository.save(serviceStatus);
//4. retrieve it again
serviceStatus = serviceStatusRepository.findOne(new Long(262));
System.out.println(serviceStatus); //ServiceStatus[id=262,serviceName=My Name,owners=[Requestor5, Requestor4]]
}

Your test appears to be working with detached objects in a way. Step one, findAll() loads these entities into the session, but then step 2 instead of using the loaded entity, creates a new one which is subsequently saved. The "attached" entity still refers to the earlier version of the entity.
The OGM does not handle this currently.
You're best off modifying the entity loaded in findAll or just a findOne(id), modify, save (instead of recreating one by setting the id). That will ensure everything is consistent.

Related

Freshly created PaymentTransaction the ID is null

i have an issue that a freshly created payment transaction has no ID.
#Override
#Transactional("blTransactionManager")
public PaymentTransaction getNewTemporaryOrderPayment(Order cart, PaymentType paymentType) {
OrderPayment tempPayment = null;
if (CollectionUtils.isNotEmpty(cart.getPayments())) {
Optional<OrderPayment> optionalPayment = NmcPaymentUtils.getPaymentForOrder(cart);
if (optionalPayment.isPresent()) {
tempPayment = optionalPayment.get();
invalidateTemporaryPaymentTransactions(tempPayment);
}else {
throw new IllegalStateException("Missing payment");
}
} else {
tempPayment = this.orderPaymentService.create();
}
tempPayment = this.populateOrderPayment(tempPayment, cart, paymentType);
//its necessary to create every time a new transaction because the ID needs to be unique in the parameter passed to 24pay
PaymentTransaction transaction = createPendingTransaction(cart);
transaction.setOrderPayment(tempPayment);
tempPayment.addTransaction(transaction);
tempPayment = orderService.addPaymentToOrder(cart, tempPayment, null);
orderPaymentService.save(transaction);
orderPaymentService.save(tempPayment);
return transaction;
}
even if i do an explicit save on the returned PaymentTransaction, the ID is still null. It is correctly persisted and has an ID in the database.
PaymentTransaction paymentTransaction = paymentService.getNewTemporaryOrderPayment(cart, PaymentType.CREDIT_CARD);
orderPaymentService.save(paymentTransaction);
how can i explicitly refresh this entity ? or any other suggestions how to solve this? I can do something like this to find my pending transaction
OrderPayment orderPayment = paymentTransaction.getOrderPayment();
Optional<PaymentTransaction> any = orderPayment.getTransactions().stream().filter(t -> t.isActive()).findFirst();
but that seems like an extra step which should not be needed. Any suggestions how to solve this in an elegant way ?
The transaction object has a null id because that variable is not updated when the order is saved.
Calls to save() methods return a new object, and that new object will have its id set.
Consider the following example:
Transaction transaction1 = createTransaction(...);
Transaction transaction2 = orderPaymentService.save(transaction1);
After this code executes, transaction1 will not have been changed in save(), so its id will still be null. Transaction2 will be a new object with the id set.
Therefore, the variable transaction, created with PaymentTransaction transaction = createPendingTransaction(cart);, is never updated with the saved value, so the id is still null at the end.
Further, the save() calls at the end for the transaction and payment probably won't work as you intend. This is because the orderService.addPaymentToOrder(cart, tempPayment, null); will save the order, which should also cascade to save the transaction and payment. I'm pretty sure that calling save again would result in new objects that are not connected to the saved order.
So what do you do about this?
The call to tempPayment = orderService.addPaymentToOrder(cart, tempPayment, null); returns a persisted OrderPayment. Read the transactions from that object to find the one you just created. It is very similar to the extra step you are trying to avoid, but you can at least cut out one line.
OrderPayment persistedPayment = orderService.addPaymentToOrder(cart, tempPayment, null);
Optional<PaymentTransaction> persistedTransaction = persistedPayment.getTransactions().stream().filter(t -> t.isActive()).findFirst();

OptimisticLockException when using JPA merge()

I have a rest application where one of the resources can be updated. Below are two methods responsible for achieving this task:
updateWithRelatedEntities(String, Store): receives id and new object Store which was constructed by deserializing PUT request entity, sets the version (used for optimistic locking) on new object and calls update in a transaction.
public Store updateWithRelatedEntities(String id, Store newStore) {
Store existingStore = this.get(id);
newStore.setVersion(existingStore.getVersion());
em.getTransaction().begin();
newStore = super.update(id, newStore);
em.getTransaction().commit();
return newStore;
}
update(String, T): a generic method for making an update. Checks that ids match and performs merge operation.
public T update(String id, T newObj) {
if (newObj == null) {
throw new EmptyPayloadException(type.getSimpleName());
}
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
superclass = ((Class) superclass).getGenericSuperclass();
}
Class<T> type = (Class<T>) (((ParameterizedType) superclass).getActualTypeArguments()[0]);
T obj = em.find(type, id);
if (!newObj.getId().equals(obj.getId())) {
throw new IdMismatchException(id, newObj.getId());
}
return em.merge(newObj);
}
The problem is that this call: T obj = em.find(type, id); triggers an update of store object in the database which means that we get OptimisticLockException when triggering merge (because versions are now different).
Why is this happening? What would be the correct way to achieve this?
I kind of don't want to copy properties from newStore to existingStore and use existingStore for merge - which would, I think, solve the optimistic lock problem.
This code is not running on an application server and I am not using JTA.
EDIT:
If I detach existingStore before calling update, T obj = em.find(type, id); doesn't trigger an update of store object so this solves the problem. The question still remains though - why does it trigger it when entity is not detached?
I can't see your entity from code which you added but I believe that you missing some key point with optimistic locking -> #Version annotation on version field.
If you have this field on your entity then container should be able to do merge procedure without problems. Please take a look to
Optimistic Locking also good article don't break optimistic locking

Hibernate update relation set without knowing primary keys

In Hibernate, what is the best way I can use to update the relation set of a given entity without knowing any of the primary keys? I basically want it to check the current relation set to see if any of the related entities have all of the same field values as the one about to be saved, and if so update it otherwise create a new one.
Below is my code:
public void updateProcesses(ComputerEntity computerEntity, JsonNode data) {
Set<ProcessEntity> processEntities = new HashSet<>();
for (JsonNode process : data.get("Processes")) {
ProcessEntity processEntity = new ProcessEntity();
processEntity.setPid(process.get("Pid").asInt());
processEntity.setName(process.get("Name").asText());
processEntity.setDescription(process.get("Description").asText());
processEntity.setWindowTitle(process.get("WindowTitle").asText());
processEntity.setThreadCount(process.get("ThreadCount").asInt());
processEntity.setCpu((byte) process.get("Cpu").asLong());
processEntity.setRam(process.get("Ram").asLong());
processEntity.setRunTime(process.get("RunTime").asLong());
processEntity.setComputer(computerEntity);
processEntities.add(processEntity);
}
computerEntity.setProcesses(processEntities);
Session session = Fusion.instance().getSession();
session.beginTransaction();
session.update(computerEntity);
session.getTransaction().commit();
session.close();
}
Currently it recreates new process records in the database and does not remove or update the old ones causing duplicates.

functional test in playframework fails when adding items to cart

I wrote a functional test to check adding items to a shopping cart.For a user to be able to add items to cart,he needs to login.So,I created a method to login the user and another method to add the item.Before and after the addtocart method in test,I am checking the size of content of cart.The addtocart functionality works without any problem when I run the app in dev mode(I can check the db too-which is postgres and not an in memory db).The addtocart fails in test.
the controller method which adds item to cart
public static void addItemToCart(Long productId,Long cartId,String quantity) {
Product product = Product.findById(productId);
ShopCart cart = ShopCart.findById(cartId);
int qty = Integer.parseInt(quantity);
CartItem cartItem = new CartItem(product,qty);
cart.addItem(cartItem);
cart.save();
System.out.println("Controller::addItemToCart()::cart id="+cart.id+" has="+cart.cartItems.size()+" items);
}
my test method is
#Test
public void testUserCanAddItemsToCart() {
Fixtures.loadModels("data.yml");
User user = User.find("byEmail","user#shop.com").first();
loginAsCustomer("user#shop.com","userpass");
ShopCart usercart = new ShopCart(user);
usercart.save();
System.out.println("BEFORE ADD::usercart="+usercart.id+" has :"+usercart.cartItems.size()+" items");
assertTrue(usercart.cartItems.size()==0);
addItemsToCart(usercart);
System.out.println("AFTER ADD::usercart="+usercart.id+" has :"+usercart.cartItems.size()+" items");
assertFalse(usercart.cartItems.size()==0);//why does this fail?
}
private Response addItemsToCart(ShopCart cart) {
Product pdt = Product.find("byIsbn","654-0451160522").first();
assertNotNull(pdt);
System.out.println("addItemsToCart():BEFORE ADD cart="+cart.id+" has="+cart.cartItems.size());
Map<String,String> addtocartParams = new HashMap<String,String>();
addtocartParams.put("cartId", cart.id.toString());
addtocartParams.put("quantity", "2");
String addtocarturl = "/items/addtocart/"+pdt.id.toString();
Response response = POST(addtocarturl,addtocartParams);
System.out.println("addItemsToCart():AFTER ADD cart="+cart.id+" has="+cart.cartItems.size());
return response;
}
The console output I get is
BEFORE ADD::usercart=48 has :0 items
addItemsToCart():BEFORE ADD cart=48 has=0
Controller::addItemToCart()::cart id=48 has=1 items
addItemsToCart():AFTER ADD cart=48 has=0
AFTER ADD::usercart=48 has :0 items
Here, in the controller method, the cart instance (of id=48) has 1 item after it is saved to db.But in the test method ,the cart instance of same id has 0 content.
I commented out the assertFalse method and retrieved the cart from db using the cartId.Even then the cart of same id has 0 content.I can't understand why this is happening..can anyone shed some light?
//test method body ..modified
ShopCart cart = ShopCart.findById(usercart.id);
System.out.println("AFTER ADD::cart="+cart.id+" has :"+cart.cartItems.size()+" items");
assertFalse(cart.cartItems.size()==0);//why does this fail?
It fails because the cart instance used by your test method and the cart instance used by the addItemToCart method are different. Each transaction has its own instance of the entity. And JPA doesn't automagically refresh an entity when some other transaction updates the row mapped by this entity.
You should reload the cart from the database after addItemsToCart has been called to check if something has been added to the cart in database.
I am a slave to object-oriented thinking, so what I'm wondering is, have you thought about making addItemsToCart() a method of your ShopCart class? I'm envisioning something like:
...
ShopCart usercart = new ShopCart(user);
usercart.addItemsToCart(pdt);
usercart.save();
String addtocarturl = "/items/addtocart/"+pdt.id.toString();
Response response = POST(addtocarturl,addtocartParams);
return response;
It's just easier for me to think about making (or retrieving) a ShopCart object, modifying it, and putting it in the database. That's how I would avoid this.
I had the same issue and adding JPA.em().clear() in my test before I get the model from the database solved this issue for me.

Can't get a string field from an object in another object in siena

I'm having trouble getting a field which is in an object which is inside another object. I can get some fields, but others no.
This is the test I created to reproduce this error.
public void commentTest(){
try {
new MyUser("mauri#mail.com","Maurizio Pozzobon","01","facebook","hash").insert();
} catch (Exception e) {}
MyUser user = MyUser.findByEmail("mauri#mail.com");
Place place = new Place(user,"posto","bel posto",null,null);
place.insert();
assertNotNull(user);
Event e =new Event(user,place, "Festa","Questa รจ una gran bella festa",null,new Date(),(long) 10,false,null);
e.insert();
assertNotNull(user.nome);
EventComment ec = new EventComment(user, e, "TestComment", new Date());
ec.insert();
List<EventComment> ecs = e.comments.fetch();
for (EventComment comment : ecs) {
assertNotNull(comment.user.id);
MyUser us= MyUser.findById(comment.user.id);
assertNotNull(us.nome);
assertNotNull(comment.user.nome);
}
}
It fails at the line
assertNotNull(comment.user.nome);
This isn't a deal breaker since I still can get to that field doing other calls to the DB, but it seems weird I can access some fields and others can't
In MyUser I tried both declaring the 'nome' field with and without the following annotations
#Column("nome")
#Max(200) #NotNull
public String nome;
No basically, this is normal.
You use GAE, am I right?
In GAE, remember that there is no JOIN as in SQL DB.
When you fetch a comment, the linked user is not fetched entirely but only the user.id field is filled. That's why assertNotNull(comment.user.id) is OK.
So, by default, if you want the user associated to a comment, you need to fetch it manually.
This limitation should change soon as we are going to provide entity grouping very soon and also a new annotation #Join that will fetch the linked entity(ies) automatically.
You can already try this annotation but it's not yet finalized.
In your comment class, add the #Join :
#Join
#Column("user")
User user;
Then when you fetch one comment, it will also fetch the user with it.
Comment comment = Comment.findById("id", value);
assertNotNull(comment.user.nome); // will be OK.
But it shouldn't work in the case : List<EventComment> ecs = e.comments.fetch();
This join is much more complicated and until we have entity grouping, it would consume too much resources behind the curtain.

Categories

Resources