0
votes

I have a service layer class having annotation @Controller and in service call I am spawning in thread and that is updating something in database. I have used @transaction annotation in thread's method. But I am getting hibernate exception "No session found". Do I need to add any annotation to Thread?

org.hibernate.HibernateException: No Session found for current thread at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97) at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:978) at com.mediaiq.commons.persistence.BaseRepository.getCurrentSession(BaseRepository.java:30) at com.mediaiq.cms.persistence.AgencyRepository.getById(AgencyRepository.java:20) at com.mediaiq.mail.client.AgencyLookupReportByDayClient.emailAgencyLookupConfirmation(AgencyLookupReportByDayClient.java:84) at com.mediaiq.mail.client.AgencyLookupReportByDayClient.sendemailreport(AgencyLookupReportByDayClient.java:67) at com.mediaiq.mail.client.BaseMailClient.run(BaseMailClient.java:222) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) at java.util.concurrent.FutureTask.run(FutureTask.java:166) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:722)

@Repository
public class BaseMailClient implements Runnable {

    public BaseMailClient() {

    }

    public BaseMailClient(PlacementsRepository placementsRepository, SessionFactory sessionFactory, String sessionID) {
    this.placementsRepository = placementsRepository;
    this.sessionFactory = sessionFactory;
    this.sessionID = sessionID;
    }

    public BaseMailClient(AgencyRepository agencyRepository, SessionFactory sessionFactory, String sessionID) {

    this.agencyRepository = agencyRepository;
    this.sessionFactory = sessionFactory;
    this.sessionID = sessionID;
    }

    private String      sessionID        = null;

    @Autowired
    private SessionFactory  sessionFactory;


    @Autowired
    private PlacementsRepository  placementsRepository;
    @Autowired
    private AgencyRepository      agencyRepository;



    final static Logger    logger          =LoggerFactory.getLogger(BaseMailClient.class);



    @Override
    public void run()
    {

        sendemailreport();

    }

@Transactional
    public void sendemailreport()
    {
        checkSessionID();
        try {
        emailAgencyLookupConfirmation();
        emailAgencyLookup();
        }
        catch (IOException | FailedToCreateTempFile e) {
        logger.info("Failed To mail due to exception :" + e.getMessage());
        }
        catch (Throwable e) {
        logger.info("Failed To mail due to exception :" + e.getMessage());
        }
    }

}

Service Class:

@Transactional
@Controller
@RequestMapping(URLEndPoints.EMAIL)
public class SendMailService {

    @Autowired
    PlacementsRepository       placementsRepository;
    @Autowired
    AgencyRepository       agencyRepository;
    /**
     * 
     */
    @Autowired
    private SessionFactory   sessionFactory;

    @Autowired
    private ThreadPoolTaskExecutor mailJobExecutor;

    final static Logger     logger = LoggerFactory.getLogger(SendMailService.class);
 @RequestMapping(method = RequestMethod.POST, value = URLEndPoints.EMAIL_AGENCY_LOOKUP_BY_DAY, produces = "application/json")
    @ResponseBody
    public String emailAgencyLookupByDay(ServletRequest request,@PathVariable("agencyID") Integer agencyID,
              @PathVariable("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date startDate,
              @PathVariable("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date endDate )
    {
        logger.debug("POST:SendEmail:AgencyLookup");
        String sessionId = getSessionIDFromCookie(request);
        BaseClient mailServiceClient = new BaseClient(getAgencyRepository(), getSessionFactory(), sessionId);
        logger.debug("Getting Executor Instance");
        mailJobExecutor.submit(mailServiceClient);
        logger.debug("Submitted to Executor");
        return "SUCCESS";
    }
}
2
If you manually create a thread (or are manually using an executor service) consider the "Async" annotation. The method that runs asynchronously could call another service and the method on that service can have the "Transactional" annotation. I also think that if you are using cglib for the aspects, you could annotate the same method with both "Async" and "Transactional" - Luciano
plus tell us your spring version. @Async and @Transactional had a bug before Spring-3.0.3. See SPR-7147 - Markus Malkusch
I am using 3.2 version and I have added client and service class - Navneet
I don't think Spring can work defined in the way you use it. Consider using @Async. - Markus Malkusch

2 Answers

1
votes

There are a few things wrong here.

First, you are creating your BaseClient object yourself, so Spring cannot add the @Transactional behavior.

BaseClient mailServiceClient = new BaseClient(getAgencyRepository(), getSessionFactory(), sessionId);

Instead, you should let Spring create a bean of that type for you. Use prototype scope.

Second, you need to carefully read this entry in the Spring documentation about Spring's proxying mechanism. Unfortunately, you cannot expect any transactional behavior when invoking a @Transactional method from a non-transactional method within the same class, which is what you do here

@Override
public void run()
{
    sendemailreport();
}

@Transactional
public void sendemailreport()
{

sendemailreport is invoked on this reference, which is the object itself, not the proxy. It will therefore not have any transactional behavior.

Consider looking into the @Async annotation alongside asynchronous execution.

0
votes

Threads which are created outside Spring will not have a hibernate session attached. You will need to attach a new hibernate session to the thread manually;

Session session = SessionFactoryUtils.openSession(sessionFactory);
try
{
    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
catch(Exception e)
{
    Logger.debug("Session already bound");
}

(You will need to inject the sessionFactory).

Make sure the session is unbound before the thread finishes;

public static void closeSession(Session se)
{
    try
    {
        try
        {
            TransactionSynchronizationManager.unbindResource(sessionFactory);
        }
        finally
        {
            if (se != null && se.isOpen() && se.isConnected())
            {
                SessionFactoryUtils.closeSession(se);
            }
        }
    }
    catch (HibernateException e)
    {
        Logger.fatal("Could not close session", e);
    }
}