Question

I am using scheduled task to update my database like this:

    public interface UserRatingManager {
        public void updateAllUsers();
    }

    @Service
    public class DefaultUserRatingManager implements UserRatingManager {

        @Autowired
        UserRatingDAO userRatingDAO;

            @Override
        @Transactional("txName")
        public void updateAllUsers() {
            List<String> userIds = userRatingDAO.getAllUserIds();
            for (String userId : userIds) {
                updateUserRating(userId);
            }
        }
    }

public interface UserRatingDAO extends GenericDAO<UserRating, String> {
    public void deleteAll();
    public List<String> getAllUserIds();
}

@Repository
public class HibernateUserRatingDAO extends BaseDAO<UserRating, String> implements UserRatingDAO {

    @Override
    public List<String> getAllUserIds() {
        List<String> result = new ArrayList<String>();
        Query q1 = getSession().createQuery("Select userId from UserRating");
    }
}

I configured the persistence like this:

@Configuration
@ComponentScan({ "com.estartup" })
@PropertySource("classpath:jdbc.properties")
@EnableTransactionManagement
@EnableScheduling
public class PersistenceConfig {

    @Autowired
    Environment env;

    @Scheduled(fixedRate = 5000)
    public void run() {
        userRatingManager().updateAllUsers();
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource(env.getProperty("connection.url"), env.getProperty("connection.username"), env.getProperty("connection.password"));
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        return driverManagerDataSource;
    }

    public PersistenceConfig() {
        super();
    }


    @Bean
    public UserRatingUpdate userRatingUpdate() {
        return new UserRatingUpdate();
    }

    @Bean
    public UserRatingManager userRatingManager() {
        return new DefaultUserRatingManager();
    }

    @Bean
    public LocalSessionFactoryBean runnableSessionFactory() {
        LocalSessionFactoryBean factoryBean = null;
        try {
            factoryBean = createBaseSessionFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factoryBean;
    }


    private LocalSessionFactoryBean createBaseSessionFactory() throws IOException {
        LocalSessionFactoryBean factoryBean;
        factoryBean = new LocalSessionFactoryBean();
        Properties pp = new Properties();
        pp.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
        pp.setProperty("hibernate.max_fetch_depth", "3");
        pp.setProperty("hibernate.show_sql", "false");
        factoryBean.setDataSource(dataSource());
        factoryBean.setPackagesToScan(new String[] { "com.estartup.*" });
        factoryBean.setHibernateProperties(pp);
        factoryBean.afterPropertiesSet();
        return factoryBean;
    }

    @Bean(name = "txName")
    public HibernateTransactionManager runnableTransactionManager() {
        HibernateTransactionManager htm = new HibernateTransactionManager(runnableSessionFactory().getObject());
        return htm;
    }
}

However, when I get to:

Query q1 = getSession().createQuery("Select userId from UserRating"); 

in the above HibernateUserRatingDAO I get an exception:

org.hibernate.HibernateException: createQuery is not valid without active transaction
    at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:352)
    at com.sun.proxy.$Proxy63.createQuery(Unknown Source)
    at com.estartup.dao.impl.HibernateUserRatingDAO.getAllUserIds(HibernateUserRatingDAO.java:36)

How can I configure to include my scheduled tasks in transactions ?

EDITED:

Here is the code for BaseDAO

@Repository
public class BaseDAO<T, ID extends Serializable> extends GenericDAOImpl<T, ID> {

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

    @Autowired
    @Override
    public void setSessionFactory(SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
    }

    public void setTopAndForUpdate(int top, Query query){
        query.setLockOptions(LockOptions.UPGRADE);
        query.setFirstResult(0);
        query.setMaxResults(top);
    }

EDITED

Enabling Spring transaction prints the following log:

DEBUG [pool-1-thread-1] org.springframework.transaction.annotation.AnnotationTransactionAttributeSource - Adding transactional method 'updateAllUsers' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'txName'
Was it helpful?

Solution 2

As I already mentioned, I used your code and created a small sample that works for me. Judging by the classes used, I assumed you are using Hibernate Generic DAO Framework. It's a standalone sample, the main() class is Main. Running it you can see the transactional related DEBUG messages in logs that show when a transaction is initiated and committed. You can compare my settings, jars versions used with what you have and see if anything stands out.

Also, as I already suggested you might want to look in the logs to see if proper transactional behavior is being used and compare that with the logs my sample creates.

OTHER TIPS

What is happening in this case is that since you are using userRatingManager() inside the configuration (where the actual scheduled method exists), the proxy that Spring creates to handle the transaction management for UserRatingUpdate is not being used.

I propose you do the following:

public interface WhateverService {

   void executeScheduled();
}

@Service
public class WhateverServiceImpl {

   private final UserRatingManager userRatingManager;

   @Autowired
   public WhateverServiceImpl(UserRatingManager userRatingManager) {
      this.userRatingManager = userRatingManager;
   }

   @Scheduled(fixedRate = 5000)
   public void executeScheduled() {
      userRatingManager.updateAllUsers()
   }
}

Also change your transaction manager configuration code to:

    @Bean(name = "txName")
    @Autowired
    public HibernateTransactionManager runnableTransactionManager(SessionFactory sessionFactory) {
        HibernateTransactionManager htm = new HibernateTransactionManager();
        htm.setSessionFactory(sessionFactory);
        return htm;
    }

and remove factoryBean.afterPropertiesSet(); from createBaseSessionFactory

I tried to replicate your problem so I integrated it in my Hibernate examples on GitHub:

You can run my CompanySchedulerTest and see it's working so this is what I did to run it:

  1. I made sure the application context is aware of our scheduler

    <task:annotation-driven/>    
    
  2. The scheduler is defined in its own bean:

    @Service
    public class CompanyScheduler implements DisposableBean {
    
    private static final Logger LOG = LoggerFactory.getLogger(CompanyScheduler.class);
    
    @Autowired
    private CompanyManager companyManager;
    
    private volatile boolean enabled = true;
    
    @Override
    public void destroy() throws Exception {
        enabled = false;
    }
    
    @Scheduled(fixedRate = 100)
    public void run() {
        if (enabled) {
            LOG.info("Run scheduler");
            companyManager.updateAllUsers();
        }
    }
    }
    
  3. My JPA/Hibernate configs are in applicationContext-test.xml and they are configured for JPA according to the Spring framework indications, so you might want to double check your Hibernate settings as well.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top