I'm performing a transaction in JPA (EclipseLink) using a custom transaction isolation level, which I set on the underlying connection of the JPA EntityManager using this code:
// begin transaction
entityManager.getTransaction().begin();
// store the old isolation level
int isolationLevelOld = entityManager.unwrap(Connection.class).getTransactionIsolation();
// set the desired isolation level for this transaction
entityManager.unwrap(Connection.class).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
[...Queries...]
// commit transaction
entityManager.getTransaction().commit();
// reset isolation level to the old value (throws NullPointerException)
entityManager.unwrap(Connection.class).setTransactionIsolation(isolationLevelOld);
If I try to reset the isolation level to the old value after having committed the transaction, the underlying connection is null (entityManager.unwrap(Connection.class) returns null). I'm worried, if I just don't reset the isolation level, a connection with a bad isolation level gets leaked back to the pool.
What is the correct way of cleaning up after changing the isolation level? Should I maybe do it before calling commit()?
The java.sql.Connection gets returned to the pool in the call to entityManager.getTransaction().commit(); So resetting the isolation level afterwards is not possible and prevented by EclipseLink by returning a null connection.
Maintaining a reference to the Connection to circumvent this will likely leak a connection with altered settings, so I cannot accept your answer RomanC
I ended up creating two instances of EntityManagerFactory. One that creates default EntityManagers and one that creates EntityManagers with Connections with my desired transaction level using a SessionCustomizer:
public static class SessionCustomizer implements org.eclipse.persistence.config.SessionCustomizer {
#Override
public void customize(Session session) throws Exception {
DatabaseLogin databaseLogin = (DatabaseLogin) session.getDatasourceLogin();
databaseLogin.setTransactionIsolation(DatabaseLogin.TRANSACTION_SERIALIZABLE);
}
}
private void init() {
entityManagerFactoryRegular = Persistence.createEntityManagerFactory("MyPersitenceRegular");
Map<String, String> props = new HashMap<>();
props.put(PersistenceUnitProperties.SESSION_CUSTOMIZER, SessionCustomizer.class.getName());
entityManagerFactoryTransactionSerializable = Persistence.createEntityManagerFactory("MyPersitenceTransactionSerializable", props);
}
See here Set Isolation level in eclipselink
I then use the EntityManagerFactory that provides whichever connection type I need. Caveat: Transactions cannot span EntityManagers from multiple EntityManagerFactories
Try the following code
Connection conn = entityManager.unwrap(Connection.class);
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
[...Queries...]
// commit transaction
entityManager.getTransaction().commit();
conn.setTransactionIsolation(isolationLevelOld);
Related
I'm trying to set a transaction isolation level for the connection associated with a given entity manager (TRANSACTION_SERIALIZABLE etc.).
I have scoured the internet for a solution and found a few. However, none of them seem to work.
When I try to do the following :
EntityManagerFactory emfactory = Persistence.createEntityManagerFactory("BankingPU");
public EntityManager em = emfactory.createEntityManager();
java.sql.Connection connection = (java.sql.Connection) em.getDelegate();
connection.setTransactionIsolation(TRANSACTION_SERIALIZABLE);
I get the following exception:
Exception in thread "main" java.lang.ClassCastException:
org.eclipse.persistence.internal.jpa.EntityManagerImpl cannot be cast to java.sql.Connection
When I do
EntityManagerFactory emfactory = Persistence.createEntityManagerFactory("BankingPU");
public EntityManager em = emfactory.createEntityManager();
java.sql.Connection connection = em.unwrap(java.sql.Connection.class);
The value stored to connection is null.
I ran the following in order to find out more:
Object obj = em.getDelegate();
The class type stored in obj is org.eclipse.persistence.internal.jpa.EntityManagerImpl
Edit: (oops, guess I could have told that from the exception)
Edit2:
I have managed to obtain the session (i think) by calling :
Session session = ((EntityManagerImpl) em).getSession();
However, neither the connection(), nor the DoWork() methods are present in it.
Any idea how to follow up on this and set the transaction isolation level?
JPA API does not have transaction (TX) isolation level management API. So it is not possible do it via entity manager.
If you want to use the same entity manager than you need, as it already mentioned in Neil Stockton comment, your JPA provider specific code to get JDBC access for changing TX isolation level.
Adam Bien almost answered on your question at his blog post.
Isolation Levels can be set on java.sql.Connection level. In a Java EE environment isolation levels can be configured on Data Source / Connection Pool (see e.g. Glassfish).
As workaround, if you want use only JPA API, you may create in your application server the several data sources for the same database with required isolation level.
However you need to understand that you will have the several 1st level caches (in different entity managers) and you should manage your entities state in appropriate way.
I have found the solution:
emfactory = Persistence.createEntityManagerFactory("BankingPU");
em = emfactory.createEntityManager();
Session session = ((EntityManagerImpl) em).getSession();
DatabaseLogin databaseLogin = (DatabaseLogin) session.getDatasourceLogin();
databaseLogin.setTransactionIsolation(DatabaseLogin.TRANSACTION_SERIALIZABLE);
I am working an a JPA 2.0 project, where I am saving my Entity class objects like :-
InitialContext ctx = new InitialContext();
UserTransaction userTrans = (UserTransaction)
ctx.lookup("java:comp/UserTransaction");
EntityManagerFactory emf = Persistence.createEntityManagerFactory(PERSISTENCE_NAME);
EntityManager em = emf.createEntityManager();
User user = new User("ankit","nigam",25);
em.persist(user); // persisted in db after this executes
userTrans.commit(); // whether it is required OR not.
So whether I am using userTrans.commit() or not, my user object is getting saved in Db, after persist() executes. But some of my colleagues say, as a standard we should commit() the transaction.
What should be the approach which I follow and whats the logic behind commit() and persist(). Please throw some lights.
Is autocommit ON in your DB? If it is then that is the reason why the changes get permanently stored in your DB irrespective of whether or not you commit the transaction from your application. In production the autocommit is generally set OFF because it hampers the performance/response time of the DB, that is why developers are generally encouraged to control the commit or rollback of a transaction from their application. The link details the command to handle autocommit in db2: http://www.db2util.com/administration/options-db2-command-line-disable-autocommit/
I'm using Hibernate transaction for read operation from db as following excample code:
Session session = sessionFactory.openSession();
Transaction tx= session.beginTransaction();
session.get(Account.class, new Long(id));
tx.commit(); //Actually I use tx.rollback()
session.close();
Due to the second level cache, result is fetched from cache after the first time reading from db. But the database connection is distributed when session.beginTransaction() is invoked, and next commit statement will be executed at database, which degrades the performance dramatically(compared to the case when no transaction is used).
So is there any possible way to know that the result will be read from second level cache so I can avoid doing commit and even use a new database connection?
Assuming you are using spring transaction support, have your tried using SUPPORTS propagation level?
#Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
I am using annotation driven transaction management with Spring JDBC.
I would like to have Spring throw an exception when by mistake I forgot to annotate with #Transactional a service method that inserts/updates/deletes.
By default data can be inserted/updated/deleted even not within a transaction.
You can use Propagation.MANDATORY in your DAO layer.
Propagation.MANDATORY will not start a transaction. It will check whether perticular method is attached to a transaction or not, if not container will throw an exception.
According to the documentation (Spring docs) it's just metadata to give an indication that the method or interface can be configured by something that is 'transactionally aware' (ie
With just tx:annotation-driven and no #Transactional attributes I believe you get the "default" transactionality applied:
Propagation setting is REQUIRED.
Isolation level is DEFAULT.
Transaction is read/write.
Transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported.
ny RuntimeException triggers rollback, and any checked Exception does not.
Assuming you're using the tx:annotation to drive it via a transaction manager then missing out the #Transactional attribute means you can't apply such properties as readOnly, isolation, propagation,rollbackFor, noRollbackFor etc.
I believe that MVC is slightly different - the hibernate session is tied directly to the MVC request - ie when the request is received the transaction starts.
Back to your example, the code for getSession() in HibernateDAOSupport is as follows:
protected final Session getSession()
throws DataAccessResourceFailureException, IllegalStateException
{
return getSession(this.hibernateTemplate.isAllowCreate());
}
Which in turn calls to:
/**
* Obtain a Hibernate Session, either from the current transaction or
* a new one. The latter is only allowed if "allowCreate" is true.
*.......
protected final Session getSession()
throws DataAccessResourceFailureException, IllegalStateException {
return getSession(this.hibernateTemplate.isAllowCreate());
}
which ultimately calls to :
/**
* ....
* #param allowCreate whether a non-transactional Session should be created
* when no transactional Session can be found for the current thread
* ....
*/
private static Session doGetSession(
SessionFactory sessionFactory, Interceptor entityInterceptor,
SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate)
Fundamentally, a Transaction:Session is tied 1:1 afaik, and the only way to run without a transaction is by using say JBoss which has a 'baked in' persistence layer which provides the transactionality for you (under the covers). Even if you call getQuery() after getSession() you still effectively have a transaction occuring as it's a JDBC/Hibernate connection.
Can you have multiple transactions within one Hibernate Session?
I'm unclear if this is an allowable desirable. In my code I have a long running thread and takes items from a Blocking Queue, depending on what is on the queue, it may need to create and save a hibernate object, or it may not need to do anything.
Each item is distinct so if item 1 is saved and item 2 fails to save whatever reason I don't want to that to prevent item 1 being added to the database.
So the simplest way to do this is for each item that needs to be created to create a new session, open transaction, save new object, commit transaction, close session
However, that means a new session is created for each item, which seems to go against Hibernates own recommendations to not do Session Per Request Pattern. So my alternative was to create one session in the thread, then just open and commit a new transaction as required when needed to create a new object. But I've seen no examples of this approach and I'm unsure if it actually works.
The session-per-request pattern uses one JDBC connection per session if you run local transactions. For JTA, the connections are aggressively released after each statement only to be reacquired for the next statement.
The Hibernate transaction API delegates the begin/commit/rollback to the JDBC Connection for local transactions and to the associated UserTransaction for JTA. Therefore, you can run multiple transactions on the same Hibernate Session, but there's a catch. Once an exception is thrown you can no longer reuse that Session.
My advice is to divide-and-conquer. Just split all items, construct a Command object for each of those and send them to an ExecutorService#invokeAll. Use the returned List to iterate and call Future#get() to make sure the original thread waits after all batch jobs to complete.
The ExecutorService will make sure you run all Commands concurrently and each Command should use a Service that uses its own #Transaction. Because transactions are thread-bound you will have all batch jobs run in isolation.
Obviously, you can. A hibernate session is more or less a database connection and a cache for database objects. And you can have multiple successive transactions in a single database connection. More, when you use a connection pool, the connection is not closed but is recycled.
Whether you should or not is a matter of reusing objects from session. If there is a good chance but you can reuse objects that a preceding transaction has put in session, you should keep one single session for multiple transactions. But if once an object has been committed, it will never be re-used, it is certainly better to close the session and re-open a new one, or simply clear it.
How to do it :
If you have a Session object, you create transactions with :
Transaction transaction;
transaction = session.beginTransaction();
... (operations in the context of transaction)
transaction.commit();
... (other commands outside of any transaction)
transaction = session.beginTransaction();
... (and so on and so forth ...)
From hibernates documentation
"A Session is an inexpensive, non-threadsafe object that should be used once and then discarded for: a single request, a conversation or a single unit of work. A Session will not obtain a JDBC Connection, or a Datasource, unless it is needed. It will not consume any resources until used."
so if you are creating sessions again and again it will not burden the system much. If you are continuing a session for too long it may create problems as session is not thread safe .In my opinion you simplest solution is the best "So the simplest way to do this is for each item that needs to be created to create a new session, open transaction, save new object, commit transaction, close session"
By the way if you are creating single record of anything you dont need transaction too much. creating single record is inherently " all or none" thing for which we use transaction
package hibernate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
class Tester {
public static void main(String[] args) {
SessionFactory sf = new org.hibernate.cfg.Configuration().configure().buildSessionFactory(new StandardServiceRegistryBuilder().configure().build());
Session session = sf.openSession();
session.beginTransaction();
Student student = new Student();
student.setName("Mr X");
student.setRollNo(13090);
session.save(student);
session.getTransaction().commit();
session.getTransaction().begin();
session.load(Student.class,23);
student.setName("New Name");
student.setRollNo(123);
session.update(student);
session.getTransaction().commit();
session.close();
}
}
Short answer is yes, you can use same session for transaction. Take a look at org.hibernate.Transaction., it has required method to manage transaction.
docs.jboss.org Chapter 13. Transactions and Concurrency
Use a single database transaction to serve the clients request, starting and committing it when you open and close the Session. The relationship between the two is one-to-one and this model is a perfect fit for many applications.
It seemed we should always obey the "one-to-one relationship" rule.
But, although the sample below will trigger a exception in the line where the second "session.beginTransaction()" is called
Exception in thread "main" java.lang.IllegalStateException: Session/EntityManager is closed
private static void saveEmployees(SessionFactory factory) {
// crate session
//Session session = factory.openSession();
Session session = factory.getCurrentSession();
{
// start a transaction
Transaction trans = session.beginTransaction();
// create an employee
Employee tempEmployee = new Employee("Steve","Rogers", "The Avengers");
// save to database
session.save(tempEmployee);
// commit the transaction
trans.commit();
}
{
// start a transaction
Transaction trans = session.beginTransaction();
// create an employee
Employee tempEmployee = new Employee("Tony","Stark", "The Avengers");
// save to database
session.save(tempEmployee);
// commit the transaction
trans.commit();
}
// close session
session.close();
}
, another sample below will work properly.
The only difference is that the second sample uses "factory.openSession()" to get a session, instead of "factory.getCurrentSession()".
private static void saveEmployees(SessionFactory factory) {
// crate session
Session session = factory.openSession();
//Session session = factory.getCurrentSession();
{
// start a transaction
Transaction trans = session.beginTransaction();
// create an employee
Employee tempEmployee = new Employee("Steve","Rogers", "The Avengers");
// save to database
session.save(tempEmployee);
// commit the transaction
trans.commit();
}
{
// start a transaction
Transaction trans = session.beginTransaction();
// create an employee
Employee tempEmployee = new Employee("Tony","Stark", "The Avengers");
// save to database
session.save(tempEmployee);
// commit the transaction
trans.commit();
}
// close session
session.close();
}
I am a starter, and I don't know why "factory.getCurrentSession()" works differently from "factory.openSession()", yet.