I'm not sure if anyone has experienced this particular twist of the LazyInitialization issue.
I have a console Spring Boot application (so: no views - everything basically happens within a single execution method). I have the standard beans and bean repositories, and the typical lazy relationship in one of the beans, and it turns out that unless I specify #Transactional, any access to any lazy collection automatically fails even though the session stays the same, is available, and is open. In fact, I can happily do any session-based operation as long as I don't try to access a lazy collection.
Here's a more detailed example :
#Entity
class Path {
... `
#OneToMany(mappedBy = "path",fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#OrderBy("projectOrder") `
public List<Project> getProjects() {
return projects; `
}`
}
Now, the main method does something as simple as this:
class SpringTest {
... `
#Autowired
private PathRepository pathRepository;
void foo() {
Path path = pathRepository.findByNameKey("...");
System.out.println(path.getProjects()); // Boom <- Lazy Initialization exception}
}
Of course if I slap a #Transactional on top of the method or class it works, but the point is - why should I need that? No one is closing the session, so why is Hibernate complaining that there´s no session when there is one?
In fact, If I do:
void foo() {
System.out.println(entityManager.unwrap(Session.class));
System.out.println(entityManager.unwrap(Session.class).isOpen());
Path basic = pathRepository.findByNameKey("...");
System.out.println(entityManager.unwrap(Session.class));
System.out.println(entityManager.unwrap(Session.class).isOpen());
System.out.println(((AbstractPersistentCollection)basic.projects).getSession());
Path p1 = pathRepository.findByNameKey("....");
}
I get that the session object stays the same the whole time, it stays open the whole time, but the internal session property of the collection is never set to anything other than null, so of course when Hibernate tries to read that collection, in its withTemporarySessionIfNeeded method it immediately throws an exception
private <T> T withTemporarySessionIfNeeded(LazyInitializationWork<T> lazyInitializationWork) {
SharedSessionContractImplementor tempSession = null;
if (this.session == null) {
if (this.allowLoadOutsideTransaction) {
tempSession = this.openTemporarySessionForLoading();
} else {
this.throwLazyInitializationException("could not initialize proxy - no Session");
}
So I guess my question would be - why is this happening? Why doesn't Hibernate store or access the session from which a bean was fetched so that it can load the lazy collection from it?
Digging a bit deeper, it turns out that the repository method executying the query does a
// method here is java.lang.Object org.hibernate.query.Query.getSingleResult()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
if (SharedEntityManagerCreator.queryTerminatingMethods.contains(method.getName())) {
...
EntityManagerFactoryUtils.closeEntityManager(this.entityManager); // <--- Why?!?!?
this.entityManager = null;
}
...
}
and the above closeEntityManager calls unsetSession on all collections:
SharedSessionContractImplementor session = this.getSession();
if (this.collectionEntries != null) {
IdentityMap.onEachKey(this.collectionEntries, (k) -> {
k.unsetSession(session);
});
}
But why?!
(Spring Boot version is 2.7.8)
So, after researching more it appears that this is standard behavior in Spring Boot - unless you use your own EntityManager, the one managed automatically by Spring is either attached to a #Transactional boundary, or opens and closes for each query.
Some relevant links:
Does Entity manager needs to be closed every query?
Do I have to close() every EntityManager?
In the end, I ended using a TransactionTemplate to wrap my code into a transaction without having to mark the whole class #Transactional.
Related
I am totally confused with one issue Spring data + hibernate
We have a Restful service which we are migrating to V2.
So the old controller looks like
#Api(tags = {"assignments"})
#RestController
#CheckedTransactional
public class AssignmentListController {
#Inject
private AssignmentListService assignmentListService;
//REST function
public list() {....}
}
The REST function list calls AssignmentListService to load assignments, which is a collection, and loads some data lazily. Its works excellent.
What I did is I copied this controller as name AssignmentListControllerV2, and it looks like
#Api(tags = {"assignments"})
#RestController
#CheckedTransactional
public class AssignmentListControllerV2 {
#Inject
private AssignmentListService assignmentListService;
#Inject
private AssignmentDtoMapper assignmentDtoMapper;
public list() {...}
}
The code is same except AssignmentDtoMapper bean added, which is created using MapStruct.
Now the problem, When I call this new service, somehow I get a Lazy Load exception. The error is
could not initialize proxy - no Session
I desperately need some help as I have no clue whats happening. I have just copied the code in a new class and its failing.
The exception is actually pretty clear, Hibernate can't load the lazy fetched member because there is no persistence context open when you hit it.
I suppose that in the V2 the:
#Inject
private AssignmentDtoMapper assignmentDtoMapper;
is to change some JPA business entity into DTO?
It's probably the source of the exception if you try to map not loaded member there.
If you want to avoid the exception on unitiliazed proxy you can try something like
public boolean isProxyInitialized(Object obj){
if(obj instanceof HibernateProxy){
HibernateProxy proxy = (HibernateProxy) obj;
return !proxy.getHibernateLazyInitializer().isUninitialized();
}
return obj != null;
}
It should return true if the member as bean fetched otherwise false.
In my Spring Boot project I have implemented following service method:
#Transactional
public boolean validateBoard(Board board) {
boolean result = false;
if (inProgress(board)) {
if (!canPlayWithCurrentBoard(board)) {
update(board, new Date(), Board.AFK);
throw new InvalidStateException(ErrorMessage.BOARD_TIMEOUT_REACHED);
}
if (!canSelectCards(board)) {
update(board, new Date(), Board.COMPLETED);
throw new InvalidStateException(ErrorMessage.ALL_BOARD_CARDS_ALREADY_SELECTED);
}
result = true;
}
return result;
}
Inside this method I use another service method which is called update:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Board update(Board board, Date finishedDate, Integer status) {
board.setStatus(status);
board.setFinishedDate(finishedDate);
return boardRepository.save(board);
}
I need to commit changes to database in update method independently of the owner transaction which is started in validateBoard method. Right now any changes is rolling back in case of any exception.
Even with #Transactional(propagation = Propagation.REQUIRES_NEW) it doesn't work.
How to correctly do this with Spring and allow nested transactions ?
This documentation covers your problem - https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with #Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. #PostConstruct.
However, there is an option to switch to AspectJ mode
Using "self" inject pattern you can resolve this issue.
sample code like below:
#Service #Transactional
public class YourService {
//... your member
#Autowired
private YourService self; //inject proxy as an instance member variable ;
#Transactional(propagation= Propagation.REQUIRES_NEW)
public void methodFoo() {
//...
}
public void methodBar() {
//call self.methodFoo() rather than this.methodFoo()
self.methodFoo();
}
}
The point is using "self" rather than "this".
The basic thumb rule in terms of nested Transactions is that they are completely dependent on the underlying database, i.e. support for Nested Transactions and their handling is database dependent and varies with it.
In some databases, changes made by the nested transaction are not seen by the 'host' transaction until the nested transaction is committed. This can be achieved using Transaction isolation in #Transactional (isolation = "")
You need to identify the place in your code from where an exception is thrown, i.e. from the parent method: "validateBoard" or from the child method: "update".
Your code snippet shows that you are explicitly throwing the exceptions.
YOU MUST KNOW::
In its default configuration, Spring Framework’s transaction
infrastructure code only marks a transaction for rollback in the case
of runtime, unchecked exceptions; that is when the thrown exception is
an instance or subclass of RuntimeException.
But #Transactional never rolls back a transaction for any checked exception.
Thus, Spring allows you to define
Exception for which transaction should be rollbacked
Exception for which transaction shouldn't be rollbacked
Try annotating your child method: update with #Transactional(no-rollback-for="ExceptionName") or your parent method.
Your transaction annotation in update method will not be regarded by Spring transaction infrastructure if called from some method of same class. For more understanding on how Spring transaction infrastructure works please refer to this.
Your problem is a method's call from another method inside the same proxy.It's self-invocation.
In your case, you can easily fix it without moving a method inside another service (why do you need to create another service just for moving some method from one service to another just for avoid self-invocation?), just to call the second method not directly from current class, but from spring container. In this case you call proxy second method with transaction not with self-invocatio.
This principle is useful for any proxy-object when you need self-invocation, not only a transactional proxy.
#Service
class SomeService ..... {
-->> #Autorired
-->> private ApplicationContext context;
-->> //or with implementing ApplicationContextAware
#Transactional(any propagation , it's not important in this case)
public boolean methodOne(SomeObject object) {
.......
-->> here you get a proxy from context and call a method from this proxy
-->>context.getBean(SomeService.class).
methodTwo(object);
......
}
#Transactional(any propagation , it's not important in this case)public boolean
methodTwo(SomeObject object) {
.......
}
}
when you do call context.getBean(SomeService.class).methodTwo(object); container returns proxy object and on this proxy you can call methodTwo(...) with transaction.
You could create a new service (CustomTransactionalService) that will run your code in a new transaction :
#Service
public class CustomTransactionalService {
#Transactional(propagation= Propagation.REQUIRES_NEW)
public <U> U runInNewTransaction(final Supplier<U> supplier) {
return supplier.get();
}
#Transactional(propagation= Propagation.REQUIRES_NEW)
public void runInNewTransaction(final Runnable runnable) {
runnable.run();
}
}
And then :
#Service
public class YourService {
#Autowired
private CustomTransactionalService customTransactionalService;
#Transactional
public boolean validateBoard(Board board) {
// ...
}
public Board update(Board board, Date finishedDate, Integer status) {
this.customTransactionalService.runInNewTransaction(() -> {
// ...
});
}
}
I am upgrading a working project from Spring2+Hibernate3 to Spring3+Hibernate4. Since HibernateTemplate and HibernateDAOSupport have been retired, I did the following
Before (simplified)
public List<Object> loadTable(final Class<?> cls)
{
Session s = getSession(); // was calling the old Spring getSession
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
closeSession(s);
return objects;
}
Now (simplified)
#Transactional(propagation=Propagation.REQUIRED)
public List<Object> loadTable(final Class<?> cls)
{
Session s = sessionFactory.getCurrentSession();
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
return objects;
}
I also added the transaction annotation declaration to Spring XML and removed this from Hibernate properties
"hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext"
The #Transactional annotation seems to have worked as I see this in the stacktrace
at com.database.spring.DatabaseDAOImpl$$EnhancerByCGLIB$$7d20ef95.loadTable(<generated>)
During initialization, the changes outlined above seem to work for a few calls to the loadTable function but when it gets around to loading an entity with a parent, I get the "collection with cascade="all-delete-orphan" was no longer referenced" error. Since I have not touched any other code that sets/gets parents or children and am only trying to fix the DAO method, and the query is only doing a sql SELECT, can anyone see why the code got broken?
The problem seems similar to Spring transaction management breaks hibernate cascade
This is unlikely problem of Spring, but rather issue with your entity handling / definition. When you are using deleteOrphans on a relation, the underlying PersistentSet MUST NOT be removed from the entity itself. You are allowed only to modify the set instance itself. So if you are trying to do anything clever within your entity setters, that is the cause.
Also as far as I remember there are some issues when you have deleteOrphans on both sides of the relation and/or load/manipulate both sides within one session.
Btw. I don't think "hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext" is necessary. In our project, this is the only configuration we have:
#Bean
public LocalSessionFactoryBuilder sessionFactoryBuilder() {
return ((LocalSessionFactoryBuilder) new LocalSessionFactoryBuilder(
dataSourceConfig.dataSource()).scanPackages(ENTITY_PACKAGES).
setProperty("hibernate.id.new_generator_mappings", "true").
setProperty("hibernate.dialect", dataSourceConfig.dialect()).
setProperty("javax.persistence.validation.mode", "none"));
}
#Bean
public SessionFactory sessionFactory() {
return sessionFactoryBuilder().buildSessionFactory();
}
The issue was with Session Management. The same block of transactional code was being called by other modules that were doing their own session handling. To add to our woes, some of the calling modules were Spring beans while others were written in direct Hibernate API style. This disorganization was sufficient work to keep us away from moving up to Hibernate 4 immediately.
Moral of the lesson (how do you like that English?): Use a consistent DAO implementation across the entire project and stick to a clearly defined session and transaction management strategy.
I want to use Spring + Hibernate in my web application.
My application is written without Spring.
When "open page" action is called I open Hibernate Session, store it in Http Session and share it between my DAOs. When save action is called I start transaction using my old session.
But now I want to migrate my old DAOs to HibernateDaoSupport based DAOs.
How can I share session in this case? If my DAOs reference to the one SessionFactory in beans.xml will they share the same session?
How can I manage session in this case(open new or use old)?
I have write the following code but I get
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
on page System.out.println(obj.getCategory().getName());
public class CategoryObjectDAOSpringImpl extends HibernateDaoSupport implements CategoryObjectDAO {
#Override
public CategoryObject get(int id) throws Exception {
CategoryObject obj = getHibernateTemplate().get(CategoryObject.class, id);
System.out.println(obj.getId());
System.out.println(obj.getCategory().getName());
for (ObjAttrCommon objAttr : obj.getAttributes()) {
//objAttr.setSession(getSession());
System.out.println(objAttr.getId());
}
return obj;
}
It is strange that if I add
getSessionFactory().openSession();
call at the top I have the same exeption.
I think the same awswer is valid for this post:
If your "unit of work" cannot be automatically per request, I think you can create it manually in your service layer using a transaction. Something like this:
public Object serviceMethod(params) {
TransactionTemplate transactionTemplate = getHibernateTemplate().get(CategoryObject.class, id);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
try {
// call your DAO's to update/delete/... and set values to service
} catch (DAOException e) {
LOGGER.error(e);
throw new ServiceException(e);
}
}
});
}
EDITED: So extrange that the exception appears within a transaction. Make sure that the problem is not in your view. If after you retrieve your entites using HibernateTemplate you access it from the view it will explain the LazyException, because when you access your objects from the view, your Hibernate session will be already closed.
In you're using your models in your JSP/JSF your need to extend your unit of work to cover all request context. You have to include a filter that manages your Hibernate session opening, what is called Open Session In view Pattern, take a look at this.
Copy the filter implementation that appears in this post and include it in your application, it should solve the problem.
I have been going through some Spring / AOP tutorials and have somewhat familiarized myself with the related concepts.
Now coming to my requirements, I need to create an Activities Log implementation which will save the activities of a logged-in user in the DB which can range from applying for a service or creating new users in case of Admin users, etc. On invocation of any method having an annotation (say #ActivityLog), this information is to be persisted in the form of actorId, actionComment, actionTime, actedUponId, ... etc.
Now, if I create a POJO class (that maps to a ActivityLog table in the DB) and want to save this data from inside the Advice (preferably using the same transaction as the method, method uses #Transactional annotation), how do I actually populate the variables in this POJO?? I can probably get the actorId from the session object & actionTime can simply be new Date() but how about the dynamic values for actionComment / actedUponId?
Any help will be brilliant! (BTW, I have a requirement to not use Hibernate Interceptors.)
Here is a complete example:
#Aspect
#Component
public class WebMethodAuditor {
protected final Log logger = LogFactory.getLog(getClass());
public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
#Autowired
AuditRecordDAO auditRecordDAO;
#Before("execution(* com.mycontrollers.*.*(..))")
public void beforeWebMethodExecution(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
User principal = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Timestamp timestamp = new Timestamp(new java.util.Date().getTime());
// only log those methods called by an end user
if(principal.getUsername() != null) {
for(Object o : args) {
Boolean doInspect = true;
if(o instanceof ServletRequestDataBinder) doInspect = false;
if(o instanceof ExtendedModelMap) doInspect = false;
if(doInspect) {
if(o instanceof BaseForm ) {
// only show form objects
AuditRecord ar = new AuditRecord();
ar.setUsername(principal.getUsername());
ar.setClazz(o.getClass().getCanonicalName());
ar.setMethod(methodName);
ar.setAsString(o.toString());
ar.setAudit_timestamp(timestamp);
auditRecordDAO.save(ar);
}
}
}
}
}
}
If you are looking to get the actionComment and actedUponId from arguments to the annotated method (assuming they're both strings), you can add binding terms to your #Around pointcut like this:
#Around("#annotation(ActivityLog) && args(actionComment,actedUponId)")
public Object logActivity(ProceedingJoinPoint pjp,
String actionComment, String actedUponId) throws Throwable {
// ... get other values from context, etc. ...
// ... write to log ...
pjp.proceed();
}
The args binding in a pointcut can be used in partially-specified mode, in case there are other arguments about that you aren't interested in, and since the aspect is itself a bean, it can be wired into everything else that is going on in the normal way.
Note that if you're mixing declarative transaction management on the same method calls, you've got to get the order of aspects correct. That's done in part by making the aspect bean also implement the Spring Ordered interface, and by controlling the precedence of transactions through the order attribute to <tx:annotation-driven/>. (If that's impossible, you'll be forced to do clever things with direct transaction handling; that's a vastly more painful option to get right…)
You will be getting a reference to the org.aspectj.lang.JoinPoint in your advice.You can get the name of target method being executed with toShortString().You can have a loop-up/property file with the method-name=comments entries.This comments can be populated into the POJO.actionComment.method-name can be set to POJO.actedUponId.
I hope the advice should run within the same transaction, if the data-access method is adviced and the service method uses #Transactional.