使用Spring的@Transaction管理进行SELECT FOR UPDATE查询会在后续更新中创建死锁

时间:2015-09-21 12:53:26

标签: java spring oracle transactions weblogic

设置:部署在Weblogic 12c上的Spring应用程序,使用JNDI查找来获取Oracle数据库的数据源。

我们有多项服务,将定期轮询数据库以获取新工作。为了防止两个服务选择相同的作业,我们在CrudRepository中使用本机SELECT FOR UPDATE查询。然后,应用程序将使用PROCESSING方法获取生成的作业并将其更新为WAITING而不是CrusRepository.save()

问题在于我似乎无法让save()FOR UPDATE交易中工作(至少这是我目前关于出错的工作理论),并且结果整个轮询冻结,直到发生默认的10分钟超时。我已经尝试将@Transactional(带有各种传播标志)基本上放在任何地方,但我无法使其工作(@EnableTransactionManagement已激活并正常工作)。

显然,我必须缺少一些基本知识。这甚至是一种可能的设置吗?不幸的是,仅使用@Transactional与非本机CrudRepository SELECT查询是不可能的,因为它显然首先使SELECT查看该行是否被锁定,并且只有然后创建一个新的SELECT来锁定它。同时,另一项服务可以很好地完成相同的工作,这就是我们需要立即锁定的原因。

与@M相关的更新。 Deinum的评论:我或许还应该提到它是一个设置,其中进行轮询的中心组件是所有其他服务使用的库(因此该库具有@SpringBootApplication,as as每个服务使用它,因此肯定存在双组件扫描)。此外,该服务有两个单独的类用于轮询,具体取决于服务类型,以及在AbstractTransactionHelper类中共享的许多公共代码。下面我为了简洁起见汇总了一些代码。

图书馆的主要班级:

@SpringBootApplication
@EnableTransactionManagement
@EnableJpaRepositories
public class JobsMain {

public static void initializeJobsMain(){
    PersistenceProviderResolverHolder.setPersistenceProviderResolver(new PersistenceProviderResolver() {

        @Override
        public List<PersistenceProvider> getPersistenceProviders() {
            return Collections.singletonList(new HibernatePersistenceProvider());
        }

        @Override
        public void clearCachedProviders() {
            //Not quite sure what this should do...
        }
    });
}

@Bean
public JtaTransactionManager transactionManager(){
    return new WebLogicJtaTransactionManager();
}

public DataSource dataSource(){
    final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
    dsLookup.setResourceRef(true);
    DataSource dataSource = dsLookup.getDataSource("Jobs");
    return dataSource;
}

}

存储库(我们只返回一个只有一个作业的集合,因为我们在返回单个对象时遇到了一些其他问题):

