Hibernate update relation set without knowing primary keys - java

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.

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();

The NodeEntity caches the value of the Set

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.

Spring data - insert data depending on previous insert

I need to save data into 2 tables (an entity and an association table).
I simply save my entity with the save() method from my entity repository.
Then, for performances, I need to insert rows into an association table in native sql. The rows have a reference on the entity I saved before.
The issue comes here : I get an integrity constraint exception concerning a Foreign Key. The entity saved first isn't known in this second query.
Here is my code :
The repo :
public interface DistributionRepository extends JpaRepository<Distribution, Long>, QueryDslPredicateExecutor<Distribution> {
#Modifying
#Query(value = "INSERT INTO DISTRIBUTION_PERIMETER(DISTRIBUTION_ID, SERVICE_ID) SELECT :distId, p.id FROM PERIMETER p "
+ "WHERE p.id in (:serviceIds) AND p.discriminator = 'SRV' ", nativeQuery = true)
void insertDistributionPerimeter(#Param(value = "distId") Long distributionId, #Param(value = "serviceIds") Set<Long> servicesIds);
}
The service :
#Service
public class DistributionServiceImpl implements IDistributionService {
#Inject
private DistributionRepository distributionRepository;
#Override
#Transactional
public DistributionResource distribute(final DistributionResource distribution) {
// 1. Entity creation and saving
Distribution created = new Distribution();
final Date distributionDate = new Date();
created.setStatus(EnumDistributionStatus.distributing);
created.setDistributionDate(distributionDate);
created.setDistributor(agentRepository.findOne(distribution.getDistributor().getMatricule()));
created.setDocument(documentRepository.findOne(distribution.getDocument().getTechId()));
created.setEntity(entityRepository.findOne(distribution.getEntity().getTechId()));
created = distributionRepository.save(created);
// 2. Association table
final Set<Long> serviceIds = new HashSet<Long>();
for (final ServiceResource sr : distribution.getServices()) {
serviceIds.add(sr.getTechId());
}
// EXCEPTION HERE
distributionRepository.insertDistributionPerimeter(created.getId(), serviceIds);
}
}
The 2 queries seem to be in different transactions whereas I set the #Transactionnal annotation. I also tried to execute my second query with an entityManager.createNativeQuery() and got the same result...
Invoke entityManager.flush() before you execute your native queries or use saveAndFlush instead.
I your specific case I would recommend to use
created = distributionRepository.saveAndFlush(created);
Important: your "native" queries must use the same transaction! (or you need a now transaction isolation level)
you also wrote:
I don't really understand why the flush action is not done by default
Flushing is handled by Hibernate (it can been configured, default is "auto"). This mean that hibernate will flush the data at any point in time. But always before you commit the transaction or execute an other SQL statement VIA HIBERNATE. - So normally this is no problem, but in your case, you bypass hibernate with your native query, so hibernate will not know about this statement and therefore it will not flush its data.
See also this answer of mine: https://stackoverflow.com/a/17889017/280244 about this topic

Update record if somcolumn!="somevalue", using Hibernate saveorupdate()

I am trying to write Criteria in Hibernate, My desired output is if column empfield1's value is not 'REGULARIZE' then update else do not update record.
i have tried below one.
Session session = factory1.openSession();
Criteria criteria=session.createCriteria(EmployeePunch.class);
criteria.add(Restrictions.ne("empField1","REGULARIZE"));
EmployeePunch empPunch = (EmployeePunch)criteria.uniqueResult();
empPunch.setId(empPuncId);
empPunch.setSigninTime(inTime);
empPunch.setSigninDate(dateOfUpdate);
empPunch.setSignoutTime(outTime);
empPunch.setPresent(presentStatus);
empPunch.setLastUpdateBy(empcode);
empPunch.setLastUpdateDate(time);
empPunch.setEmpField1(remark);
session.saveOrUpdate(empPunch);
tx.commit();
but it gives me error
Exception : query did not return a unique result: 527
I think you forget to give id without giving id hibernate will return multiple records with empField1="REGULARIZE"
You should give id as well like below:
Criteria criteria=session.createCriteria(EmployeePunch.class);
criteria.add(Restrictions.ne("empField1","REGULARIZE"))
.add(Restrictions.eq("empPuncId",empPuncId));
Now it will return single matching record and then update it.
That means ,With that criteria there are multiple records are there in your Database.
To know how many records are there,
Try
List<EmployeePunch> emps = (ArrayList<EmployeePunch>)criteria.list();
So that emps will give you a list of EmployeePunch's which meets the criteria.
Then iterate the list and see how many items are there inside database.
Why not use a HQL in this way?
Query query = session.createQuery("update EmployeePunch set signinTime = :signinTime, signinDate = :signinDate where empField1 = 'REGULARIZE').setParameter("signinTime",signinTime).setParameter("signinDate",signinDate);
int updateRecordCount = query.executeUpdate();
Of course, you have to set values for other properties (except for Id if it is your #Id field); in updateRecordCount you get the count of updated records.

Removing entities from Collection in hibernate

I'm trying to manually delete every entity that's in a collection on an entity. The problem is, the entities don't get deleted from the database, even though they get removed from the collection on the task.
Below is the code im using to achieve this:
public int removeExistingCosts(final DataStoreTask task) {
int removedAccumulator = 0;
Query query = entityManager.createNamedQuery(DataStoreCost.GET_COSTS_FOR_TASK);
query.setParameter(DataStoreCost.TASK_VARIABLE_NAME, task);
try {
List costsForTask = query.getResultList();
for(Object cost : costsForTask) {
task.getCosts().remove(cost);
removedAccumulator++;
}
} catch (NoResultException e) {
logger.debug("Couldn't costs for task: {}", task.getId());
}
entityManager.flush();
entityManager.persist(task);
return removedAccumulator;
}
Any ideas?
P.S the collection is represented as:
#OneToMany(targetEntity = DataStoreCost.class, mappedBy = "task", cascade = CascadeType.ALL)
private Collection<DataStoreCost> costs;
Cheers.
I think you need to explicitly remove the Cost entity via the entityManager. When you remove the Cost from the Tasks cost list you actually only remove the reference to that instance. It does not know that that particular Cost will not be used anywhere else.
It's not deleting the entity, because it doesn't know if something else is referring to it.
You need to enable delete orphan. In jpa2, use the orphanRemoval attribute. If you're using hibernate annotations, use CascadeStyle delete orphan.

Categories

Resources