Spring data @Transactional not roll back

17,343

Solution 1

I found the problem. In app-context.xml and servlet-context.xml, both have <context:component-scan/>. That tag will cause the problem that explained in this link: Spring @Transactional annotations ignored.

Solution 2

your Attribute class do not have id field. is it coming from AbstractPersistable<Long> can you show how did you annoted the ID field? is it Auto generated?

Depending upon answs to above there may be sevral possible things. 1. your Id never becomes greater than 1 and excepion is never thrown

I will suggest to try with below code and let me know if even after it the transaction is not rolled back.

t = attributeRepository.save(t);
throw new Exception();

Also showing code on how id is mapped in your Attribute class will be helpful to help you

Also how and where you have called the AttributeService Save Method also affects the transactions. that part of code will be helpful too.

Share:
17,343
Chrisma Andhika
Author by

Chrisma Andhika

Updated on June 04, 2022

Comments

  • Chrisma Andhika
    Chrisma Andhika almost 2 years

    I develops my first application using Spring MVC and Spring JPA. I get a problem when using @Transactional annotation. I annotate one of my service methods with @Transactional and I throw exception in that method because I expect it to be rolled back, but it do not.

    here is my configuration files and my class files content:

    app-context.xml

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <beans xmlns="http://www.springframework.org/schema/beans" 
        xmlns:context="http://www.springframework.org/schema/context" 
        xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
        xmlns:tx="http://www.springframework.org/schema/tx" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:jpa="http://www.springframework.org/schema/data/jpa"
        xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd   http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    
        <tx:annotation-driven transaction-manager="transactionManager"/>
    
        <context:component-scan base-package="id.co.cslgroup"/>
        <jpa:repositories base-package="id.co.cslgroup"/>
    
        <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="${database.url}"/>
            <property name="username" value="${database.username}"/>
            <property name="password" value="${database.password}"/>
            <property name="testOnBorrow" value="true"/>
            <property name="testOnReturn" value="true"/>
            <property name="testWhileIdle" value="true"/>
            <property name="timeBetweenEvictionRunsMillis" value="1800000"/>
            <property name="numTestsPerEvictionRun" value="3"/>
            <property name="minEvictableIdleTimeMillis" value="1800000"/>
            <property name="validationQuery" value="SELECT 1"/>
            <property name="defaultAutoCommit" value="false"/>
        </bean>
        <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
        </bean>
        <bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
            <property name="persistenceUnitName" value="kms-pu"/>
            <property name="dataSource" ref="dataSource"/>
            <property name="jpaVendorAdapter" ref="hibernateJpaAdapter"/>
        </bean>
    
        <bean id="customUserDetailsService" class="id.co.cslgroup.kms.svc.CustomUserDetailsService" />
        <bean id="hibernateJpaAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    
        <context:property-placeholder location="classpath:/META-INF/database.properties"/>
    </beans>
    

    web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    
        <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath:/META-INF/spring/app-context.xml 
                /WEB-INF/spring/security-app-context.xml
                        /WEB-INF/spring/collection-context.xml
            </param-value>
        </context-param>
    
        <filter>
            <filter-name>springSecurityFilterChain</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
    
        <filter-mapping>
          <filter-name>springSecurityFilterChain</filter-name>
          <url-pattern>/*</url-pattern>
        </filter-mapping>
    
        <!-- Creates the Spring Container shared by all Servlets and Filters -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
            <!-- place constraints on a single user's ability to log in to your application -->
            <listener>
                <listener-class>
                    org.springframework.security.web.session.HttpSessionEventPublisher
                </listener-class>
            </listener>
    
        <!-- Processes application requests -->
        <servlet>
            <servlet-name>kmsServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>/WEB-INF/spring/servlet-context.xml</param-value>
            </init-param>   
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>kmsServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

    servlet-context.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns="http://www.springframework.org/schema/mvc"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
    
        <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
    
        <!-- Enables the Spring MVC @Controller programming model -->
        <annotation-driven conversion-service="conversionService"/>
    
        <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
        <resources mapping="/resources/**" location="/resources/" />
    
        <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
        <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <beans:property name    ="prefix" value="/WEB-INF/views/" />
            <beans:property name="suffix" value=".jsp" />
        </beans:bean>
    
    
    
            <beans:bean id="conversionService"
                class="org.springframework.context.support.ConversionServiceFactoryBean">
              <beans:property name="converters">
                  <beans:list>
                      <beans:bean class="id.co.cslgroup.kms.svc.AttributeConverter"/>
                      <beans:bean class="id.co.cslgroup.kms.svc.KeyClassConverter"/>
                      <beans:bean class="id.co.cslgroup.kms.svc.KeyTypeConverter"/>
                      <beans:bean class="id.co.cslgroup.kms.svc.KeyProfileConverter"/>
                      <beans:bean class="id.co.cslgroup.kms.svc.StringToDateConverter"/>
                      <beans:bean class="id.co.cslgroup.kms.svc.StringToIntegerConverter"/>
                  </beans:list>
              </beans:property>
          </beans:bean>
        <context:component-scan base-package="id.co.cslgroup" />    
    </beans:beans
    

    >

    persistence.xml

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="kms-pu" transaction-type="RESOURCE_LOCAL">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            <properties>
                <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
                <!-- value="create" to build a new database on each run; value="update" to modify an existing database; value="create-drop" means the same as "create" but also drops tables when Hibernate closes; value="validate" makes no changes to the database -->
                <property name="hibernate.hbm2ddl.auto" value="update"/>
                <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
                <property name="hibernate.connection.charSet" value="UTF-8"/>
                <!-- Uncomment the following two properties for JBoss only -->
                <!-- property name="hibernate.validator.apply_to_ddl" value="false" /-->
                <!-- property name="hibernate.validator.autoregister_listeners" value="false" /-->
            </properties>
        </persistence-unit>
    </persistence>
    

    AttributeController.java

    @Controller
    @RequestMapping(value="/attribute")
    public class AttributeController {
    
        @Autowired
        private AttributeService attService;
    
        @RequestMapping(value = "/save", method = RequestMethod.POST)
        public String save(@ModelAttribute("attribute") @Valid AttributeMasterForm attr,BindingResult result,Model model,@RequestParam("id") Long attID,HttpServletRequest httpRequest)
        {
    
            Map paramMap = httpRequest.getParameterMap();
            String[] frmTitlePar = (String[]) paramMap.get("formTitle");
    
            if(result.hasErrors()){
                model.addAttribute("formTitle",frmTitlePar[0]);
                model.addAttribute("id", attID);
                return "attribute/add";
            }
    
            System.out.println("Status: " + attr.getStatus());
    
            try{
                if(attr.getStatus() == null)
                    attr.setStatus(false);
    
                if(attID != null){    
                    attr = attService.edit(attr,attID);
                }
                else{
                    attr = attService.save(attr);
                }
            }
            catch(Exception e){
                e.printStackTrace();
                model.addAttribute("violateMsg", "Constraint Violation");
                model.addAttribute("formTitle",frmTitlePar[0]);
                model.addAttribute("id", attr.getId());
                return "attribute/add";
            }
            return "redirect:/attribute/show?id="+attr.getId();
        }
    }
    

    AttributeRepository.java

    public interface AttributeRepository extends JpaRepository<Attribute, Long> {
    }
    

    AttributeService.java

    @Service
    @Transactional
    public class AttributeService {
    @Autowired
        private AttributeRepository attributeRepository;
    
    @Transactional(readOnly=false,propagation= Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
        public AttributeMasterForm save(AttributeMasterForm attForm) throws Exception{
            Attribute t = new Attribute();
            AttributeMasterForm masterForm = new AttributeMasterForm();
            //transfer value from master form to entity
            t = updateValue(t,attForm);
            //persist to database
            t = attributeRepository.save(t);
    
            if(t.getId() > 1){
                throw new Exception();
            }
    
            return this.convertToMasterForm(t);
        }
    
    private AttributeMasterForm convertToMasterForm(Attribute attr){
                AttributeMasterForm masterForm = new AttributeMasterForm();
                masterForm.setId(attr.getId());
                masterForm.setCode(attr.getCode());
                masterForm.setName(attr.getName());
                masterForm.setStatus(attr.getStatus());
    
            return masterForm;
        }
    
    private Attribute updateValue(Attribute attr,AttributeMasterForm attForm){
                attr.setCode(attForm.getCode());
                attr.setName(attForm.getName());
                attr.setStatus(attForm.getStatus());
    
                return attr;
            }
    }
    

    Attribute.java

    package id.co.cslgroup.kms.entity;
    
    import java.util.Collection;
    import java.util.Date;
    import java.util.Set;
    import javax.persistence.CascadeType;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.JoinColumn;
    import javax.persistence.JoinTable;
    import javax.persistence.ManyToMany;
    import javax.persistence.OneToMany;
    import javax.persistence.PrePersist;
    import javax.persistence.PreUpdate;
    import javax.persistence.Temporal;
    import javax.persistence.Version;
    import javax.validation.constraints.NotNull;
    import org.hibernate.validator.constraints.NotEmpty;
    
    
    import org.springframework.data.jpa.domain.AbstractPersistable;
    
    @Entity
    public class Attribute extends AbstractPersistable<Long> {
    
        @Column(nullable = false, unique = true)
        @NotEmpty
        @NotNull
        private String code;
        @Column(nullable = false)
        @NotEmpty
        @NotNull
        private String name;
    
        @OneToMany(fetch= FetchType.LAZY, mappedBy = "pk.attribute")
        private Collection<ProfileAttribute> profileAttribute;
    
        @Column(nullable = false)
        @Temporal(javax.persistence.TemporalType.DATE)
        private Date createTime;
        @Column(nullable = false)
        private String createBy;
        @Column(nullable = true)
        @Temporal(javax.persistence.TemporalType.DATE)
        private Date updateTime;
        @Column(nullable = true)
        private String updateBy;
        @Column(nullable = false)
        private Boolean status;
        @Version
        @Column
        private Long version = 0L;
    
    //        @ManyToMany(cascade=CascadeType.ALL)
    //        @JoinTable(name = "key_attributes",
    //            joinColumns = {@JoinColumn(name="attribute_id")},
    //            inverseJoinColumns = {@JoinColumn(name="key_table_id")}
    //        )
    //        private Set<KeyTable> keytables;
    //        
    //        public Set<KeyTable> getKeyTables(){
    //            return keytables;
    //        }
    //        
    //        public void setKeyTables(Set<KeyTable> kt){
    //            this.keytables = kt;
    //        }
    //        @Transient
    //        private String[] testArr;
    //        
    //        public String[] getTestArr() {
    //      return testArr;
    //  }
    //
    //  public void setTestArr(String[] ta) {
    //      this.testArr = ta;
    //  }
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Boolean getStatus() {
            return status;
        }
    
        public void setStatus(Boolean status) {
            this.status = status;
        }
    
        public String getCreateBy() {
            return createBy;
        }
    
        public void setCreateBy(String createBy) {
            this.createBy = createBy;
        }
    
        public String getUpdateBy() {
            return updateBy;
        }
    
        public void setUpdateBy(String updateBy) {
            this.updateBy = updateBy;
        }
    
        public Date getCreateTime() {
            return createTime;
        }
    
        public Date getUpdateTime() {
            return updateTime;
        }
    
        public Long getVersion() {
            return version;
        }
    
        @PrePersist
        private void prePersist() {
            this.createBy = "Admin";
            this.createTime = new Date();
        }
    
        @PreUpdate
        private void preUpdate() {
            this.updateBy = "Admin";
            this.updateTime = new Date();
        }
    
        /**
         * @return the keyProfiles
         */
            public Collection<ProfileAttribute> getProfileAttribute() {
            return profileAttribute;
        }
    
        /**
         * @param keyProfiles the keyProfiles to set
         */
        public void setProfileAttribute(Collection<ProfileAttribute> profileAttribute) {
            this.profileAttribute = profileAttribute;
        }
    }
    

    AttributeMasterForm.java

    /*
     * To change this template, choose Tools | Templates
     * and open the template in the editor.
     */
    package id.co.cslgroup.kms.model;
    
    import java.util.Date;
    import javax.validation.constraints.NotNull;
    import org.hibernate.validator.constraints.NotEmpty;
    
    /**
     *
     * @author supriadi
     */
    public class AttributeMasterForm {
    
        private Long id;
    
        @NotEmpty
        @NotNull
        private String code;
    
        @NotEmpty
        @NotNull
        private String name;
    
        private Date createTime;
        private String createBy;
        private Date updateTime;
        private String updateBy;
    
        private Boolean status;
    
        /**
         * @return the id
         */
        public Long getId() {
            return id;
        }
    
        /**
         * @param id the id to set
         */
        public void setId(Long id) {
            this.id = id;
        }
    
        /**
         * @return the code
         */
        public String getCode() {
            return code;
        }
    
        /**
         * @param code the code to set
         */
        public void setCode(String code) {
            this.code = code;
        }
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @param name the name to set
         */
        public void setName(String name) {
            this.name = name;
        }
    
        /**
         * @return the status
         */
        public Boolean getStatus() {
            return status;
        }
    
        /**
         * @param status the status to set
         */
        public void setStatus(Boolean status) {
            this.status = status;
        }
    
        /**
         * @return the createTime
         */
        public Date getCreateTime() {
            return createTime;
        }
    
        /**
         * @param createTime the createTime to set
         */
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }
    
        /**
         * @return the createBy
         */
        public String getCreateBy() {
            return createBy;
        }
    
        /**
         * @param createBy the createBy to set
         */
        public void setCreateBy(String createBy) {
            this.createBy = createBy;
        }
    
        /**
         * @return the updateTime
         */
        public Date getUpdateTime() {
            return updateTime;
        }
    
        /**
         * @param updateTime the updateTime to set
         */
        public void setUpdateTime(Date updateTime) {
            this.updateTime = updateTime;
        }
    
        /**
         * @return the updateBy
         */
        public String getUpdateBy() {
            return updateBy;
        }
    
        /**
         * @param updateBy the updateBy to set
         */
        public void setUpdateBy(String updateBy) {
            this.updateBy = updateBy;
        }
    }
    

    In AttributeService.java, in save method, you can find:

    t = attributeRepository.save(t);
    
    if(t.getId() > 1){
                throw new Exception();
            }
    

    So after I call save, I immediately throw an exception, but then I check the database and it did not rolled back.

    I really appreciate any help from you guys, Thanks!