I want to test if my Spring Transaction Management using Spring JDBC is working properly when one of the database updates fails. Following is my code to update two DB tables: person and contact_info
public void createWithContactInfo(String username, String name, Date dob,
String contactName, String contactPhone, String contactEmail) {
try {
String sqlStmt = "INSERT INTO person (username, name, dob) VALUES (?, ?, ?)";
jdbcTemplateObject.update(sqlStmt, "paul", "Paul", dob);
sqlStmt = "INSERT INTO contact_info(username, customer_name, contact_name, contact_phone, contact_email) VALUES (?, ?, ?, ?, ?)";
jdbcTemplateObject.update(sqlStmt, username, name, contactName,
contactPhone, contactEmail);
} catch (DataAccessException e) {
e.printStackTrace();
}
}
I use the Spring declarative transaction management to configure the beans:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="createWithContactInfo"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="createOperation"
expression="execution(* com.example.db.CustomerJDBCTemplate.createWithContactInfo(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation" />
</aop:config>
<!-- Initialization for data source -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/Customer" />
<property name="username" value="myusername"/>
<property name="password" value="12345"/>
<property name="initialSize" value="10"/>
</bean>
<!-- Definition for customerJDBCTemplate bean -->
<bean id="customerJDBCTemplate" class="com.example.db.CustomerJDBCTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Then in my test code, I have:
public class JdbcTest {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
CustomerDAO dao = (CustomerDAO) ctx.getBean("customerJDBCTemplate");
Date dob = new Date(90, 9, 10);
dao.createWithContactInfo("m9087", "Sam", dob, "David", "123456", "a123#example.com");
}
}
After running the main program, I got the Exception saying that Duplicate entry 'm9087' for key 'PRIMARY'. This is expected because m9087 already exists in the contact_info table. But since the second DB insert fails in the transaction, I thought the first jdbcTemplateObject.update(sqlStmt, "paul", "Paul", dob); will not be committed in the transaction. However I checked the person table and it returns valid entry for username=paul:
SELECT * FROM person WHERE username='paul';
This means the first DB insert was successful even though the second DB insert failed due to duplicate key exception.
My understanding is that the transaction should rollback and no commit will be made if any of the DB operation fail. But in this case, even though the second DB update fails due to duplicate key exception, the first DB insert still succeeded. Isn't it wrong behavior of transaction management? Is my setup correct in transaction management?
It's not rolling back because you're catching the exception. Transactions are rolled back when unchecked exception is thrown. And you are swallowing it with your catch block:
catch (DataAccessException e) {
e.printStackTrace();
}
The JdbcTemplate does not create automatically an transaction that envelop your both update statements in one transaction.
#Autowire
PlatformTransactionManager transactionManager;
public void createWithContactInfo(String username, String name, Date dob,
String contactName, String contactPhone, String contactEmail) {
DefaultTransactionDefinition paramTransactionDefinition =
new DefaultTransactionDefinition();
TransactionStatus status =
transactionManager.getTransaction(paramTransactionDefinition);
try{
... your 2 statmenets here ...
platformTransactionManager.commit(status);
}catch (Exception e) {
platformTransactionManager.rollback(status);
}
}
Related
I am stuck into a big trouble that i cannot connect the oracle 11g database form my Spring MVC application.
The error i am getting is
HTTP Status 500 - Request processing failed; nested exception is org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: Connections could not be acquired from the underlying database!
also,
in the stack trace i'm getting the error-
java.lang.ClassNotFoundException: oracle.jdbc.OracleDriver
If you can help me to resolve the issue, it will be a great help.
I am providing my configuration and coding details below:
Default-servlet.xml
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxPoolSize" value="${jdbc.maxPoolSize}" />
<property name="minPoolSize" value="${jdbc.minPoolSize}" />
<property name="maxStatements" value="${jdbc.maxStatements}" />
<property name="testConnectionOnCheckout" value="${jdbc.testConnection}" />
</bean>
<bean id="userAuthenticationRepository"
class="com.era.repository.impl.UserAuthenticationRepositoryImpl">
<property name="dataSource" ref="dataSource" />
</bean>
UserAuthenticationRepositoryImpl.java
#Repository
public class UserAuthenticationRepositoryImpl implements UserAuthenticationRepository {
#Qualifier("dbDataSource")
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public User getUserAuthentication(User userToBeAuthenticated) {
// TODO Auto-generated method stub
String query = "select id, name, role from User where login =";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
StringBuilder queryString = new StringBuilder();
queryString.append(" SELECT ")
.append( "*" )
.append(" FROM table_name ")
.append(" WHERE login = ? ");
Object[] parameterList = { userToBeAuthenticated.getLogin() };
SqlRowSet dataRow = jdbcTemplate.queryForRowSet(queryString.toString(), parameterList);
if (dataRow.next()) {
System.out.println("Query executed successfully");
}
return null;
}
As you are using maven, note here you can't directly get Oracle driver jar to .m2 due to licence restriction, so you may need to manually download and place it to your repository.You may find this link helpful.
I need in my application to remove all data from a cachable table.
I suposed that to delete all contents, I had to remove the second level cache, then use a truncate.
#Entity
#Table(name = "\"cpf_formacode\"")
#Cacheable
public class CpfRefFormaCode implements Serializable {
.......
}
the Dao method:
public void deleteAll() {
SessionFactory sf = em.unwrap(SessionFactory.class);
sf.getCache().evictEntityRegion(CpfRefFormaCode.class);
em.createNativeQuery("TRUNCATE TABLE cpf_formacode").executeUpdate();
}
persistence file:
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="org.hibernate.FlushMode" value="commit" />
<!-- property name="hibernate.hbm2ddl.auto" value="create-drop" / -->
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.infinispan.cachemanager" value="java:jboss/infinispan/hibernate" />
<property name="hibernate.cache.region.factory_class" value="org.jboss.as.jpa.hibernate4.infinispan.InfinispanRegionFactory" />
<property name="hibernate.cache.infinispan.statistics" value="true" />
</properties>
the error i have :
17:50:17,161 ERROR [org.jboss.as.ejb3.tx.CMTTxInterceptor] (http--127.0.0.1-8080-2) javax.ejb.EJBTransactionRolledbackException: Hibernate cannot unwrap interface org.hibernate.SessionFactory
17:50:17,163 ERROR [org.jboss.ejb3.invocation] (http--127.0.0.1-8080-2) JBAS014134: EJB Invocation failed on component CpfRefFormaCodeDao for method public void com.agefos.corp.business.dao.CpfRefFormaCodeDao.deleteAll(): javax.ejb.EJBTransactionRolledbackException: Hibernate cannot unwrap interface org.hibernate.SessionFactory
Caused by: javax.persistence.PersistenceException: Hibernate cannot unwrap interface org.hibernate.SessionFactory
I finished by findig the solution,
The problem that i was trying to clean the cach before deleting the data, and it was not the best practice
public void deleteAll() {
try {
TypedQuery<MyEntity> query = em.createQuery("From MyEntity f", MyEntity.class);
query.setHint("org.hibernate.cacheable", true);
List<MyEntity> result = null;
result = query.getResultList();
if (!result.isEmpty()) {
for (MyEntity f : result) {
em.remove(f);
}
}
em.flush();
} catch (Exception e) {
throw new PersistanceException("An error occurred while attempting to delete an instance of an object : " + entityClass, e);
}
}
the problem was resolved by adding the
em.flush();
In other words, flush tells Hibernate to execute the SQL statements needed to synchronize the JDBC connection's state with the state of objects held in the session-level cache. so i was abel to save other entities without ID problems
I'm trying to catch a ConstraintViolationException in my service layer and rethrowing a user defined checked exception. I'm catching the exception in my controller and adding an error object to my BindingResult. I'm using declarative transaction management I've tried to make my DAO a Repository and added a PersistenceExceptionTranslationPostProcessor to catch a spring translated exception. I've also added a txAdvice to rollback on all throwables. My exception does get caught but I'm getting an error 500 with:
Hibernate: insert into user (email, password, first_name, last_name, userType) values (?, ?, ?, ?, 1)
[acme]: [WARN ] - 2013-Feb-05 11:12:43 - SqlExceptionHelper:logExceptions(): SQL Error: 1062, SQLState: 23000
[acme]: [ERROR] - 2013-Feb-05 11:12:43 - SqlExceptionHelper:logExceptions(): Duplicate entry 'admin' for key 'email_unique'
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:processCommit(): Initiating transaction commit
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:doCommit(): Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
[acme]: [ERROR] - 2013-Feb-05 11:12:43 - AssertionFailure:<init>(): HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in com.test.model.AdminUser entry (don't flush the Session after an exception occurs)
[acme]: [DEBUG] - 2013-Feb-05 11:12:43 - HibernateTransactionManager:doRollbackOnCommitException(): Initiating transaction rollback after commit exception
org.hibernate.AssertionFailure: null id in com.test.model.AdminUser entry (don't flush the Session after an exception occurs)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:79)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:194)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1213)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:402)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
at org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:468)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
My Controller:
#RequestMapping (method = RequestMethod.POST)
#Transactional
public String registerAdmin(#Valid #ModelAttribute("user") AdminUser user, BindingResult bindingResult, ModelMap model) {
if (bindingResult.hasErrors()) {
return "admin/admins/form";
}
else if (!user.getPassword().equals(user.getConfirmPassword())) {
bindingResult.addError(new ObjectError("user.confirmPassword", "Passwords don't match"));
return "admin/admins/form";
}
else {
user.setPassword(passwordEncoder.encodePassword(user.getPassword(), null));
try {
userService.save(user);
return "redirect:/admin/admins";
} catch(ApplicationException ce) {
bindingResult.addError(new ObjectError("user.email", "Email already registered"));
return "admin/admins/form";
}
}
}
Part of my Spring config:
<context:component-scan base-package="com.test.dao, com.test.service" />
<context:property-placeholder location="/WEB-INF/spring.properties"/>
<import resource="springapp-security.xml"/>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdb?zeroDateTimeBehavior=convertToNull"/>
<property name="username" value="test"/>
<property name="password" value="test"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="mappingLocations" value="classpath*:com/test/model/hbm/**/*.hbm.xml" />
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
hibernate.show_sql=true
</value>
</property>
</bean>
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" />
</tx:attributes>
</tx:advice>
Service layer:
public class UserServiceImpl implements UserDetailsService, UserService {
private UserDAO dao;
#Override
public void save(User c) throws ApplicationException {
try {
dao.save(c);
} catch(DataIntegrityViolationException cve) {
throw new ApplicationException("email already registered");
}
}
If I don't catch the runtime exception I don't get the hibernate exception (don't flush the session..)
You may want to remove the transaction annotation from your controller and add it to the service layer.
The service layer would look like below. If your service layer is throwing a checked exception you can add that to your annotation so that the insert is not even attempted to be committed.
public class UserServiceImpl implements UserDetailsService, UserService {
private UserDAO dao;
#Override
#Transactional(rollbackFor=ApplicationException.class)
public void save(User c) throws ApplicationException {
try {
dao.save(c);
} catch(DataIntegrityViolationException cve) {
throw new ApplicationException("email already registered");
}
}
What is happening currently in your code is that the transaction is not being rolled back but has to rollback because it actually tried to commit the data but because a database constraint the transaction had to be rolled back. By forcing the rollback with the #Transactional(rollbackFor=ApplicationException.class) it will not allow the transaction to perform a commit but it will rollback and your app will still add the error to the BindingResult.
Hi im new to this Spring , MVC and JdBC support.
I want to be able to connect to a mysql database..but when i run my web it returns null. Below codes i believe should be easy,What am i missing here ? Thanks for all replies
Below is my error when try to query the URL
java.lang.NullPointerException
com.simple.myacc.dao.JdbcContactDao.findAll(JdbcContactDao.java:55)
com.simple.myacc.ContactController.getAll(ContactController.java:44)
My spring.xml
.....
<context:component-scan base-package="com.simple.myacc" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/webcontact" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<bean id="jdbcContactDao" class="com.simple.myacc.dao.JdbcContactDao">
<property name="dataSource" ref="dataSource" />
</bean>
My JdbcContactDao
public class JdbcContactDao {
protected static Logger logger = Logger.getLogger("service");
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public JdbcContactDao() {
}
public List<Contact> findAll() {
String sql = "select * from contact";
List<Contact> contacts = new ArrayList<Contact>();
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
for (Map rs : rows) {
Contact contact = new Contact();
contact.setId((Integer) rs.get("id"));
contact.setFirstname((String) rs.get("firstname"));
contact.setLastname((String) rs.get("lastname"));
contact.setEmail((String) rs.get("email"));
contact.setPhone((String) rs.get("phone"));
contacts.add(contact);
}
return contacts;
}
#Resource(name = "dataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
} }
My controller , some part of it
#RequestMapping(value="/contact/list2",method = RequestMethod.GET)
public String getAll(ModelMap model) {
dao=new JdbcContactDao();
List<Contact> contacts = dao.findAll();
// Attach persons to the Model
model.addAttribute("contacts", contacts);
return "contact.list";
}
This is the line that says the NULL
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
A common idiom when using the JdbcTemplate class is to configure a DataSource in your Spring configuration file, and then dependency inject that shared DataSource bean into your DAO classes; the JdbcTemplate is created in the setter for the DataSource.
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
You can read more on this here
Your code will look like this
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/webcontact" />
<property name="username" value="root" />
<property name="password" value="password" />
You don't need this
<bean id="jdbcContactDao" class="com.simple.myacc.dao.JdbcContactDao">
<property name="dataSource" ref="dataSource" />
Instead do this
#Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
and annotate your JdbcContactDao class with #Repository
I think that should work
You have your dataSource and your JdbcContactDAO bean configured in the config file.
So in the same way you need to inject the jdbcContactDAO bean into your Controller.
<bean id="myController" class="mypath.MyController">
<property name="dao" ref="jdbcContactDao"/>
</bean>
And in your controller....
public JdbcContactDao dao;
#Resource(name="dao")
public void setDao(JdbcContactDao dao){
this.dao = dao;
}
#RequestMapping(value="/contact/list2",method = RequestMethod.GET)
public String getAll(ModelMap model) {
List<Contact> contacts = dao.findAll();
// Attach persons to the Model
model.addAttribute("contacts", contacts);
return "contact.list";
}
I'm guessing line 55 of JdbcContactDao is this one List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql); You declare jdbcTemplate, but never give it a value and it's also not annotated for injection, so it's always going to be null. So, when you attempt to use it, you will get the NPE.
Had a similar problem was connecting to old tables using java/jdbc
String sql = "select user_name from table"
jdbc.queryForList(sql);
queryReturnList = jdbc.queryForList(sql);
for (Map mp : queryReturnList){
String userName = (String)mp.get("user_name");
}
userName was always null. looking through the map of returned values found that the map was not using user_name but a label set up on the table of "User Name" Had to have DBA's fix. Hope this helps
As part of a web app i am trying to build a registration process. after validating the process there are three sql statments to be performed. If any should fail then they should all be rolled back. However If i purposefully write the 3rd sql to fail (use a table name that doesnt exist). I see exception being thrown but the 1st and 2nd swl statments are not rolled back.
Can someone advise me on how this should be done.
from my application-context.xml
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="userDAO" class="com.doyleisgod.golf.database.JdbcUserDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="registration" class="com.doyleisgod.golf.services.Registration"/>
<tx:annotation-driven />
My registration service class
public class Registration implements IRegistration {
#Autowired JdbcUserDao userDAO;
#Override
#Transactional (rollbackFor = Exception.class)
public boolean registerUser(Object command) {
return userDAO.registerUser(command);
}
}
my userDAO registration method
public boolean registerUser(Object command) {
try {
setUserCommand(command);
sql = "INSERT INTO users (USERNAME,PASSWORD, ENABLED)VALUES ('"+username+"', '"+EncryptedPassword+"', TRUE);";
getSimpleJdbcTemplate().update(sql);
sql = "INSERT INTO user_roles (USERNAME,AUTHORITY)VALUES ('"+username+"', 'ROLE_USER');";
getSimpleJdbcTemplate().update(sql);
sql = "INSERT INTO users_details (USERNAME,FIRST_NAME, LAST_NAME, EMAIL_ADDRESS, HANDICAP)VALUES ('"+username+"', '"+firstname+"', '"+lastname+"', '"+email+"', '"+handicap+"');";
getSimpleJdbcTemplate().update(sql);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
Exampl of exception being thrown
15-Feb-2012 21:13:48 org.springframework.jdbc.support.SQLErrorCodesFactory <init>
INFO: SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
org.springframework.jdbc.BadSqlGrammarException: StatementCallback; bad SQL grammar [INSERT INTO users_details (USERNAME,FIRST_NAME, LAST_NAME, EMAIL_ADDRESS, HANDICAP)VALUES ('d', 'd', 'd', 'd', '0');]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'golf.users_details' doesn't exist
Can someone tell me what i have missed. Why when the 3rd sql statment fails do the other 2 transactions not get rolled back?
Because you are catching the exception in your DAO, it is not thrown to the TransactionManager.