Spring @Transactional не работает

Ранее у меня был пост по этой проблеме, которая была решена. Однако после перестроения проекта с автоматическими проводными компонентами и меньшей конфигурацией XML я обнаружил, что возвращаюсь к этой проблеме. Я следовал тому, как мой предыдущий проект реализовал это, но он не работает. Может ли кто-нибудь помочь мне с тем, почему или что я должен изменить, чтобы заставить его работать?

Я намеренно использую имя несуществующей таблицы в методе вставки сведений о пользователе, чтобы намеренно вызвать исключение. Однако операторы для вставки пользователя и вставки ролей пользователя не откатываются. Пожалуйста помоги.


Мой текущий дизайн для регистрации выглядит так.

Часть servlet.xml:

<context:component-scan base-package="com.doyleisgod.golfer.controllers"/>
<context:component-scan base-package="com.doyleisgod.golfer.dao"/>
<context:component-scan base-package="com.doyleisgod.golfer.services"/>
<context:component-scan base-package="com.doyleisgod.golfer.validators"/>


Часть контекста приложения:

<context:annotation-config />
<tx:annotation-driven />    

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>


Регистрация контроллера:

package com.doyleisgod.golfer.controllers;

import javax.validation.Valid;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.doyleisgod.golfer.formdata.RegistrationForm;
import com.doyleisgod.golfer.services.IRegistrationService;
import com.doyleisgod.golfer.validators.RegistrationFormValidator;

/**
 * Description: Registration controller provides and processes the registration form.
 * @author Chris Doyle
*/
@Controller
@RequestMapping("/registration.htm")
public class RegistrationController {
    protected final Log logger = LogFactory.getLog(getClass());
    @Autowired private IRegistrationService iRegistrationService;
    @Autowired private RegistrationFormValidator registrationFormValidator;

    // sets a customer validator for the registration form
    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(registrationFormValidator);
    }

    // Description: Method called by a get request to the registration controller. Returns the
    @RequestMapping(method=RequestMethod.GET)
    public String registration (Model model){
        model.addAttribute(new RegistrationForm());
        return "registration";
    }

     // Description: Method called by a post request to the registration controller. Method calls validation on the registration form using custom validator and returning 
     // any errors back to the user. 
    @RequestMapping(method=RequestMethod.POST)
    public String processRegistration (@Valid RegistrationForm registrationForm, BindingResult bindingResult, Model model){
        logger.info("Received the following registration form details");
        logger.info(registrationForm.toString());

        if (bindingResult.hasErrors()) {
            logger.warn("Registration Validation Failed");
            model.addAttribute("validationError", "Please correct the fields marked with errors");
            return "registration";
        }

        try {
            iRegistrationService.registerUser(registrationForm);
        } catch (Exception e) {
            logger.error("An Exception has occured processing the registration form");
            model.addAttribute("exceptionError", "An exception has occured, please try again.");
            e.printStackTrace();
            return "registration";
        }

        return "redirect:login.htm?registration=sucessful";
    }
}


Регистрационная услуга:

package com.doyleisgod.golfer.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.doyleisgod.golfer.dao.IRegistrationDAO;
import com.doyleisgod.golfer.formdata.RegistrationForm;

@Service("IRegistrationService")
public class RegistrationService implements IRegistrationService {
    @Autowired private IRegistrationDAO iRegistrationDAO;
    private final boolean enabled = true;
    private final String roles = "ROLE_USER";


    @Override
    @Transactional (rollbackFor = Exception.class)
    public void registerUser(RegistrationForm registrationForm) throws Exception {
        System.out.println("inside the registerUser method. is wrapped in transaction: "+TransactionSynchronizationManager.isActualTransactionActive());

        String username = registrationForm.getUsername();
        String password = registrationForm.getPassword();
        String firstname = registrationForm.getFirstname();
        String lastname = registrationForm.getLastname();
        String email = registrationForm.getEmail();
        int handicap = Integer.parseInt(registrationForm.getHandicap());
        String encryptedPassword = ((new ShaPasswordEncoder()).encodePassword(password, username));

        iRegistrationDAO.insertUser(username, encryptedPassword, enabled);
        iRegistrationDAO.insertRoles(username, roles);
        iRegistrationDAO.insertUserDetails(username, firstname, lastname, email, handicap);
    }

    @Override
    public boolean checkUser(String username) {
        return iRegistrationDAO.checkUserName(username);
    }
}


Регистрация DAO:

