内部有事务和传播的Spring异步方法

时间:2017-12-20 08:54:32

标签: java spring hibernate annotations spring-transactions

我必须创建一个异步方法来更新数据库和文件系统。在此方法中,某些操作必须是Transactional而其他操作不是。 所以从我的服务

@Service
public class FleetAndCarServicesImpl implements FleetAndCarServices {

    @Autowired
    private DatabaseFleetsAndCarsServices databaseFleetsAndCarsServices;
    @Autowired
    private DatabaseAcquisitionServices databaseAcquisitionServices;
    @Autowired
    private DatabaseAdministrationServices databaseAdministrationServices;
    @Autowired
    private MakeName makeName;
    @Autowired
    private MakeAcquisitionPath makeAcquisitionPath;
    @Autowired
    private Environment env;
    private static String PROPERTY_NAME_FILESYSTEM_BASEPATH = "filesystem.basepath";

   //OTHER CODES 

    @Override
    @SetEditingFleet
    public void modifyFleet(User user, FleetForm fleetForm) throws Exception{
        databaseFleetsAndCarsServices.modifyFleet(user, fleetForm);
    }

SetEditingFleet注释将布尔字段editing设置为true,仅当此布尔值为false时才允许某些操作。 这是设置和取消设置此字段的代码:

@Before("@annotation(SetEditingFleet) && args(user, fleetForm)")
public void setEditingFleet(User user, FleetForm fleetForm) throws QueryException {
    utils.setEditingFleet(user, fleetForm);  
}

@After("@annotation(UnSetEditingFleet) && args(user, fleetForm)")
public void unSetEditingFleet(User user, FleetForm fleetForm) throws QueryException {
    utils.unSetEditingFleet(fleetForm.getIdFleet()); 
}

Utils类:

@Service
public class Utils {

    @Autowired
    private FleetServices fleetServices;
    @Autowired
    private CarServices carServices;

    @Transactional(rollbackFor=Exception.class)
    public void unSetEditingFleet(Integer idFleet) throws QueryException {
        try {
            fleetServices.setEditingFleet(idFleet, false);  
            for(Car car : carServices.findByFleetIdFleet(idFleet)){
                carServices.setEditingCar(car.getIdCar(), false);   //Unset cars associated with the fleet 
            }    
        }catch(Exception e){
            throw new QueryException(e);
        }
    }

    @Transactional(rollbackFor=Exception.class)
    public void setEditingFleet(User user, FleetForm fleetForm) throws QueryException {
        try{
            fleetServices.setEditingFleet(fleetForm.getIdFleet(), true);
            for(Car car : carServices.findByFleetIdFleet(fleetForm.getIdFleet())){
                carServices.setEditingCar(car.getIdCar(), true);     //Set cars associated with the fleet
            }  
        }catch(Exception e){
            throw new QueryException(e);
        }
    }
}

从异步方法和unSetEditingFleet调用class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler方法,在异步方法结束时或在出现异常的情况下将布尔字段editing设置为false。 异步方法(我删除了所有无用的代码):

@Override
@Async
@Transactional(rollbackFor=Exception.class)
public void modifyFleet(User currentUser, FleetForm fleetForm) throws Exception {
    LOG.info("Editing fleet with id: "+ fleetForm.getIdFleet());
    //Keep the progress status on DB
    fleetServices.setEditingProgress(fleetForm.getIdFleet(), "Start editing fleet");

        utils.unSetEditingFleet(oldFleet.getIdFleet());
    }catch(Exception e){
        ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
        LOG.error("Threw exception in DatabaseFleetsAndCarsServicesImpl::modifyFleet: " + errorResponse.getStacktrace());
        //Keep the progress status on DB
        fleetServices.setEditingProgress(oldFleet.getIdFleet(), "Sorry an error occured during the procedure, wait until restore is ended!");
        //Restore the file system procedure
        restoreProcedure(oldFleet.getIdFleet(), fleetFile, backupFolderFile);       
        //Keep the progress status on DB
        fleetServices.setEditingProgress(oldFleet.getIdFleet(), "");

        //Even with this exception set the fleet as not in editing
        throw new EditingException("fleet has been restored!");
    }finally{
        LOG.info("Editing end for fleet with id: "+ fleetForm.getIdFleet());
    }
}

fleetServices.setEditingProgress()必须立即写入数据库,因为该值必须显示给用户。 为此,我将此方法放在另一个类中:

@Service
@Transactional
public class FleetServicesImpl implements FleetServices{

    @Resource
    private FleetRepository fleetRepository;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW) //necessary to set immediately the text into the database inside a transactional method. This annotation create a new transaction
    public void setEditingProgress(Integer idFleet, String editingProgress) {
        fleetRepository.setEditingProgress(idFleet, editingProgress);   
    }       
}

