I have this locking method (New TX) on Spring boot application and SQL Server, Java. Is it possible that this locks whole Car entity in database? Because other thread that wants to lock other carId is hanging. Moreover it seems to work on one node and problems are on external server. Could it be related to SQL Server configuration?
#Override
public void lockCar(Long entityId) {
Map<String, Object> properties = getLockProperties();
entityManager.find(Car.class, entityId, LockModeType.PESSIMISTIC_WRITE, properties);
Car car = carRepository.findById(entityId)
.orElseThrow(() -> new EntityIdNotFoundException(Car.class, String.valueOf(entityId)));
if (iCarLocked(car)) {
throw new EntityLockedException(Car.class, entityId);
}
car.setLockedDate(LocalDateTime.now());
}
In logs I see:
select car0_.id as id1_6_0_, car0_.action as aktion2_6_0_ from car
car0_ with (updlock, holdlock, rowlock) where car0_.id=?
So is it locking whole table or row only?
Related
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.
I am locking one row from a table using PESSIMISTIC_WRITE in MySQL .
However when I execute 'select' statement using MySQL Workbench that particular row is still visible.
Is there any way not to show-up this row when I use 'select' statement. Although I am not able to update that locked row, which means PESSIMISTIC_WRITE lock is working correctly.
Service Layer
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = {Exception.class})
public List<Employee> getEmployeeList()
{
return employeeDAO.getEmploeeList();
}
Dao Layer
#Override
public List<Employee> getEmploeeList() {
List<Employee> employeeList = null;
this.session = this.sessionFactory.getCurrentSession();
String myQry = "from Employee";
employeeList = session.createQuery(myQry, Employee.class)
.setMaxResults(1)
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.getResultList();
Employee e = employeeList.get(0);
return employeeList;
}
The entire code is available at https://github.com/vishy-wisy/Java/tree/main/LockingDemo
Thanks in advance for your help!
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'm using
Spring Boot 1.4.2
Spring Data JPA 1.10.5
PostgreSQL 9.5 database
I want to have a findOne method with pessimistic lock in my Spring Data repository that is separate from the findOne method that is already provided.
Following this answer I wrote:
public interface RegistrationRepository extends CrudRepository<Registration, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query("select r from Registration r where r.id = ?1")
Registration findOnePessimistic(Long id);
}
This almost works.
Unfortunately, this does not refresh previous instance of my entity in the entity manager cache. I have two concurrent requests updating the status of my registration
the second one waits for the transaction of the first one to commit
the second one does not take into account the changes made by the first one.
Hence broken behavior.
Any clue why #Lock does not out of the box refresh the entity manager?
Update
Here is the requested example code:
public interface RegistrationRepository extends CrudRepository<Registration, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query("select r from registration_table r where r.id = ?1")
Registration findOnePessimistic(Long id);
}
public void RegistrationService {
#Transactional
public void doSomething(long id){
// Both threads read the same version of the data
Registration registrationQueriedTheFirstTime = registrationRepository.findOne(id);
// First thread gets the lock, second thread waits for the first thread to have committed
Registration registration = registrationRepository.findOnePessimistic(id);
// I need this to have this statement, otherwise, registration.getStatus() contains the value not yet updated by the first thread
entityManager.refresh(registration);
registration.setStatus(newStatus);
registrationRepository.save(registration);
}
}
You need to use the entityManger transaction that Spring creates for you :
#Transactional
public void doSomething(long id){
// Both threads read the same version of the data
Registration registrationQueriedTheFirstTime = registrationRepository.findOne(id);
// First thread gets the lock, second thread waits for the first thread to have committed
Registration registration = registrationRepository.findOnePessimistic(id);
// I need this to have this statement, otherwise, registration.getStatus() contains the value not yet updated by the first thread
entityManager.refresh(registration);
EntityManager em = EntityManagerFactoryUtils.getTransactionalEntityManager(<Your entity manager factory>);
em.refresh(registration);
registration.setStatus(newStatus);
registrationRepository.save(registration);
}
}
My problem is that Hibernate does not read any data from DB if it was already inserted manually (I'm using MySQL). What I mean is that I dropped PATIENTS table and then Hibernate created it for me and after that I inserted data from PATIENTS.sql. When I launch my application there are no patients displayed in, for example, show_patients.jsp. But I still can access my application via login page using inserted records.
Here is my method in #Controller:
#RequestMapping(value = "/therapist/showPatients", method = RequestMethod.GET)
public String showExistingPatients(ModelMap map) {
List<Patient> patientsList = userService.getAllPatients();
map.addAttribute("patientsList", patientsList);
return "therapist/show_patients";
}
Here is my method in UserDAOImpl:
public List<Patient> selectAllPatients(){
DetachedCriteria criteria = DetachedCriteria.forClass(Patient.class);
List<Patient> patients = null;
try{
patients = getHibernateTemplate().findByCriteria(criteria);
} catch (DataAccessException ex){
ex.printStackTrace();
}
return patients;
}
The only way I can see any patients in show_patients.jsp is to add new patient via my application allowing Hiberante to save it. So my question is if there is any condition why this works only after Hibernate's save() or am I doing something wrong?
Two variants:
Query cache is turned on in Hibernate. Check settings; maybe, "evict" can be used for remove query results from cache.
Maybe, web page caching present on WebServer or browser settings, check required.