1

I know this question has been asked before however none of the solutions have worked for me.

I am trying to hit a controller to populate an index. The issue arises when I try and search the database for updates.

Here is are the classes I am dealing with:

Configuration:

@Configuration
@EnableTransactionManagement
public class WebApplication implements WebApplicationContextInitializer, ApplicationContextAware {

    @Bean(name="dataSource")
    public DataSource getDataSource() throws IOException {
        InitialContext initialContext = new Context();
        return (DataSource) initialContext.lookup("java:comp/env/jdbc/myDataSource");
    }

    @Bean(name="sessionFactory")
    public SessionFactory getSessionFactory() throws IOException {
        LocalSessionFactoryBuilder sessionBuilder = new LocalSessionFactoryBuilder(getDataSource());
            sessionBuilder.scanPackages(PropertyUtil.getInstance().getPropertySplitTrimmed("hibernate", "packagesToScan"));
            sessionBuilder.addProperties(PropertyUtil.getInstance().getProperties("hibernate"));
            return sessionBuilder.buildSessionFactory();
    }

    @Bean(name="transactionManager")
    public HibernateTransactionManager transactionManager() throws IOException {
        return new HibernateTransactionManager(getSessionFactory());
    }
}

Controller:

@RestController
@Transactional
@RequestMapping("/persons")
public class IndexController {
   @Autowired
   PersonsDao personsDoa;

   private ExecutorService executorService = Executors.newFixedThreadPool(100);

  @RequestMapping(value="/index")
  public void populateIndex(@DefaultValue("") @RequestParam String name){
    ...
    ...
    List<Future<Persons>> holder = new ArrayList<>();

    for(Persons p : people){
       String name = p.name();
       Future<Person> f = this.executorService.submit(new Callable<Person>(){
          @Override
          public Person call() throws Exception {
            return personsDao.findByName(name);  // <-- Throws error here
          }
       });
       holder.add(f);  // process the array later once all threads are finished
    }
    ...
    ...
  }
}

UPDATE: I've updated my Controller according to some suggestions, however I am still receiving the same error

Controller:

@RestController
@Transactional
@RequestMapping("/persons")
public class IndexController {
   @Autowired
   PersonsDao personsDoa;

   private ExecutorService executorService = Executors.newFixedThreadPool(100);

  @RequestMapping(value="/index")
  public void populateIndex(@DefaultValue("") @RequestParam String name){
    ...
    ...
    List<Future<Persons>> holder = new ArrayList<>();
    TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(sessionFactory.getCurrentSession()));  //<-- THROWS ERROR HERE
    for(Persons p : people){
       String name = p.name();
       Future<Person> f = this.executorService.submit(new Callable<Person>(){
          SessionHolder holder = (SessionHolder)TransactionSynchronizationManager.getResources(sessionFactory);
          Session session = holder.getSession();
          @Override
          public Person call() throws Exception {
            Transaction t = session.getTransaction();
            t.begin();
            Persons p = personsDao.findByName(name);
            t.commit();
            session.flush();
            return p;
          }
       });
       holder.add(f);  // process the array later once all threads are finished
    }
    ...
    ...
  }
}
Dan
  • 979
  • 1
  • 8
  • 29
  • Did you tried putting `@Transactional` annotation above the method `findByName(String name)` in `PersonsDao` ? – The Coder Aug 27 '15 at 19:42
  • Yes. I tried removing the annotation and managing transactions manually and I still got that error. I am wondering if the initial setup is not correct. I try and get the currentSession (`sessionFactory.getCurrentSession()`) and the error is thrown – Dan Aug 27 '15 at 19:46

1 Answers1

0

Usualy the request thread use only one shared Session, this session is binded at the start of the request, and unbinded at the end of the request, but given you want to use it in another thread, we must :

1_ prevent the session from being closed from the Request Thread.

2_ bind this session to the new thread, to offer the TransactionManager to works with same Session.

First the current Session must not be closed, so if you are using the OpenInViewFilter, you need to add a method before calling the new Thread.

