Reputation: 341
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!
Upvotes: 4
Views: 17676
Reputation: 341
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.
Upvotes: 3
Reputation: 5615
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.
Upvotes: 1