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.