Recently I faced an issue regarding updating entities from a #Scheduled method where it would fail with the exception org.hibernate.TransientPropertyValueException: object references an unsaved transient instance even though it would work seamless when invoked from a #RestController method. This is the relevant example:
The offending method (other parts of the class omitted for brevity):
#Service
public class AnonymizationService
{
private final ItemRepository itemRepository;
public Result anonymizeItemsOlderThan(int days) {
List<Item> data = itemRepository.findAllByCreatedDateBeforeAndAnonymizationDateIsNull(Instant.now().minus(days, ChronoUnit.DAYS));
List<String> itemsAnonymized = new ArrayList<>(data.size());
data.forEach(item -> itemsAnonymized.add(itemRepository.save(item.anonymize()).getRequestId()));
return Result.builder().anonymizedItems(itemsAnonymized).build();
}
}
The #RestController caller (again most stuff omitted):
#RestController
public class DataAnonymizationAPI
{
private final AnonymizationService anonymizationService;
#PutMapping(path = "${datadeletion.path:/anonymize}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Result> anonymizeAll(#Valid DataDeletionRules dataDeletionRules) {
return ResponseEntity.ok(anonymizationService.anonymizeItemsOlderThan(dataDeletionRules.getMinimunAge()));
}
}
Again, this works just fine when used like above. The problem happens when AnonymizationService#anonymizeItemsOlderThan() is instead invoked from the following #Scheduled method:
#Component
public class DataDeletionTasks
{
private final AnonymizationService anonymizationService;
private final DataAnonymizationProperties properties;
#Scheduled(cron = "${datadeletion.anonymization.schedule}")
public void anonymizeItemsPeriodically() {
anonymizationService.anonymizeItemsOlderThan(properties.getAnonymization().getMinAge());
}
}
In this case it fails with the exception mentioned above (org.hibernate.TransientPropertyValueException).
Upon changing the log level to DEBUG and carefully analyzing it, nothing unexpected happens:
When the method is invoked from the #RestController an existing EntityManager is used and a transaction created:
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1702787226<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
When the method is invoked from the #Scheduled method a new EntityManager is created:
o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(644498403<open>)] for JPA transaction
Naturally, my instinct was to add #Transactional to the Anonymization#anonymizeItemsOlderThan() method which immediately solved it, but why?
Why does it work in one case and not in the other? Why does the saveAndFlush() must be performed using the same EntityManager used to retrieve the entity in the first place?
This situation made me think my knowledge is flawed on a very basic level, but somehow couldn't find a clear explanation to it. In any case feel free to point me towards relevant literature that might help me.
Related
I have a Spring Boot 2.3 REST application with a standard architecture (controller -> service -> repository). For auditing purposes, I inserted a thin layer (some kind of a Mediator), so that I persist all the requests to some specific service method regardless they are successfully persisted or an exception is thrown and the transaction is rollbacked. Example:
#Component
public class Mediator {
private final Service service;
private final AuditService auditService;
public Mediator(Service service, AuditService auditService) {
this.service = service;
this.auditService = auditService;
}
#Transactional
public void saveReport(Report report) {
try {
service.saveReport(report);
auditService.saveReport(report);
} catch (Exception exception) {
auditService.saveReport(report, exception);
throw exception;
}
}
}
Thus I encountered a weird situation: if I place the #Transactional on the Mediator's method (example above), all the operations in the JPA Repositories are successfully persisted. If I place the #Transactional on the ServiceImpl method instead (example below) and no longer on the Mediator, one of the delete queries is not ran, although the rest of the queries are executed just fine. Suppose my ServiceImpl looks something like:
#Service
public class ServiceImpl implements Service {
private final RepositoryA repositoryA;
private final RepositoryB repositoryB;
public ServiceImpl(RepositoryA repositoryA, RepositoryB repositoryB) {
this.repositoryA = repositoryA;
this.repositoryB = repositoryB;
}
#Transactional
public void saveReport(Report report) {
repositoryA.save(report.getA());
repositoryB.save(report.getB());
repositoryB.delete(report.getSomethingElse());
}
}
The only visible difference between the two approaches with respect to the Transactions is that in the first scenario, I have this for each Mediator call:
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(909894553<open>)] for JPA transaction
while in the second scenario, I have this:
tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
I guess there is a difference between annotating directly a bean's method with #Transactional (what I do in the Mediator) and annotating a bean's (that is the implementation of the interface injected) method with the same thing (what we usually do by annotating a ServiceImpl method), but I am not sure and cannot spot the reason for this weird behaviour. Does anyone have an idea why this happens?
I guess that this difference in behavior is due to Spring OpenSessionInView which is enabled by default.
You must set in application.yml
spring:
jpa:
open-in-view: false
Please see OSIV
I am sure that I am missing something, but I don't know exactly what...
Giving the following snippet:
#Service
public class MyClass {
private MyClass self;
private UserRepository userRepository;
private ApplicationContext applicationContext;
#PostConstruct
private void init() {
self = applicationContext.getBean(MyClass.class);
}
#Transactional
public void doA(User user) {
...
if (condition) {
self.doB(user);
throw new SecurityException();
}
user.setRandomField("x");
userRepository.save(user);
}
#Transactional(value = Transactional.TxType.REQUIRES_NEW)
public void doB(User user) {
...
userRepository.save(user);
}
}
What do I know about #Transactional is that if it is used, is redundant to call repository.save(entity).
What I am trying to do, is to process an entity from a transactional method, and if there is a breaking condition, call a new method (annotated with REQUIRES_NEW) that will update some fields of the entity and save it. The root method (doA) then throws an exception. FYI: the #Transactional(dontRollbackOn = SecurityException.class) is not an option in this situation.
For using this commiting mechanism, instead of creating a new bean just with one method I just injected the current bean into a variable just called self, therefore I can use the bean proxy for transaction management.
The odd thing is that if I am removing from doB the save call, when doA transaction is rollbacked because of the SecurityException, the changes performed by doB are rollbacked as well. But if I let it in there, this is working as expected.
Am I doing something wrong or am I missing something?
Thanks!
Try to do not pass User instance in the doB().
Pass an Id instead and read the User from the repo internally. I am not sure how the attached entity is handled between the different sessions.
While processing a request, I would like to 'kick off' separate task in a separate transaction based on the data received and stored in the database.
The main advantage is that I can return the result to my request before all additional processing is done, also if additional processing fails, this will not affect the original processing of the request (as they are in different transactions). This additional processing might require adding extra information to the data.
I have the following setup in mind. Where using #Asynchronous the additional task is created.
#Stateless
public class Bean1
{
#EJB
Bean2 bean2;
#PersistenceContext
private EntityManager entityManager;
public MyResult doSomething(MyInput input) {
MyEntity myEntity = new MyEntity();
// Fill data based on input
entityManager.persist(myEntity);
bean2.asyncActOnData(myEntity);
// Perhaps do some more work and return result
}
}
#Stateless
public class Bean2
{
#Asynchronous // This causes a new transaction to happen
public void asyncActOnData(MyInput input){
// Act upon data and store result in DB
}
}
This seems like a clean way, however this causes JPA Entity to become detached, possibly during processing in Bean2.
Currently I don't plan on changing the data after the persist call (and asnyc), but as the application grows I feel it would be safer to allow this to avoid mistakes.
What is the correct way to kick off the separate asynchronous task based on the persisted data?
I am using: Java EE 6 with Eclipselink 2.5.2 for JPA.
You can continue to make use of the detached instance provided that:
You're not planning to perform further persistence operations;
All associated entities (if any) were available when asyncActOnData was invoked.
However, if you need to perform further persistence operations you can do the following:
#Stateless
public class Bean1
{
#EJB
Bean2 bean2;
#PersistenceContext
private EntityManager entityManager;
public MyResult doSomething(MyInput input) {
MyEntity myEntity = new MyEntity();
// Fill data based on input
entityManager.persist(myEntity);
// Flush the persistence context and detach the entity
// An entity is not permitted to be in more than one
// persistence context at a time. This should prevent
// a race condition with the merge in bean2.
entityManager.flush();
entityManager.detach(myEntity);
Future<Result> futureResult = bean2.asyncActOnData(myEntity);
// Perhaps do some more work and return result
....
return futureResult.get();
}
}
#Stateless
public class Bean2 {
#PersistenceContext
private EntityManager entityManager;
#Asynchronous
public Future<Result> asyncActOnData(MyInput input) {
// this will ensure that the database still matches input
// and add input into the persistence context
input = entityManager.merge(input);
...
return new javax.ejb.AsyncResult<Result>(result);
}
}
You will find it useful to read §3.2 of the "Java™ Persistence API, Version 2.1" specification.
I have a Question regarding the JPA Usage in Play.
I have already created a Model Class and annotated it with #Entity, and a Controller Class, in which i have a static method annotated with #play.db.jpa.Transactional. When i start activator run, everything is compiled correctly, and the Database is started, but as soon as the Code where i call the Entity Manager via
EntityManager em = play.db.jpa.JPA.em();
a NullException is thrown.
As far as I am concerned, the persistence.xml and the application.conf is correct, i have already researched the web, but to no avail.
What are possible Solutions for this?
Thanks in advance!
Here is the concrete Piece of code where I use the JPA:
public class UserController extends Controller {
#play.db.jpa.Transactional
public static Result registerResponse(){
System.out.println("registerResponse called!");
Form<MyUser> regForm = Form.form(MyUser.class).bindFromRequest();
if (regForm.hasErrors()) {
System.out.println("bad request");
System.out.println("Form: " + regForm.toString());
System.out.println("Errors: "+regForm.errors().toString());
return badRequest(views.html.registration.render(regForm,Arrays.asList(Avatar.values())));
} else {
MyUser newuser = regForm.get();
System.out.println("persisting");
EntityManager em = play.db.jpa.JPA.em(); //Here is the Nullpointer exception
em.persist(newuser);
System.out.println("persisted");
return ok(authentication.render(Form.form(Login.class)));
}
}
I think that you do not need to instatiate this class, you need to use he methods in play.db.jpa.JPA.em()
change this
EntityManager em = play.db.jpa.JPA.em(); //Here is the Nullpointer exception
em.persist(newuser);
by this:
try {
JPA.em().persist(newuser);
}
catch(PersistenceException pe){
//the code for the exception
}
with the import import play.db.jpa.JPA;
I get the an exception when trying to get data, lazily(Exception at the very end)
//application gets data by the following DAO.
public T findById(PK id) {
T result = getHibernateTemplate().get(this.type, id);
getHibernateTemplate().flush();
return result;
}
//Junit test calls a serviceX.getById
#Transactional
public SomeObject getById(int x){
return (SomeObject) aboveDao.findById(x);
}
//Withing the JUnit
SomeObject someObj = serviceX.getById(3);
someObj.getAnotherObject().y.equals("3"); //**Exception** at this line.
//SomeObject class has the following property.
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
private AnotherObject anotherObject;
I get the following exception when tryin to access anotherObject in the junit
Methods already tried + extra configuration
We use spring annotation TransactionManager.
<tx:annotation-driven /> specified in the config file.
Also, I tried to add #Transaction(propagation = Propagation.REQUIRED) on top of the JUnit, this did not solve the issue. If I run the application, it works without any issues.
How to solve this type of issue for JUnit?
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role xxxxx , no session or session was closed
Here's what happens
SomeObject someObj = serviceX.getById(3); // #Transactional boundary, no more session
someObj.getAnotherObject().y.equals("3"); // session doesn't exist, can't fetch LAZY loaded object
Because your AnotherObject is LAZY fetched, it doesn't actually get loaded in the getById() method. The Session it was associated with is lost when the #Transactional ends, ie. when execution returns from getById(). Because there is no longer a Session, you get the exception.
You can change your FetchType to EAGER. If you're going to that field of your object, you need to initialize it in your Transaction boundaries.
If you only some times need the anotherObject, a possible solution is to create a #Transactional method that calls the getById and eagerly loads the object.
#Transactional
public SomeObject eagerGetById(int x){
SomeObject obj = getById(x);
obj.getAnotherObject(); // will force loading the object
return obj;
}
Calls this method whenever you need to eagerly load the object.
This is could be useful to you LazyInitilializationException