设置:部署在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);
}
}
};
}
答案 0 :(得分:0)
事实证明,问题是WeblogicJtaTransactionManager
。我的猜测是FOR UPDATE
导致JPA事务,但是在更新数据库中的对象时,使用了WeblogicJtaTransactionManager
,但未能找到正在进行的JTA事务。由于我们在Weblogic上进行部署,我们错误地认为我们必须使用WeblogicJtaTransactionManager
。
无论哪种方式,将TransactionManager
与JpaTransactionManager
交换(并在其上明确设置EntityManagerFactory
和DataSource
)基本上解决了所有问题。
@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;
}