I'm using spring 3 with hibernate 3.5.4
1- I want to create an object in transaction and save it to DB ( which passes successfully ).
2- I want to update some fields in that object (same object) and updates in in DB in another transaction (and here is the problem).
The problem is, is saves the object successfully in the first transaction but it doesn't update it in DB in the second one.
here is code example:
public String entry(String str){
Bill b = create(str);
b = update(b);
b = updateAgain(b);
return "DONE";
}
#Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public Bill create(String num){
Bill bill = new Bill();
bill.setBillNumber(num);
baseDao.saveObject(bill);
return bill;
}
#Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public Bill update(Bill bill){
bill.setRetailAmount(152.0);
baseDao.saveObject(bill);
return bill;
}
NOTE: I don't want to put the #transactional annotation on method "entry".
Thanks,
The annotation will not take affect if called on a method within the same class. AOP cannot intercept that through proxy. Move your entry method outside the class.
EDIT: Spring enables the Transactional annotation via annotation-driven AOP with proxies or sub-classing. When enabled with proxies, your proxy is out of the picture in a local method call. This blog post has a good explanation with pictures.
Related
I am preparing notification system for API which I've build before.
Basically I have an aspect which listens on projectRepository.save method. What I want to achieve is check project status in an entity which is a parameter for save method with original status from database record. What I have notice is that when I search for the DB record by id it returns cached value so it is always the same as the object which is in save method even if database still have old value. Can I force Spring Data Jpa to return database record instead of cached entity?
#Aspect
#Component
#RequiredArgsConstructor
public class NotificationAspect {
private final UserService userService;
private final ProjectRepository projectRepository;
private final NotificationService notificationService;
#Pointcut("execution(* *com.stars.domain.project.ProjectRepository.save(..))")
public void projectSavePointcut() {}
#Before("projectSavePointcut()")
public void sendNotificationOnStatusChange(JoinPoint joinPoint) {
if(joinPoint.getArgs().length > 0 && joinPoint.getArgs()[0] instanceof Project) {
Project projectToUpdate = (Project) joinPoint.getArgs()[0];
Optional<Project> oldProject = projectRepository.findById(projectToUpdate.getProjectId());
if(oldProject.isPresent() && !oldProject.get().getStatus().equals(projectToUpdate.getStatus())) {
notificationService.saveNotification(
MessageFormat.format("Project: {} status has been changed from: {} to: {}",
projectToUpdate.getName(),
oldProject.get().getStatus(),
projectToUpdate.getStatus()),
List.of(userService.getUser(projectToUpdate.getCreatedBy())));
}
}
}
}
This line always returns true even if database record has different value.
oldProject.get().getStatus().equals(projectToUpdate.getStatus())
I can think of two ways.
First, if you're interested only in status field, you can create a custom native query in a repository, which will bypass EntityManager, for example like this:
#Query("SELECT p.status FROM projects p WHERE p.id = :id", nativeQuery = true)
String getProjectStatusById(#Param("id") String projectId);
Second looks like a bad idea, but it should work - you can make the entity manager's cache detach all managed entities, so it will be forced to make a DB call again.
For this inject EntityManager in your aspect bean and call its .clear() method right before calling projectRepository.findById method.
I am updating my application from Spring Boot 1.4.5 / Hibernate 4.3.5 to Spring Boot 2.0.9 / Hibernate 5.2.18 and code that used to work in the previous configuration is no longer working.
The scenario is as follows:
Start a transaction by entering a method annotated with #Transactional
Hydrate the entity
Change the entity
Make another query
Detect a problem. As a result of this problem, determine that changes should not persist.
Evict the entity
Exit the method / transaction
With Hibernate 4.3.5, calling entityManager.detach() would prevent the changes from being persisted. However, with Hibernate 5.2.18, I'm finding that changes are persisted even with this call. I have also tried to evict() from the session and I have tried to clear() all entities from the session (just to see what would happen).
So I ask - is it possible to discard entity changes in Hibernate 5.2.18 the way that I was able to do in Hibernate 4.3.5?
The relevant code is below...
#Entity
public class Agreement {
private Long agreementId;
private Integer agreementStateId;
#Id
#Column(name = "agreement_id")
public Long getAgreementId() {
return agreementId;
}
public void setAgreementId(Long agreementId) {
this.agreementId = agreementId;
}
#Basic
#Column(name = "agreement_state_id", nullable = false)
public Integer getAgreementStateId() {
return agreementStateId;
}
public void setAgreementStateId(Integer agreementStateId) {
this.agreementStateId = agreementStateId;
}
}
#Component
public class Repo1 {
#PersistenceContext(unitName = "rights")
private EntityManager entityManager;
public void evict(Object entity) {
entityManager.detach(entity);
}
public Agreement getAgreement(Long agreementId) {
// Code to get entity is here.
// Agreement with an agreementStateId of 5 is returned.
}
public void anotherQuery() {
// Code to make another query is here.
}
}
#Component
public class Service1 {
#Autowired
Repo1 repo;
#Transactional
public void doSomething() {
Agreement agreement = repo.getAgreement(1L);
// Change agreementStateId. Very simple for purposes of example.
agreement.setAgreementStateId(100);
// Make another query
repo.anotherQuery();
// Detect a problem here. Simplified for purposes of example.
if (agreement.getAgreementStateId() == 100) {
repo.evict(agreement);
}
}
}
I have found the problem and it has nothing to do with evict(). It turns out that an additional query was causing the session to flush prior to the evict() call.
In general, the application uses QueryDSL to make queries. Queries made in this way did not result in the session flushing prior to making a query. However in this case, the query was created via Session.createSQLQuery(). This uses the FlushMode already assigned to the session which was FlushMode.AUTO.
I was able to prevent the flush by calling setHibernateFlushMode(FlushMode.COMMIT) on the query prior to making the query. This causes the session FlushMode to temporarily change until after the query has been run. After that, the evict() call worked as expected.
Does Spring and Hibernate supports Session sharing between two different (nested or sequenced) physical Transactions ?
I know that Spring supports nested transactions, but it is the same Physical Transaction just with save points, i.e. nested transaction is separated logically :
From Spring docs:
PROPAGATION_NESTED uses a single physical transaction with multiple savepoints that it can roll back to.
So, can i achive behavior similar to:
#Transactional
void invokeOuterTransaction() {
invokeInnerTransaction();
}
#Transactional
void invokeInnerTransaction() {
// here are the same Session as in 'invokeOuterTransaction',
// but this transaction is new PHYSICAL transaction
}
So, exploring this issue, using setup below, i discovered, that Hibernate Session "per request" and not "per transaction" is quite interesting.
Setup:
Hibernate 5, Spring 5, PostgreSQL
Below is a quick java-like pseudo-code for short:
#Controller {
#Inject FirstService fServ;
#Inject SecondService sServ;
#RequestMapping
handleHttpRequest() {
fServ.invokeFirstTransactional();
sServ.invokeSecondTransactional();
}
}
FirstService {
#Transactional
void invokeFirstTransactional() {
// Session object system hashcode = 187000543
// Thread object system hashcode = 167000522
// Transaction_ID in database = 650
}
}
SecondService {
#Transactional
void invokeSecondTransactional() {
// Session object system hashcode = 187000543
// Thread object system hashcode = 167000522
// Transaction_ID in database = 651
}
}
As you can see - same Hibernate Session, Same Thread, but DIFFERENT PHYSICAL transactions !
I am using spring boot with spring data jpa and postgre. I have "item" entity that has price, quantity, auto generated int id and order that it belongs to.
I've searched how to edit that entity changing its price and quantity only, without making new entity and the only answer I got is to get the entity from the db and set each property to the new one then save it. But if i have 6 other properties except price and quantity that means in the update method i will set a property 8 times and this seems to me like way too much boilerplate code for spring. My question is: Is there better/default way to do that?
You can provide a copy constructor:
public Item(Item item) {
this(item.price, item.quantity);
}
or use org.springframework.beans.BeanUtils method:
BeanUtils.copyProperties(sourceItem, targetItem, "id");
Then in controller:
#RestController
#RequestMapping("/items")
public class ItemController {
#Autoware
private ItemRepo repo;
#PutMapping("/{id}")
public ResponseEntity<?> update(#PathVariable("id") Item targetItem, #RequestBody Item sourceItem) {
BeanUtils.copyProperties(sourceItem, targetItem, "id");
return ResponseEntity.ok(repo.save(targetItem));
}
}
No, you don't need to set anything for 8 times. If you want to change price and quantity only, just change those two. Put it in a #Transactional method:
#Transactional
public void updateItem(Item item){
// ....
// EntityManager em;
// ....
// Get 'item' into 'managed' state
if(!em.contains(item)){
item = em.merge(item);
}
item.price = newPrice;
item.quantity = newQuantity;
// You don't even need to call save(), JPA provider/Hibernate will do it automatically.
}
This example will generate a SELECT and a UPDATE query. And that's all.
Try using #Query annotation and define your update statement
#Modifying
#Transactional
#Query("update Site site set site.name=:name where site.id=:id")
void updateJustNameById(#Param("id")Long id, #Param("name")String name);
You should use spring data rest which handles all of this by itself. you just have to call a patch request at the specified URL and provide the changed entity properties. if you have some knowledge of spring data rest have a look at https://github.com/ArslanAnjum/angularSpringApi.
Just use this #DynamicUpdate in your Entity class
#DynamicUpdate
public class Item{
}
I'm trying to use the DomainClassConverter from Spring Data to load entities in a controller and then use these entities in a service.
The problem is that I get a LazyInitializationException when I access lazy loaded collection from my Service.
Adding Transactional annotation to the controller does not help, I guess the conversion occurs before the start of the controller transaction.
Is there a way to use this converter in this kind of use case ? can I reattach the entity to the current session someway ?
My controller:
#RestController
#RequestMapping("/api/quotation-requests/{id}/quotation")
public class QuotationResource {
#RequestMapping(value = "/lines", method = RequestMethod.POST, params="freeEntry")
#Timed
public ResponseEntity<PricingLineDTO> addFreeEntryLine(#PathVariable("id") Quotation quotation, #RequestBody PricingLineDTO pricingLineTo)
{
PricingLine pricingLine = conversionService.convert(pricingLineTo, PricingLine.class);
pricingLine = quotationService.addFreeLineToQuotation(quotation, pricingLine);
return new ResponseEntity<>(conversionService.convert(pricingLine, PricingLineDTO.class), HttpStatus.OK);
}
}
The service:
#Service
#Transactional
public class QuotationService {
public PricingLine addFreeLineToQuotation(Quotation quotation, PricingLine pricingLine) {
quotation.getPricingLines().add(pricingLine); // org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: x.y.z.Quotation.pricingLines, could not initialize proxy
}
}
And the entity
#Entity
public class Quotation {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<PricingLine> pricingLines = new ArrayList<>();
}
If it is not possible, what is my best alternative :
make the controller transactionnal, inject data repositories in it and still offer a Service API that takes Entities parameters instead of IDs.
Cons: controller become transactional, which seem to be commonly considered as a bad practice; it introduces boilerplate code.
make the Service API takes IDs as parameters, letting the controller out of the transaction.
Cons: The API become harder to read and can be error prone as every entities as referred as "Long" object, especially when a Service method needs severals entities as input. For example:
void addUserToGroup(Long userId, Long groupId)
One could easily switch parameters.