public interface JobRepository extends CrudRepository<Job, Integer> {

@Query(value = "SELECT * FROM JOB WHERE JOB.ID IN "
                    + "(SELECT ID FROM "
                        + "(SELECT * FROM JOB WHERE "
                            + "JOB.STATUS = :status1 OR "
                            + "JOB.STATUS = :status2 "
                        + "ORDER BY JOB.PRIORITY ASC, JOB.CREATED ASC) "
                    + "WHERE ROWNUM <= 1) "
                + "FOR UPDATE", nativeQuery = true)
public Set<Job> getNextJob(@Param("status1") String status1, @Param("status2") String status2);

交易处理类:

@Service
public class JobManagerTransactionHelper extends AbstractTransactionHelper{

@Transactional
@Override
public QdbJob getNextJobToProcess(){
        Set<Job> jobs = null;
        try {
            jobs = jobRepo.getNextJob(Status.DONE.name(), Status.FAILED.name());
        } catch (Exception ex) {
            logger.error(ex);
        }
    return extractSingleJobFromSet(jobs);
}

更新2:更多代码。

AbstractTransactionHelper:

@Service
public abstract class AbstractTransactionHelper {

@Autowired
QdbJobRepository jobRepo;

@Autowired
ArchivedJobRepository archive;

protected Job extractSingleJobFromSet(Set<Job> jobs){
    Job job = null;
    if(jobs != null && !jobs.isEmpty()){
        for(job job : jobs){
            if(this instanceof JobManagerTransactionHelper){
                updateJob(job);
            }
            job = job;
        }
    }
    return job;
}

protected void updateJob(Job job){
    updateJob(job, Status.PROCESSING, null);
}

protected void updateJob(Job job, Status status, String serviceMessage){
    if(job != null){
        if(status != null){
            job.setStatus(status);
        }
        if(serviceMessage != null){
            job.setServiceMessage(serviceMessage);   
        }
        saveJob(job);
    }
}

protected void saveJob(Job job){
    jobRepo.save(job);
    archive.save(Job.convertJobToArchivedJob(job));
}

更新4:线程。 newJob()由使用该库的每个服务实现。

@Service
public class JobManager{

@Autowired
private JobManagerTransactionHelper transactionHelper;

@Autowired
JobListener jobListener;

@Autowired
Config config;

protected final AtomicInteger atomicThreadCounter = new AtomicInteger(0);

protected boolean keepPolling;    
protected Future<?> futurePoller;
protected ScheduledExecutorService pollService;
protected ThreadPoolExecutor threadPool;

public boolean start(){
    if(!keepPolling){
        ThreadFactory pollServiceThreadFactory = new ThreadFactoryBuilder()
            .setNamePrefix(config.getService() + "ScheduledPollingPool-Thread").build();
        ThreadFactory threadPoolThreadFactory = new ThreadFactoryBuilder()
                            .setNamePrefix(config.getService() + "ThreadPool-Thread").build();
        keepPolling = true;
        pollService = Executors.newSingleThreadScheduledExecutor(pollServiceThreadFactory);
        threadPool = (ThreadPoolExecutor)Executors.newFixedThreadPool(getConfig().getThreadPoolSize(), threadPoolThreadFactory);
        futurePoller = pollService.scheduleWithFixedDelay(getPollTask(), 0, getConfig().getPollingFrequency(), TimeUnit.MILLISECONDS);
        return true;
    }else{
        return false;
    }
}

protected Runnable getPollTask() {
    return new Runnable(){
        public void run(){
            try{
                while(atomicThreadCounter.get() < threadPool.getMaximumPoolSize() && 
                        threadPool.getActiveCount() < threadPool.getMaximumPoolSize() && 
                        keepPolling == true){
                    Job job = transactionHelper.getNextJobToProcess();
                    if(job != null){
                        threadPool.submit(getJobHandler(job));
                        atomicThreadCounter.incrementAndGet();//threadPool.getActiveCount() isn't updated fast enough the first loop
                    }else{
                        break;
                    }
                }
            }catch(Exception e){
                logger.error(e);
            }
        }
    };
}

protected Runnable getJobHandler(final Job job){
    return new Runnable(){
        public void run(){
            try{
                atomicThreadCounter.decrementAndGet();
                jobListener.newJob(job);
            }catch(Exception e){
                logger.error(e);
            }
        }
    };
}

1 个答案:

答案 0 :(得分:0)

事实证明,问题是WeblogicJtaTransactionManager。我的猜测是FOR UPDATE导致JPA事务,但是在更新数据库中的对象时,使用了WeblogicJtaTransactionManager,但未能找到正在进行的JTA事务。由于我们在Weblogic上进行部署,我们错误地认为我们必须使用WeblogicJtaTransactionManager

无论哪种方式,将TransactionManagerJpaTransactionManager交换(并在其上明确设置EntityManagerFactoryDataSource)基本上解决了所有问题。

@Bean
public PlatformTransactionManager transactionManager() {
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory().getObject());
    jpaTransactionManager.setDataSource(dataSource());
    jpaTransactionManager.setJpaDialect(new HibernateJpaDialect());
    return jpaTransactionManager;
}

假设您还添加了一个EntityManagerFactoryBean,如果您想在同一个项目中使用多个数据源(我们正在做,但不在单个事务中,那么就不需要JTA)。

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();

    LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();

    factoryBean.setDataSource(dataSource());
    factoryBean.setJpaVendorAdapter(vendorAdapter);
    factoryBean.setPackagesToScan("my.model");

    return factoryBean;
}