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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内部有事务和传播的Spring异步方法相关的知识,希望对你有一定的参考价值。

我必须创建一个异步方法来更新数据库和文件系统。在此方法中,某些操作必须是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注释将boolean fieldediting设置为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方法不会立即写入数据库

昨天我使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初始化程序包: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[] { "/" };
    }
}
答案

在使用@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);
    }
}
}

以上是关于内部有事务和传播的Spring异步方法的主要内容,如果未能解决你的问题,请参考以下文章

Spring事务的传播行为

Spring事务之传播机制

Spring事务传播机制

Spring事务传播行为

Spring事务传播行为中可能的坑点

Spring事务传播机制详解