I am new to Spring and learning the transaction concepts. Unable to get the #Transactional to work.
Use Case:
Data insert of Employee and Employee details should rollback when getEmployee() throws RunTimeException. But the rollback is not happening.
I am using Oracle database 11g and spring 4.3.1.RELEASE. Below is the standalone java code am running.
Code
public static void main( String[] args )
{
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext("spring-bean.xml");
ctx.registerShutdownHook();
Employee emp = new Employee("149518", "Mickey", "Mouse", "15 years", "tennis");
IEmployee empIntfc = (IEmployee)ctx.getBean("empService");
try {
empIntfc.createEmployee(emp);
empIntfc.createEmployeeDetails(emp);
//Below will throw RunTime Exception
empIntfc.getEmployee(2);
}catch (Exception e ) {
e.printStackTrace();
} finally {
ctx.close();
}
}
EmployeeService.java
public class EmployeeService implements IEmployee {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
jdbcTemplate = new JdbcTemplate(this.dataSource);
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
#Override
#Transactional
public int createEmployee(Employee emp) {
String sql1 = "INSERT INTO TEST_T1(EMP_ID, EMP_FNAME, EMP_LNAME) values
(?,?,?)";
return getJdbcTemplate().update(sql1, emp.getEmpId(),
emp.getEmpFirstName(), emp.getEmpLastName());
}
#Override
#Transactional
public int createEmployeeDetails(Employee emp) {
String sql = "INSERT INTO TEST_T2(EMP_ID, EXP, SKILLS) values (?,?,?)";
return getJdbcTemplate().update(sql, emp.getEmpId(), emp.getExp(),
emp.getSkills());
}
#Override
#Transactional(readOnly = true, noRollbackFor=RuntimeException.class)
public Employee getEmployee(int empId) {
throw new RuntimeException("Intentional runtime exception");
}
}
spring-bean.xml
<beans xmlns="http://www.springframework.org/schema/beans">
<context:annotation-config/>
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:#//xxxx:1521/xxxx"/>
<property name="username" value="user"/>
<property name="password" value="user"/>
</bean>
<bean id="empService" class="com.service.EmployeeService">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
Your main method is not transactional ... means : entering 'createEmployee' creates a new transaction and commits it, 'createEmployeeDetails' creates a new transaction and commits it .
Related
I developed the application using spring transactions and insert records in the table.
I'm explicitly throwing the exception in DAO class but spring is inserting the record into the table rather than roll back the transaction.
I have created the two applications as below . In Case 1 record is inserted into table even though exception is thrown . But In Case 2 no record is inserted and spring roll back the transaction successfully. Can you explain me the difference between these two applications.
Case 1:
Item.java
public class Item {
int itemNo;
String itemName;
String itemType;
String itemSize;
public int getItemNo() {
return itemNo;
}
public void setItemNo(int itemNo) {
this.itemNo = itemNo;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getItemType() {
return itemType;
}
public void setItemType(String itemType) {
this.itemType = itemType;
}
public String getItemSize() {
return itemSize;
}
public void setItemSize(String itemSize) {
this.itemSize = itemSize;
}
}
ItemDao
#Service
public class ItemDao {
#Autowired
JdbcTemplate jdbcTemplate ;
void insert(Item item){
jdbcTemplate.update("insert into item_test(itemno, itemtype,itemsize,itemname) values (?,?,?,?)", new Object[]{item.getItemNo(),item.getItemType(),item.getItemSize(),item.getItemName()});
int a=2/0;
}
}
ItemService.java
#Service
public class ItemService {
#Autowired
ItemDao itemDao;
#Transactional
public void insert(Item item){
try{
itemDao.insert(item);
}
catch(Exception e){
e.printStackTrace();
}
}
}
Test.java
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext ct = new ClassPathXmlApplicationContext("spring.xml");
ItemService itemService = ct.getBean("itemService", ItemService.class);
Item item = new Item();
item.setItemNo(1234);
item.setItemName("sofa");
item.setItemSize("4");
item.setItemType("furniture");
itemService.insert(item);
}
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- Enable Annotation based Declarative Transaction Management -->
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
<context:component-scan base-package="com.spring.springtransaction" />
<!-- Creating TransactionManager Bean, since JDBC we are creating of type
DataSourceTransactionManager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- MySQL DB DataSource -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:#locahost:1521:xe)))" />
<property name="username" value="system" />
<property name="password" value="system" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="itemService" class="com.spring.springtransaction.ItemService" />
</beans>
Case 2:
ItemService.java
#Service
public class ItemService {
#Autowired
ItemDao itemDao;
#Autowired
ItemManger itemManger;
#Transactional
public void insert(Item item){
try{
itemManger.insert(item);
}
catch(Exception e){
e.printStackTrace();
}
}
}
ItemManger.java
#Service
public class ItemManger {
#Autowired
ItemDao itemDao;
#Transactional
public void insert(Item item){
itemDao.insert(item);
}
}
ItemDao.java
#Service
public class ItemDao {
#Autowired
JdbcTemplate jdbcTemplate ;
void insert(Item item){
jdbcTemplate.update("insert into item_test(itemno, itemtype,itemsize,itemname) values (?,?,?,?)", new Object[]{item.getItemNo(),item.getItemType(),item.getItemSize(),item.getItemName()});
int a=2/0;
}
}
Annotate you ItemDao as #Repository instead of #Service
You should execute unit test with spring Transactional context instead of main , for example using TestNG:
#ContextConfiguration(classes = {ConfigurationClass.class})
#ActiveProfiles({"test"})
public class TestItemDAO extends AbstractTransactionalTestNGSpringContextTests {
#Autowired
private ItemDao dao;
#Test
public void testItemDao() {
dao.insert(item);
}
}
Remove the try block, you are trying to handle the exception so this is reason why RollbackException is not cutting the transaction stream.
I have 3 classes (UserSrvImpl in service layer, MailDaoImpl and UserDaoImpl in dao (repository) layer). In my service layer (UserSrvImpl), i have a method that calls a method from MailDaoImpl and a method from UserDaoImpl. These 2 dao methods have to save objects in 2 different tables. These 2 tables have a OneToOne relasionship. My problem is when the first insertion succeeds and the second fails, there is no roll back. I'm struggling with this problem since a week. I applied all the solutions that i found in google, but still no success. Can you please give me a help.
Here is my service layer:
#Service
public class UserSrvImpl implements IUserSrv {
#Autowired
private IUserDao userDao;
#Autowired
private IMailDao mailDao;
#Override
#Transactional
public boolean create(UserDto userDto) {
boolean result = false;
try {
User userToSave = transformDtoToEntity(userDto);
MailToken mailToken = createMailTokenForNewUser(userToSave);
userToSave.setMailToken(mailToken);
if (mailDao.createMailToken(mailToken)) {
result = userDao.create(userToSave);
}
} catch (Exception ex) {
}
return result;
}
The service is calling the dao's method mailDao.createMailToken and userDao.create. If the first succeeds and the second fails, there is no roll back on the first table in the database.
Below is the code in MailDao:
#Repository
public class MailDao implements IMailDao {
#Autowired
private SessionFactory sessionFactory;
public Session getSession() {
Session sess = this.sessionFactory.getCurrentSession();
if (sess == null) {
sess = this.sessionFactory.openSession();
}
return sess;
}
#Override
public boolean createMailToken(MailToken mailToken) {
try {
getSession().save(mailToken);
return true;
} catch (HibernateException ex) {
} catch (Exception ex) {
// TODO: handle exception
}
return false;
}
Below is the code in UserDaoImpl:
#Repository
public class UserDaoImpl implements IUserDao {
#Autowired
private SessionFactory sessionFactory;
public Session getSession() {
Session sess = this.sessionFactory.getCurrentSession();
if (sess == null) {
sess = this.sessionFactory.openSession();
}
return sess;
}
#Override
public boolean create(User userToSave) {
try {
getSession().save(userToSave);
return true;
} catch (HibernateException ex) {
ex.printStackTrace();
} catch (Exception ex) {
// TODO: handle exception
}
return false;
}
This is my spring configuration:
<bean id="hibernate5AnnotatedSessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="packagesToScan">
<array value-type="java.lang.String">
<value>com.pointsante.dao.domaine</value>
</array>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.hbm2ddl.auto">validate</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
Please can you tell me why the #Transactional on the method in the service layer is not working. Thank you.
you should probably use the HibernateTransactionManager instead of DataSourceTransactionManager.
use
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
instead of
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
also check from where the service is called. if you call it from within the service class itself, the proxy which handles the transaction management wont be used.
I am novice in spring Transaction Management, I am using it in batch insert but the batch processing is not working, its committing line by line when autoCommit is true, and if autoCommit is false then the data is not inserting in D.B.
below is the insert function.
#Override
#Transactional(propagation= Propagation.REQUIRED, rollbackFor=Exception.class)
public void insertInviteContactMsisdn(final long ettId, final List<String> msisdnList, final List<String> allreadyMsisdnList) {
try {
String qr = "insert into InviteContactMsisdn (id,ettIdAparty,msisdn,status) values(0,?,?,?)";
getJdbcTemplate().batchUpdate(qr, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, ettId);
ps.setString(2, msisdnList.get(i));
ps.setBoolean(3, allreadyMsisdnList.contains(msisdnList.get(i)));
}
#Override
public int getBatchSize() {
return msisdnList.size();
}
});
}catch(DataAccessException dataAccessException) {
log.error("error in processing insertInviteContactMsisdn ettId:"+ettId);
throw dataAccessException;
}
}
also the dataSource configuration:-
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${db.url}" />
<property name="username" value="${db.user}" />
<property name="password" value="${db.pass}" />
<property name="removeAbandoned" value="true"/>
<property name="initialSize" value="1" />
<property name="maxActive" value="1" />
<property name="defaultAutoCommit" value="false" />
Thanks.
I know I am doing something seriously wrong.
when I using programmatic Transaction Management the same problem occurring.
#Override
public void insertInviteContactMsisdn(final long ettId, final List<String> msisdnList, final List<String> allreadyMsisdnList) {
// TODO Auto-generated method stub
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
String qr = "insert into InviteContactMsisdn (id,ettIdAparty,msisdn,status) values(0,?,?,?)";
jdbcTemplateObject.batchUpdate(qr, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, ettId);
ps.setString(2, msisdnList.get(i));
ps.setBoolean(3, allreadyMsisdnList.contains(msisdnList.get(i)));
}
#Override
public int getBatchSize() {
return msisdnList.size();
}
});
transactionManager.commit(transactionStatus);
}catch(DataAccessException dataAccessException) {
log.error("error in processing insertInviteContactMsisdn ettId:"+ettId);
transactionManager.rollback(transactionStatus);
throw dataAccessException;
}
}
I use JpaRepository to save data, but the hibernate.show_sql shows "select" and won't save data. Following is my service:
#Autowired
private UserRepository userRepository;
#PostConstruct
public void init() {
User admin = new User();
admin.setDisplayName("admin");
admin.setEmailAddress("admin#admin");
admin.setPassword("admin___");
admin.setRegisteredAt(new Date());
admin.setLastAccessAt(new Date());
admin.setUuid(UUID.randomUUID().toString());
try {
System.out.println("before save");
userRepository.save(admin);
System.out.println("after save");
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
The output looks like this:
========before save======
Hibernate: select user0_.uuid as uuid1_0_0_, user0_.display_name as display_2_0_0_, user0_.email_address as email_ad3_0_0_, user0_.last_access_at as last_acc4_0_0_, user0_.password as password5_0_0_, user0_.registered_at as register6_0_0_ from User user0_ where user0_.uuid=?
========after save=======
Following is my applicationContext.xml:
<context:component-scan base-package="test">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/helloworld" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<bean id="myEmf"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="packagesToScan" value="test.entity"></property>
<property name="dataSource" ref="myDataSource" />
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource" />
</bean>
<jpa:repositories base-package="test.repository"
entity-manager-factory-ref="myEmf" transaction-manager-ref="transactionManager"></jpa:repositories>
Attached is my class generated by JPA Tools:
#Entity
#NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private String uuid;
#Column(name="display_name")
private String displayName;
#Column(name="email_address")
private String emailAddress;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="last_access_at")
private Date lastAccessAt;
private String password;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="registered_at")
private Date registeredAt;
public User() {
}
public String getUuid() {
return this.uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getDisplayName() {
return this.displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getEmailAddress() {
return this.emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public Date getLastAccessAt() {
return this.lastAccessAt;
}
public void setLastAccessAt(Date lastAccessAt) {
this.lastAccessAt = lastAccessAt;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getRegisteredAt() {
return this.registeredAt;
}
public void setRegisteredAt(Date registeredAt) {
this.registeredAt = registeredAt;
}
}
Since you're using JPA, the transaction manager should be a JpaTransactionManager, not a DataSourceTransactionManager.
I am facing a problem, when I tried to insert a data into Database through JPA (#persistanceContex)
Observations
Not getting any errors;
Record is not storing into database (save)
When I tried with listAll() ; it retrieving the data from database
Domain
#Entity
public class Test {
#Id
private int id;
#Column(name="full_name")
private String fullName;
#Column(name="mobile_number")
private int mobileNumber;
.....
}
DAO Class
#Repository("testDAO")
#Transactional
public class TestDAO {
private EntityManager entityManager;
#PersistenceContext(unitName="CRUD_Test_Annotation")
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public void save(Test test){
entityManager.persist(test);
}
}
Service
#Service("testService")
#Transactional
public class TestService {
private static final Logger logger = LoggerFactory.getLogger(TestService.class);
#Autowired(required=true)
private TestDAO testDAO;
public void save(Test test){
logger.info("TestService::save()");
testDAO.save(test);
}
public void list(){
testDAO.getAll();
}
}
Controller
#RequestMapping(value = "/add", method = RequestMethod.GET)
public String add(Locale locale, Model model) {
Test test = new Test();
test.setId(xx);
test.setFullName("xxxxx");
test.setMobileNumber(yyyyyy);
testService.save(test);
return "home";
}
application-context.xml
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Declare a JPA entityManagerFactory-->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml"></property>
<property name="persistenceUnitName" value="CRUD_Test_Annotation" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
First of all, you don't need two transaction boundaries, I suggest you remove #Transactional from your DAO and keep the one in your service.
Start by verifying that spring-transaction has initiated a transaction: Use the debugger and stop the application after the transaction boundary, for instance in your TestService.save-method. If transactions are running, you will see org.springframework.transaction.interceptor.TransactionInterceptor#invoke in the call stack. If you don't see the TransactionInterceptor, then that's your problem. Post your persistence.xml file if transactions are running.