I have a problem when testing JTA1.2 #Transactional annotation on Glassfish 4.1 application server.
If I run execute() method of this bean:
#Named
#RequestScoped
public class IndexController {
#Resource(name = "ds")
private DataSource ds;
#Transactional
public void execute() throws SQLException, SystemException {
try (Connection con = ds.getConnection();) {
try (PreparedStatement ps = con.prepareStatement(
"INSERT INTO test(id) VALUES(1)"
);) {
ps.executeUpdate();
throw new IllegalArgumentException();
}
}
}
}
I get expected error:
Caused by: javax.transaction.RollbackException: Transaction marked for rollback.
but when I execute select statement:
SELECT * FROM test;
I see that row was inserted. What's wrong?
What DB you are using and in which mode it is? Maybe I don't see it, but where are you doing a rollback? So the entry will still be temporarily written to DB until you rollback. Try this:
#Transactional(rollbackOn={Exception.class})
And normaly in a catch block you will call the rollback method. Because the transaction is only marked to be rolled back and you be responsable to roll it back.
Related
I wanted to insert some records using preparedstatement in Spring Boot application. So I have established the DB connection using hibernate(HibernateConfig - only for db connection). Since I'm going to use preparedstatement I acquired a Connection from Hibernate SessionFactory or Session.
I know that when using getCurrentSession will take care of the transaction management itself for hibernate operation like session.beginTransaction() and session.save().
Do I need to close the connection and other stuffs, explicitly in finally block when using sessionFactory.getCurrentSession()? Kindly check finally block in the below code:
In DAOImpl file
#Repository
#Transactional
public class TestDAOImpl implements TestDAO {
#Autowired
private SessionFactory sessionFactory;
public Connection getConnection(){
Session session = sessionFactory.getCurrentSession();
Connection conn = session.doReturningWork(new ReturningWork<Connection>() {
#Override
public Connection execute(Connection conn) throws SQLException {
return conn;
}
});
return conn;
}
public void insertRecords() {
Connection conn= getConnection();
PreparedStatement ps = null;
try {
String inserQuery ="INSERT IN TO TEST VALUES(?,?,?)" //some insert statement
conn.setAutoCommit(false);
ps = conn.prepareStatement(inserQuery);
//set values ps.setString() and ps.addBatch(); -some code here
int[] insertCounts = ps.executeBatch();
conn.commit();
} catch(BatchUpdateException e){
} catch(SQLException e){
} finally {
if(ps!=null)ps.close();
if(conn!=null) conn.close();
if(sessionFactory!=null) sessionFactory.close();
}
}
Please note that I'm using 3 more methods in same DAOImpl class and creating connection for each method Connection conn= getConnection(); and closing in finally block.
Also let me know best practices or any other alternate way to get connection from SessionFactory.
Yes, you defintitely should close connections in order to prevent memory leaks.
Also the best approach would be a try-with-resources statement, which automatically closes the closable resources you opened within it.
try(Connection con = getConnection; Statement stmt = con.prepareStatement(insertSql)) {
...
} catch(Exception e) {
...}
You dont need the finally block this way, too.
EDIT: FYI if you don't close a connection it will stay open until you shut down the application, and each subsequent call opens a new one. I guess you can imagine what this means, when your DB only allows a maximum of 100 open connections at a time.
As suggested by #MarkRotteveel, I removed hibernate config file and added below code
#Configuration
public class DataSourceConfig {
#Bean("dataSource")
#Primary
#ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
In DAOImpl class,
#Autowired
private DataSource dataSource;
try(Connection conn= dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(inserQuery);) {
.....
}catch(Exception e){
....
}
Please check and let me know if any modification required here.
I am developing a web application, where, among other things, I need to upload a file to a BLOB column in a mysql table. From what I can see this can be done with JDBC calls (PrepareStatement() etc), but I would like to be able to do this in an EJB class - what I have cobbled together looks like this:
#Stateless
public class ItemsSession {
#PersistenceContext(unitName ="officePU")
private EntityManager em;
private List<Items> itl;
private static final Logger logger=
Logger.getLogger(ItemsSession.class.getName());
...
public String updateDocument(Integer id,InputStream is) throws SQLException{
String msg="";
try{
java.sql.Connection conn = em.unwrap(java.sql.Connection.class);
PreparedStatement pstmt=conn.prepareStatement("UPDATE Documents SET doc = ? WHERE id = ?");
pstmt.setBinaryStream(1, is);
pstmt.setLong(2, id);
pstmt.executeUpdate();
pstmt.close();
}
catch (PersistenceException e){
msg=e.getMessage();
}
return msg;
}
...
}
I have two questions, though:
I would like not to use JDBC directly - is there a way to do this that is 'pure JPA' (edit: not EJB)?
If I have to do it this way, is the PreparedStatement included in the container managed transaction?
Another edit: the code above does the job - I have now tested it. But it isn't pretty, I think.
The first thing you have to do to persist BLOB values the JPA way is you define an entity. The following an example pseodo code:
#Entity
public class Documents {
#Id
private Long id;
#Lob
private byte[] doc;
// .... getters + setters
}
Then you modify your EJB as follows:
#Stateless
public class ItemsSession {
#PersistenceContext(unitName ="officePU")
private EntityManager em;
// ... the rest of your code
public String updateDocument(Integer id,InputStream is) throws SQLException{
String msg = "";
Documents docs = em.find(Documents.class, id); // fetch record from DB
// Assuming your InputStream is a ByteArrayInputStream
byte[] doc = new byte[is.available()]; // create target array
is.read(doc, 0, doc.length); // read bytes into byte array
docs.setDoc(doc); //
return msg; // returning exception message from method call?????
}
...
}
If you don't change the defaults EJB methods are invoked in a transaction by default. So when your method exits, the update should be synchronized with the database.
This answer kann only help you if you read and understand the basics of the JPA. And here is an official tutorial to JPA persistence among other lots of tutorials on the web.
Update
I would like not to use JDBC directly - is there a way to do this that is 'pure JPA'
No.
If I have to do it this way, is the PreparedStatement included in the container managed transaction?
No. But you can use bean managed transaction. If you want to use BMT, the following pseudocode might help you:
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
public class ItemsSession {
#Resource UserTransaction ut;
#Resource DataSource datasource; // you should define datasource on your application server
...
public String updateDocument(Integer id,InputStream is) throws SQLException{
// ...
try (java.sql.Connection conn = datasource.getConnection();
PreparedStatement pstmt=conn.prepareStatement("UPDATE Documents SET doc = ? WHERE id = ?")) {
pstmt.setBinaryStream(1, is);
pstmt.setLong(2, id);
ut.begin();
pstmt.executeUpdate();
ut.commit();
} catch (PersistenceException e){
// ... error handling
}
return ...;
}
...
}
I think you use EJB intergate to with JPA , because you are using this:
#PersistenceContext(unitName ="officePU")
Refernce: http://www.adam-bien.com/roller/abien/entry/ejb_3_persistence_jpa_for
Using Spring & JPA & Hibernate I use method to persist some entity with Exception handling like this one:
#Repository
public class UserDAOImpl implements UserDAO {
#PersistenceContext
EntityManager em;
#Override
public void createUserRole(String role) throws RoleAlreadyExistsException {
try {
UserRole userRole = new UserRole(role);
em.persist(userRole);
} catch (Exception e) {
throw new RoleAlreadyExistsException();
}
}
}
My service:
#Service("userService")
public class UserService
#Transactional
public void createUserRole(String role) throws RoleAlreadyExistsException {
userDao.createUserRole(role);
}
}
later I implement some logic:
try{
userService.createUserRole(role.name());
} catch (AuthorityEntityAlreadyExistsException e){}
but it doesn't catch exception which report about duplicate key :
SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [PRIMARY]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:259)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:485)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'ROLE_ADMIN' for key 'PRIMARY'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
So, wasn't thrown none of my RoleAlreadyExistsException, catch block was missed. Exception was thrown during transaction commit in the end of service method. How to catch that exception ??? Or how to implement this logic in Spring in another way??
1) DAOs shouldn't catch exceptions. Let the exception generated by Hibernate or Spring be thrown and leave it alone. Especially if your exception-conversion is extremely broad and loses information (throws away the original stacktrace).
2) Don't rely on constraint violations to tell you there's a pre-existing entry. You can query for it before telling Hibernate to perform the insert. As long as the query is in the same transaction as the insert, and the isolation level is repeatable-read or better, the query result will be reliable.
EntityManager.persist didn't necessary execute a sql statements : The new entity is registered in a cache, and send to the database when needed.
If your constraints are checked by the database, you'll see an error when this entity is send to the database, in the best case when the transaction commit : it's the behavior you see in your trace.
You can force your JPA implementation to send his cache to the database by invoking EntityManager.flush()
public void createUserRole(String role) throws RoleAlreadyExistsException {
try {
UserRole userRole = new UserRole(role);
em.persist(userRole);
em.flush();
} catch (Exception e) {
throw new RoleAlreadyExistsException();
}
}
however, calling flush too much can be a performance lost, you should call it only when necessary. It can maybe be more simple to separate DAO ans service : try cach DataIntegrityException in UserService and don't call flush in the DAO
use #transactional annotation for the DAO or service or controller layer for the method you want to roll back when ever SQLexception is caught during commit.
i.e #transactional
public void method(#param somevalue)
{
// Database saving or updating function
}
if you want specific message to be shown during exception
use the below if you want to have global handler for particular expception class
#ExceptionHandler(SQLException.class)
public String exceptionMessage(){ //return exception message}
This is my connection detail in JBoss standalone.xml
<connection-url>
jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(ADDRESS=(PROTOCOL=TCP)(HOST=xx.1xx.119.1xx)(PORT=1521))(LOAD_BALANCE=on)(FAILOVER=on))(CONNECT_DATA=(SERVICE_NAME=XE)))
</connection-url>
I want to handle a corner case of failover where post getting EntityManager object during a call of persist(), the connection is lost. Failover option is not switching to next database in the same transaction, it switches to active connection in the next transaction. I attempted something like this: (Catch Exception and get updated bean object)
public EntityManager getEntityManager() {
try {
entityManager = getEntityManagerDao(Constant.JNDI_NFVD_ASSURANCE_ENTITY_MANAGER);
} catch (NamingException e) {
LOGGER.severe("Data could not be persisted.");
throw new PersistenceException();
}
return entityManager.getEntityManager();
}
/**
* Inserts record in database. In case multiple connections/databases exist, one more attempt will be made to
* insert record.
*
* #param entry
*/
public void persist(Object entry) {
try {
getEntityManager().persist(entry);
} catch (PersistenceException pe) {
LOGGER.info("Could not persist data. Trying new DB connection.");
getEntityManager().persist(entry);
}
}
private static Object getJNDIObject(String path) throws NamingException {
Object jndiObject = null;
InitialContext initialContext = new InitialContext();
jndiObject = initialContext.lookup(path);
return jndiObject;
}
private static AssuranceEntityManager getEntityManagerDao(String path) throws NamingException {
return (AssuranceEntityManager) getJNDIObject(path);
}
But this one also is not helping. After catching the exception, getting a new bean with JNDI lookup does not contain an updated new connection and an exception is thrown. This results in loss of data of that transaction.
Please suggest how to handle this corner case of "Connection lost post getting EntityManager and before persisting."
I think it's quite impossible what you want to achieve. The thing is that if internal DB transction is aborted then the JTA transaction is in abort state and you can't continue with it.
I expect it's kind of similar to this case
#Stateless
public class TableCreator {
#Resource
DataSource datasource;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void create() {
try(Connection connection = datasource.getConnection()) {
Statement st = connection.createStatement();
st.execute("CREATE TABLE user (id INTEGER NOT NULL, name VARCHAR(255))");
} catch (SQLException sqle) {
// ignore this as table already exists
}
}
}
#Stateless
public class Inserter {
#EJB
private TableCreator creator;
public void call() {
creator.create();
UserEntity entity = new UserEntity(1, "EAP QE");
em.persist(entity);
}
}
In case that table user exists and you would use annotation #TransactionAttribute(TransactionAttributeType.REQUIRED) then the create call will be part of the same jta global transaction as call of persist. As in such case the transaction was aborted the persist call would fail with exception like (postgresql case)
Caused by: org.postgresql.util.PSQLException: ERROR: current transaction is aborted, commands ignored until end of transaction block
I mean if Oracle jdbc driver is not able to to handle connection fail transparently to JBoss app server and throws the exception upwards then I think that the only possible solution is to repeat the whole update action.
I have a JDBC code where there are multiple Savepoints present; something like this:
1st insert statement
2nd insert statement
savepoint = conn.setSavepoint("S1");
1st insert statement
2nd update statement
savepoint = conn.setSavepoint("S2");
1st delete statement
2nd delete statement
savepoint = conn.setSavepoint("S3");
1st insert statement
2nd delete statement
savepoint = conn.setSavepoint("S4");
Now in the catch block, I am catching the exception and checking whether the Savepoint is null or not; if yes then rollback the entire connection else rollback till a Savepoint. But I am not able to understand till which Savepoint shall I roll back.
Will it be fine if I change all the savepoint names to "S1" ? In that case how will I understand how many till Savepoint did work correctly?
Please advise how to understand until what Savepoint the work was performed correctly?
Would view this as multiple transactions. Hence you could handle this with multiple try/ catch blocks. You also seem to be overwriting the savepoint objects hence it would be not feasible to rollback.
More info.
JDBC also supports to set save points and then rollback to the specified save point. The following method could be used to define save points.
SavePoint savePoint1 = connection.setSavePoint();
Rollback a transaction to an already defined save point using rollback call with an argument.
connection.rollback(savePoint1);
Reference.
https://www.stackstalk.com/2014/08/jdbc-handling-transactions.html
In such cases, I've found out the tricky part is to make sure you commit the transaction only if all inserts succeed, but rollback all updates if any insert fails. I've used a savepoint stack to handle such situations. The highly simplified code is as follows:
A connection wrapper class:
public class MyConnection {
Connection conn;
static DataSource ds;
Stack<Savepoint> savePoints = null;
static {
//... stuff to initialize datasource.
}
public MyConnection() {
conn = ds.getConnection();
}
public void beginTransaction() {
if (savePoints == null) {
savePoints = new Stack<Savepoint>();
conn.setAutoCommit(false);
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
} else {
savePoints.push(conn.setSavepoint());
}
}
public void commit() throws SQLException {
if (savePoints == null || savePoints.empty()) {
conn.commit();
} else {
Savepoint sp = savePoints.pop();
conn.releaseSavepoint(sp);
}
}
public void rollback() throws SQLException {
if (savePoints == null || savePoints.empty()) {
conn.rollback();
} else {
Savepoint sp = savePoints.pop();
conn.rollback(sp);
}
}
public void releaseConnection() {
conn.close();
}
}
Then you can have various methods that may be called independently or in combination. In the example below, methodA may be called on its own, or as a result of calling methodB.
public class AccessDb {
public void methodA(MyConnection myConn) throws Exception {
myConn.beginTransaction();
try {
// update table A
// update table B
myConn.commit();
} catch (Exception e) {
myConn.rollback();
throw e;
} finally {
}
}
public void methodB(MyConnection myConn) throws Exception {
myConn.beginTransaction();
try {
methodA(myConn);
// update table C
myConn.commit();
} catch (Exception e) {
myConn.rollback();
throw e;
} finally {
}
}
}
This way, if anything goes wrong, it rolls back fully (as a result of the exception handling), but it will only commit the entire transaction instead of committing a partially completed transaction.