OpenEntityManagerInViewFilter.getCurrent().keepEmfOpen(request);            

Then inside the Thread you need to attach the current session.

public void attachThread() {// this must bind the session to this thread.
    OpenEntityManagerInViewFilter.getCurrent().registerEmfs(request, session);
}

private boolean registerEmf(String key, ServletRequest request, EntityManagerFactory emf){
        if (emf == null)
            return true;
        if (TransactionSynchronizationManager.hasResource(emf))         
            return true;        
        else {
            boolean isFirstRequest = true;
            WebAsyncManager asyncManager = null;
            if (request!=null){
                asyncManager = WebAsyncUtils.getAsyncManager(request);
                isFirstRequest = !(request instanceof HttpServletRequest) || !isAsyncDispatch((HttpServletRequest) request);
            }
            if (emf.isOpen())
            if (isFirstRequest || !applyEntityManagerBindingInterceptor(asyncManager, key)) {
                logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
                try {
                    EntityManager em = createEntityManager( emf );
                    EntityManagerHolder emHolder = new EntityManagerHolder( em );
                    TransactionSynchronizationManager.bindResource( emf, emHolder );
                    if (asyncManager!=null)
                        asyncManager.registerCallableInterceptor( key, new EntityManagerBindingCallableInterceptor( emf, emHolder ) );
                    return false;
                }
                catch (PersistenceException ex) {
                    throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
                }
            }
        }
        return true;
    }

in case of OpenSessionInViewFilter :

private void openHibernateSessionInView(){
  Session session=SessionFactoryUtils.getSession(sessionFactory,true);
  SessionHolder holder=new SessionHolder(session);
  if (!TransactionSynchronizationManager.hasResource(sessionFactory)) {
    TransactionSynchronizationManager.bindResource(sessionFactory,holder);
  }
}

private void closeHibernateSessionInView(){
  if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
    SessionHolder sessionHolder=(SessionHolder)TransactionSynchronizationManager.unbindResource(sessionFactory);
    if (sessionHolder.getTransaction() != null && !sessionHolder.getTransaction().wasRolledBack() && !sessionHolder.getTransaction().wasCommitted()) {
      sessionHolder.getTransaction().commit();
    }
    SessionFactoryUtils.closeSession(sessionHolder.getSession());
  }
}
Nassim MOUALEK
  • 4,702
  • 4
  • 25
  • 44
  • I dont believe we are using the `OpenInViewFilter`. Also, I am not using the `JpaTransactionManager`, but opted to use the `HibernateTransactionManager` – Dan Aug 27 '15 at 19:37
  • yes but, you should got the idea you must bind the current session to the Thread, and prevent the Session to be closed when the request is end from the parent thread – Nassim MOUALEK Aug 27 '15 at 19:43
  • Tried passing the session in and I still get that error – Dan Aug 27 '15 at 19:47
  • update your question with your new try, i will try to help you – Nassim MOUALEK Aug 27 '15 at 19:49
  • It's not an update since I just tried it for testing purposes. Basically I took the current session and tried to get the transaction from it. The error was thrown however when as asked for the `currentSession`, which leads me to believe something in not correct with the initial configuration – Dan Aug 27 '15 at 20:04
  • please take a look at this http://stackoverflow.com/questions/32200474/could-not-initialize-proxy-no-session-exception/32225005#32225005 – Nassim MOUALEK Aug 27 '15 at 20:36
  • How would I integrate the `TransactionSynchronizationManager` into my existing code then? Normally, we would handle this type of situation with AOP, but unfortunately we do not have that available at the moment. – Dan Aug 27 '15 at 21:53
  • I've updated my code according to the link you provided. I am still getting the error however when I try and bind the `Session` – Dan Aug 28 '15 at 15:21
  • first you must use Session session=SessionFactoryUtils.getSession(sessionFactory,true); the you still didn't understand my answer – Nassim MOUALEK Aug 28 '15 at 15:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/88222/discussion-between-dan-and-nassim-moualek). – Dan Aug 28 '15 at 15:47