A Grails 2.3.4 application is connecting to an Oracle database using the following domain class:
class Person {
String name
static mapping = {
id column: "PERSON_ID", generator: "sequence", params: [sequence: 'person_seq']
}
}
The PersonController makes a call to a method in PersonService and it makes a call to UtilService. The method in UtilService being called has some logic based on wether this Person object is new:
if (personInstance.id == null) { ... }
What I have found is that the id property of personInstance (which is passed through the method calls described above) is assigned when the UtilService is called.
The controller action calling PersonService is #Transactional, and the services do not have any transaction configuration.
So, a couple of questions:
When is id value assigned by GORM (I assumed at insert but that seems wrong)?
Is there a better way of checking if the object is new (isAttached() returns true so that's not good for me)?
EDIT: save() has not been called on the personInstance when UtilService does the id check.
The id is assigned when you call save(). For most persistence calls, Hibernate delays flushing the change until it feels it has to flush) to ensure correctness. But save() calls are treated differently. I believe the motivation is that even if we know that flush() will eventually be called, even if it's at the very end of the request, we want to retrieve the id early so it doesn't suddenly change.
Note that a service that does "not have any transaction configuration" is transactional - the only way to get a non-transactional service is to remove all #Transactional annotations (the newer Grails annotation and the older Spring annotation) and add
static transactional = false
All other services are transactional, although you can configure individual methods to be ignored ## Headin.
Turns out, I had a findBy which was flushing the session:
utilService.someMethod(Person.findByUsername(username))
It was at this point that the id was populated.
Got around it by using withNewTransaction:
def personInstance = Person.withNewSession { Person.findByUsername(username) }
utilService.someMethod(personInstance)
Which now leads me onto the next question...
Related
I am autowiring service in controller. And in service, I have a scenario where I need to throw an exception and DB changes also. So, I tried #Async
#Transactional
public void verifyOtp(OtpDto otpDto)
...
if(xyz){
deactivateOtp(emp.getId());
throw new ServException("Mobile no requested is already assigned", "error-code");
}
}
#Async
#Transactional //with or without
public void deactivateOtp(Integer id){
otpRepo.deactivateOtp(id);
}
public interface OtpRepository extends JpaRepository<Otp, Integer> {
#Modifying
#Query("UPDATE Otp SET isActive = 0 WHERE id = :id")
public void deactiveOtp(#Param("id") Integer id);
This is not creating new thread. But, if I gives at repo, it works
public void deactivateOtp(Integer id){
otpRepo.deactivateOtp(id);
}
public interface OtpRepository extends JpaRepository<Otp, Integer> {
#Async
#Transactional
#Modifying
#Query("UPDATE Otp SET isActive = 0 WHERE id = :id")
public void deactiveOtp(#Param("id") Integer id);
First of all check that the service is wrapped into proxy (you can place a breakpoint in controller and see the reference to the service, it will be with proxy). Otherwise there is something wrong with the configuration and #Transactional/#Async won't work till you fix that.
Now, assuming this is not an issue, there is an issue in the code:
When the controller calls service.verifyOtp it goes to the proxy (to handle the transaction) and then to your implementation.
But when it reaches your implementation and you call the method that belongs to the same impl, it doesn't pass through the proxy again, instead it directly goes to the deactivateOtp as if there is no spring at all here. Of course, #Async doesn't work.
In terms of resolution:
Consider using self injection if you work with spring 4.3+. Read this thread for more information.
Alternatively, refactor your code so that the deactivateOtp will be a public method of another class. In this case the call won't be "internal" anymore, it will path through Proxy hence the #Async will work.
This is discussed many times.
Basically, Spring AOP will not intercept local method call
(in your case call deactivateOtp() within the same class)
You can read more about this behavior here: Understanding AOP proxies
Highlight:
The key thing to understand here is that the client code inside the main(..) of the Main class has a reference to the proxy. This means that method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.
Latest Spring Boot with JPA and Hibernate: I'm struggling to understand the relationship between transactions, the persistence context and the hibernate session and I can't easily avoid the dreaded no session lazy initialization problem.
I update a set of objects in one transaction and then I want to loop through those objects processing them each in a separate transaction - seems straightforward.
public void control() {
List<> entities = getEntitiesToProcess();
for (Entity entity : entities) {
processEntity(entity.getId());
}
}
#Transactional(value=TxType.REQUIRES_NEW)
public List<Entity> getEntitiesToProcess() {
List<Entity> entities = entityRepository.findAll();
for (Entity entity : entities) {
// Update a few properties
}
return entities;
}
#Transactional(value=TxType.REQUIRES_NEW)
public void processEntity(String id) {
Entity entity = entityRepository.getOne(id);
entity.getLazyInitialisedListOfObjects(); // throws LazyInitializationException: could not initialize proxy - no Session
}
However, I get a problem because (I think) the same hibernate session is being used for both transactions. When I call entityRepository.getOne(id) in the 2nd transaction, I can see in the debugger that I am returned exactly the same object that was returned by findAll() in the 1st transaction without a DB access. If I understand this correctly, it's the hibernate cache doing this? If I then call a method on my object that requires a lazy evaluation, I get a "no session" error. I thought the cache and the session were linked so that's my first confusion.
If I drop all the #Transactional annotations or if I put a #Transactional on the control method it all runs fine, but the database commit isn't done until the control method completes which is obviously not what I want.
So, I have a few questions:
How can I make the hibernate session align with my transaction scope?
What is a good pattern for doing the separation transactions in a loop with JPA and declarative transaction management?
I want to retain the declarative style (i.e. no xml), and don't want to do anything Hibernate specific.
Any help appreciated!
Thanks
Marcus
Spring creates a proxy around your service class, which means #Transactional annotations are only applied when annotated methods are called through the proxy (where you have injected this service).
You are calling getEntitiesToProcess() and processEntity() from within control(), which means those calls are not going through proxy but instead have the transactional scope of the control() method (if you aren't also calling control() from another method in the same class).
In order for #Transactional to apply, you need to do something like this
#Autowired
private ApplicationContext applicationContext;
public void control() {
MyService myService = applicationContext.getBean(MyService.class);
List<> entities = myService.getEntitiesToProcess();
for (Entity entity : entities) {
myService.processEntity(entity.getId());
}
}
I’m using Spring 3.2.11.RELEASE, JPA 2.1, Hibernate 4.3.6.Final, and MySQL 5.5.37. I’m getting the below error in a JUnit test
testSendValidAssignment(org.mainco.subco.thirdParty.service.ThirdPartyServiceIT) Time elapsed: 13.366 sec <<< ERROR!
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.mainco.subco.lessonplan.domain.LessonPlan.classrooms, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:575)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:155)
at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:278)
at org.mainco.subco.ThirdParty.service.ThirdPartyServiceIT.testSendValidAssignment(ThirdPartyServiceIT.java:102)
However, I don’t know what line is to blame for this error message. In my method I have
#Service
#Transactional
public class MyServiceImpl implements MyService
{
....
#Override
public void sendAssignment(final String assignmentId)
{
final Assignment assignment = m_lessonPlanDao.getAssignment(assignmentId);
if (processData(assignment))
{
// Gather the students who have been assigned this assignment
final List<Classroom> classes = lessonPlan.getClassrooms();
System.out.println("got " + classes.size() + " classes.");
// Send one request for each class assignment
for (final Classroom classroom : classes)
{
final List<User> classStudents = m_classroomSvc.findClassStudents(classroom.getId());
System.out.println("got " + classStudents.size() + " students.");
and both System.out lines print numbers. There are no other references to this lazily-loaded collection in the method so I don’t know what else to check in my data. Any advice is appreciated.
Your service method is transactional, but your entire test method is not. The size checking of the element is performed in an assertion statement in your JUnit test and is done outside of the transaction scope of the service, so it causes lazy initialization exception. There are three ways you can go
you can try call size() method inside your dao method to make
outside call of the size() method safe.
you can force the user of your method to
open transaction(document which objects are detached and make it a
contract/or use infamous open session in view pattern)
you can create a DTO layer and return size as a part of DTO
all three solutions have pros and cons:
the first solution will look weird for the supporters of the code when they'll find that you call a getter for no obvious reson - it needs commenting.
the second makes life harder to the users of the interface
the third violates DRY principle
p.s. you can also disable lazy initialization of course
I have an #ModelAttribute("myModel") that is injected into a spring form and also stored as part of the session in a #SessionAttribute("myModel").
At the controller, the model is picked up and changes merged into the #SessionAttribute.
#RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(#ModelAttribute("myModel") MyModel myModel) {
myModel.getSomething();
...
}
The problem is that all my hibernate domain objects are LAZY. Because the Hibernate Session that created myModel no longer exists, when I try to access someting that was not included as part of a fetch statement in the HQL, I get a LazyInitializationException.
Hibernate has a merge() method, how can I use this to merge an object created in a previous hibernate session into the current one so that all the LAZY properties can be accessed? Or is there another way of doing this?
Call merge(), and use its returned value: it's the attached entity containing the values found in the detached entity passed as argument:
Foo modifiedAttachedFoo = session.merge(modifiedDetachedFoo);
modifiedAttachedFoo.getLazyCollection().size(); // no problem: the entity is attached
I've got in my project code similar to this
#Transactional(readOnly = true)
public void tt() {
dd();
}
#Transactional()
public void dd() {
gg();
}
#Transactional(readOnly = true)
public void gg() {
}
Function dd is used both by other readonly transaction functions and not readonly functions. Assuming that transaction should extendend from execution of tt to gg - operations in dd will in be read-only transaction or not?
In this particular example, your question is moot.
The call to dd() from tt() will not pass the proxy boundary so no transactional advise will be applied to dd() (since it's a call inside the same instance). Same with the call to gg() from dd(). Consequently, only the call from outside to tt() would actually be transaction-advised (in your case, with readOnly=true) and that would be the transaction that would be used in the entire call-chain.
In the general case though, read the documentation hinted by #melihcelik - it explains the behavior.
Spring's AbstractPlatformTransactionManager has a property named validateExistingTransaction that controls this behavior. Javadoc states that:
When participating in an existing transaction (e.g. with PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing transaction), this outer transaction's characteristics will apply even to the inner transaction scope. Validation will detect incompatible isolation level and read-only settings on the inner transaction definition and reject participation accordingly through throwing a corresponding exception.
Since default propagation for Spring #Transactional annotation is REQUIRED and default validation strategy is false, I expect Spring to use existing transaction created from tt method call in readonly mode.
If you want to have a read only transaction, then you have to annotate your method with:
#Transactional(propagation=Propagation.REQUIRES_NEW, readOnly=true)
use #Transactional(readoOnly = true) if you are performing a get/select and not making any changes, this means that no locks will be applied (which is more efficent).
For updates/inserts/deletions/saves/merges I use (when a lock is required) :
#Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)