Org.hibernate.HibernateException: createQuery недействителен без активной транзакции @scheduled

Я использую запланированное задание для обновления моей базы данных следующим образом:

    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");
    }
}

Я сконфигурировал упорство следующим образом:

@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;
    }
}

Однако, когда я добираюсь до:

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

в приведенном выше HibernateUserRatingDAO Я получаю исключение:

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)

Как настроить включение запланированных задач в транзакции?

Редакция:

Вот код для 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

Включение транзакции Spring распечатывает следующий журнал:

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

Ответ 1

Как я уже упоминал, я использовал ваш код и создал небольшой образец, который работает для меня. Судя по используемым классам, я предположил, что вы используете Hibernate Generic DAO Framework. Это автономный образец, класс main() - Main. Запустив это, вы можете увидеть связанные с транзакцией сообщения DEBUG в журналах, которые показывают, когда транзакция инициирована и совершена. Вы можете сравнить мои настройки, версии банок, используемые с тем, что у вас есть, и посмотреть, что-то выделяется.

Кроме того, как я уже сказал, вы можете захотеть посмотреть в журналах, чтобы увидеть, используется ли правильное транзакционное поведение и сравнить его с журналами, которые создает мой образец.

Ответ 2

Что происходит в этом случае, так это то, что, поскольку вы используете userRatingManager() внутри конфигурации (где существует реальный метод планирования), прокси, созданный Spring для обработки управления транзакциями для UserRatingUpdate, не используется.

Я предлагаю вам сделать следующее:

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()
   }
}

Также измените код конфигурации менеджера транзакций на:

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

и удалите factoryBean.afterPropertiesSet(); из createBaseSessionFactory

Ответ 3

Я попытался реплицировать вашу проблему, поэтому интегрировал ее в свои примеры Hibernate на GitHub:

Вы можете запустить мой CompanySchedulerTest и посмотреть, как он работает, вот что я сделал для его запуска:

  • Я убедился, что контекст приложения знает о нашем планировщике

    <task:annotation-driven/>    
    
  • Планировщик определен в собственном 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();
        }
    }
    }
    
  • Мои конфигурации JPA/Hibernate находятся в applicationContext-test.xml, и они настроены для JPA в соответствии с показаниями среды Spring, поэтому вы можете также дважды проверить свои настройки Hibernate.