package com.doyleisgod.golfer.dao;

import javax.annotation.Resource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionSynchronizationManager;

@Repository("iRegistrationDAO")
public class RegistrationDAO extends JdbcTemplate implements IRegistrationDAO {

    @Resource private BasicDataSource dataSource;

    @Override
    public boolean checkUserName(String username) {
        int db_user = queryForInt("select count(username) from users where username = ?", username);

        if (db_user == 1 ){
            return true;
        }

        return false;
    }

    @Override
    public void insertUser(String username, String password, boolean enabled) throws Exception {
        System.out.println("inside the insertuser method. is wrapped in transaction: "+TransactionSynchronizationManager.isActualTransactionActive());

        update("insert into users (username, password, enabled) VALUES (?,?,?)", username, password, enabled);
    }

    @Override
    public void insertRoles(String username, String roles) throws Exception {
        update("insert into user_roles (username, authority) VALUES (?,?)", username, roles);
    }

    @Override
    public void insertUserDetails(String username, String firstname, String lastname, String email, int handicap) throws Exception {
        update("insert into user_detailss (username, first_name, last_name, email_address, handicap)" + 
                "VALUES (?,?,?,?,?)", username, firstname, lastname, email, handicap);

    }

    public void setDataSource(BasicDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public BasicDataSource getDataSource() {
        return dataSource;
    }
}

person Chris Doyle    schedule 04.04.2012    source источник
comment
Проблема, похоже, была связана с тем, что компонент сканирует компоненты, отличные от контроллеров, в сервлете xml. Я переместил <context:component-scan base-package="com.doyleisgod.golfer.dao"/> <context:component-scan base-package="com.doyleisgod.golfer.services"/> <context:component-scan base-package="com.doyleisgod.golfer.validators"/> в контекст приложения xml, который, похоже, решил проблему. Я не уверен, почему так, если кто-то хочет указать причину, по которой это работает, может свободно публиковать.   -  person Chris Doyle    schedule 05.04.2012


Ответы (1)


Причина, по которой перемещение тегов context:component-scan в контекст приложения xml зафиксировало транзакционное поведение, заключается в следующем: <tx:annotation-driven /> — это постпроцессор, который обертывает @Transactional аннотированных методов компонента с перехватчиком метода АОП, который обрабатывает транзакционное поведение. Постпроцессоры Spring работают только в конкретном контексте приложения, в котором они определены.

В вашем случае вы определили постпроцессор <tx:annotation-driven /> в контексте приложения, а bean-компоненты, аннотированные @Transactional, находятся в контексте приложения сервлета. Таким образом, постпроцессор <tx:annotation-driven /> работал только с компонентами контекста приложения, а не с компонентами контекста сервлета. Когда теги context:component-scan были перемещены в контекст приложения, постпроцессор <tx:annotation-driven /> соответствующим образом обернул их транзакционные методы.

Надеюсь, это имеет смысл.

[Изменить]

В чем разница между контекстом приложения и контекстом сервлета ?

Что такое постпроцессор Spring и как он работает?

Что такое АОП в Spring?

person MarkOfHall    schedule 05.04.2012
comment
Я думал, что Spring MVC требует сканирования @Controller из контекста DispatcherServlet, а не корневого контекста ContextLoaderListener. Следовательно, я удивлен, что перенос сканирования @Controller в контекст приложения по-прежнему заставляет работать @RequestMapping. (Действительно, это позволит использовать @Transactional в классах @Controller; просто интересуюсь побочными эффектами.) - person Arjan; 03.02.2013
comment
(быстрый тест, удаляющий сканирование @Controller из контекста DispatcherServlet и сканирующий все из контекста приложения, действительно приводит к тому, что все мои @RequestMapping игнорируются и, следовательно, терпят неудачу.) - person Arjan; 03.02.2013
comment
ОП заявляет в комментариях, что он переместил сканирование компонента dao и сервисов в контекст приложения, что устранило его проблему с транзакцией. Вопрос в комментарии был почему, на который я ответил. OP НЕ перемещал сканирование компонентов для контроллеров MVC из контекста сервлета. Таким образом, я не уверен, о чем эти комментарии от @Arjan. - person MarkOfHall; 20.03.2013
comment
Большое спасибо, Стив, очень ясное и точное объяснение! Я столкнулся с этой проблемой с неправильным размещением <tx:annotation-driven>, и ваш ответ все прояснил. Проблема решена. - person informatik01; 07.12.2016