我有两个问题:

  1. setEditingFleet方法在发生错误时无法回滚
  2. setEditingProgress方法不会立即写入数据库
  3. 昨天我将setEditingProgress设为正确但更改了@Transactional注释我犯了一些错误,现在它甚至无法正常工作。
    你知道我错了吗?

    配置类:

    @EnableWebSecurity 
    @EnableAspectJAutoProxy
    @EnableAsync
    @EnableWebMvc
    @Configuration
    @PropertySource(value = { "classpath:application.properties" })
    @ComponentScan({ "com.*" })
    @EnableTransactionManagement
    @Import({ SecurityConfig.class, SpringMvcInitializer.class})
    @EnableJpaRepositories("com.repository")
    public class AppConfig extends WebMvcConfigurerAdapter implements AsyncConfigurer{
        @Autowired
        private Environment env;
        @Autowired
        private MyAsyncUncaughtExceptionHandler myAsyncUncaughtExceptionHandler;
    
        private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
        private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
        private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
        private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
    
        private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
        //  private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
        private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
        private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
    
        /**
         * This method allows use of . with pathparam into web services, otherwise it truncate after dot.
         * @param configurer
         */
         public void configurePathMatch(PathMatchConfigurer configurer) {
            configurer.setUseSuffixPatternMatch(false);
          }
    
        /**
         * This and the next methods are used to avoid exception while jackson mapping the entity, so fields are setted with null value
         * unless use Hibernate.initialize
         * @return
         */
        public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
            MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
    
            ObjectMapper mapper = new ObjectMapper();
            //Registering Hibernate4Module to support lazy objects
            mapper.registerModule(new Hibernate4Module());
    
            messageConverter.setObjectMapper(mapper);
            return messageConverter;
    
        }
        /**
         * Used for spring security
         * @return
         */
        @Bean
        public SpringSecurityDialect springSecurityDialect() {
            SpringSecurityDialect dialect = new SpringSecurityDialect();
            return dialect;
        }
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            //Here we add our custom-configured HttpMessageConverter
            converters.add(jacksonMessageConverter());
            super.configureMessageConverters(converters);
        }
    
        private Properties getHibernateProperties() {
            Properties properties = new Properties();
            properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
            //      properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
            properties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
            properties.put("hibernate.enable_lazy_load_no_trans",true);
            return properties;
        }
    
        @Bean(name = "dataSource")
        public BasicDataSource dataSource() {
            BasicDataSource ds = new BasicDataSource();
            ds.setValidationQuery("SELECT 1");
            ds.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
            ds.setUrl(env.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
            ds.setUsername(env.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
            ds.setPassword(env.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
            return ds;
        }
    
        @Bean
        public ServletContextTemplateResolver TemplateResolver(){
            ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
            resolver.setPrefix("/WEB-INF/templates/pages/");
            resolver.setSuffix(".html");
            resolver.setTemplateMode("LEGACYHTML5");
            resolver.setCacheable(false);
            return resolver;
            /*ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
            resolver.setPrefix("/WEB-INF/pages/");
            resolver.setSuffix(".html");
            resolver.setTemplateMode("HTML5");
            return resolver;*/
        }
    
        @Bean
        public SpringTemplateEngine templateEngine(){
            SpringTemplateEngine templateEngine = new SpringTemplateEngine();
            templateEngine.setTemplateResolver(TemplateResolver());
            templateEngine.addDialect(springSecurityDialect());
            return templateEngine;
        }
    
    
        @Bean
        public ThymeleafViewResolver viewResolver() {
            ThymeleafViewResolver resolver = new ThymeleafViewResolver();
            resolver.setTemplateEngine(templateEngine());
            resolver.setOrder(1);
            resolver.setViewNames(new String[]{"*", "js/*", "template/*"});
            return resolver;
        }
    
        /**
         * Register multipartResolver for file upload
         * @return
         */
        @Bean
        public CommonsMultipartResolver multipartResolver() {
            CommonsMultipartResolver resolver=new CommonsMultipartResolver();
            resolver.setDefaultEncoding("utf-8");
            return resolver;    
        }
    
        /**
         * Allow use of bootstrap
         */
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/static/**")
            .addResourceLocations("/static/");
        }
    
        /**
         * Allow use of JPA
         */
        @Bean
        public JpaTransactionManager transactionManager() {
            JpaTransactionManager transactionManager = new JpaTransactionManager();
            transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
            return transactionManager;
        }
        @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
            LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
            entityManagerFactoryBean.setDataSource(dataSource());
            entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
            entityManagerFactoryBean.setPackagesToScan(env.
                    getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
            entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
            return entityManagerFactoryBean;
        }
    
        @Override
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10);
            executor.setMaxPoolSize(100);
            executor.setQueueCapacity(100);
            executor.initialize();
            return executor;
        }
    
        public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
            return myAsyncUncaughtExceptionHandler;
        }
    
        //Concurrent Session Control. Necessary for maximumSessions(1) into securityConfig
        @Bean
        public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
            return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
        }
    }
    

    MVC初始化程序: package com.config.core;

    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[] { AppConfig.class };
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return null;
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };
        }
    }
    

1 个答案:

答案 0 :(得分:-3)

在使用@Transactional之前使用注释@Test。试试这个:

@Service
public class Utils {

@Autowired
private FleetServices fleetServices;
@Autowired
private CarServices carServices;

public static long stream(InputStream input, OutputStream output) throws IOException {
    try (
            ReadableByteChannel inputChannel = Channels.newChannel(input);
            WritableByteChannel outputChannel = Channels.newChannel(output);
            ) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(10240);
        long size = 0;

        while (inputChannel.read(buffer) != -1) {
            buffer.flip();
            size += outputChannel.write(buffer);
            buffer.clear();
        }
        return size;
    }
}

@Test
@Transactional
public void unSetEditingFleet(Integer idFleet) throws QueryException {
    try {
        fleetServices.setEditingFleet(idFleet, false);  
        for(Car car : carServices.findByFleetIdFleet(idFleet)){
            carServices.setEditingCar(car.getIdCar(), false);   //Unset cars associated with the fleet 
        }    
    }catch(Exception e){
        throw new QueryException(e);
    }
}

@Test
@Transactional
public void setEditingFleet(User user, FleetForm fleetForm) throws QueryException {
    try{
        fleetServices.setEditingFleet(fleetForm.getIdFleet(), true);
        for(Car car : carServices.findByFleetIdFleet(fleetForm.getIdFleet())){
            carServices.setEditingCar(car.getIdCar(), true);     //Set cars associated with the fleet
            throw new Exception("fleet has been restored Exception");
        }  
    }catch(Exception e){
        throw new QueryException(e);
    }
}
}