I need to set a mysql session variable for my application to work with a MariaDB Galera Cluster as expected. The SQL call is: SET SESSION wsrep_sync_wait = 1. It shall be set at all times when the application uses the database. I am using EclipseLink as the JPA provider.
My question is: What is the best way to achieve this?
Option 1: EclipseLink Session Customizer
Register a session customizer in persistence.xml:
public class SessionCustomizerImpl implements org.eclipse.persistence.config.SessionCustomizer {
private final static String WSREP_SYNC_WAIT_CHECK_SQL = "SHOW SESSION VARIABLES LIKE 'wsrep_sync_wait'";
private final static String WSREP_SYNC_WAIT_SET_SQL = "SET SESSION wsrep_sync_wait = 1";
#Override
public void customize(Session session) throws Exception {
Vector result = session.executeSQL(WSREP_SYNC_WAIT_CHECK_SQL);
if ((result != null) && !result.isEmpty()) {
session.executeNonSelectingSQL(WSREP_SYNC_WAIT_SET_SQL);
// Galera connection detected; wsrep_sync_wait set to 1
} else {
// No Galera connection detected; wsrep_sync_wait not set
}
}
}
This does not work for me. Querying the session variable from an EntityManager returns a value of 0.
Option 2: EntityManager factory
Every time a new EntityManager is created, the SQL is executed.
public class SyncWaitEntityManagerFactory implements Factory<EntityManager> {
private final EntityManagerFactory emf;
#Inject
public SyncWaitEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
#Override
public EntityManager provide() {
final EntityManager em = emf.createEntityManager();
// set it
em.getTransaction().begin();
em.createNativeQuery("SET SESSION wsrep_sync_wait = 1").executeUpdate();
em.getTransaction().commit();
return em;
}
#Override
public void dispose(EntityManager instance) {
if (instance.isOpen()) {
instance.close();
}
}
}
This works, but I'm not sure if it is overkill. Also, I am worried about the cost of the transaction, which is only required by Query#executeUpdate(), but not by the actual SQL call.
Option 3: Via JDBC URL
Appending the variable and value to the JDBC URL (see here for details):
String jdbcUrl = "jdbc:mysql://db.example.test:3306/"+ JDBC_DB
+"?sessionVariables=wsrep_sync_wait=1";
Properties p = new Properties();
p.put("javax.persistence.jdbc.url", jdbcUrl);
p.put("javax.persistence.jdbc.user", JDBC_USER);
p.put("javax.persistence.jdbc.password", JDBC_PASSWORD);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU", p);
EntityManager entityManager = emf.createEntityManager();
Nice solution. Works for me; no effort, no transaction necessary. Downside: I can't catch exceptions (example: check first if the variable exists, then set it -- allows deployment of the code on systems that don't support/use this specific variable).
You could also use an aspect to execute a query every time a getConnection() is called which is for every transaction basically (the aspect is set after the call so that we have a valid connection object):
#Component
#Aspect
public class CustomConnectionPreparer implements ConnectionPreparer
{
#AfterReturning(pointcut = "execution(* *.getConnection(..))", returning = "connection")
public Connection prepare(Connection connection) throws SQLException {
// execute the query (also exception handling)
try (Statement statement = connection.createStatement()) {
statement.execute("SET SESSION wsrep_sync_wait = 1");
} catch (SQLException e) {
throw e;
}
return connection;
}
}
And before you return the connection to the caller you execute your query and you should always have that value set.
Related
I have a ProcessRecon usecase class with a single method named execute. It saves an entity Reconciliation using paymentRepository.saveRecon and calls a web service as part of acknowledgement using paymentRepository.sendReconAck.
Now there's a chance that this external web service might fail in which case I want to rollback the changes i.e. the saved entity. Since I am using Unirest, it throws UnirestException which is a checked exception.
There are no errors on the console but this will probably be helpful [UPDATED].
2020-08-20 17:21:42,035 DEBUG [http-nio-7012-exec-6] org.springframework.transaction.support.AbstractPlatformTransactionManager: Creating new transaction with name [com.eyantra.payment.features.payment.domain.usecases.ProcessRecon.execute]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-com.mashape.unirest.http.exceptions.UnirestException
...
2020-08-20 17:21:44,041 DEBUG [http-nio-7012-exec-2] org.springframework.transaction.support.AbstractPlatformTransactionManager: Initiating transaction rollback
2020-08-20 17:21:44,044 DEBUG [http-nio-7012-exec-2] org.springframework.orm.jpa.JpaTransactionManager: Rolling back JPA transaction on EntityManager [SessionImpl(621663440<open>)]
2020-08-20 17:21:44,059 DEBUG [http-nio-7012-exec-2] org.springframework.orm.jpa.JpaTransactionManager: Not closing pre-bound JPA EntityManager after transaction
2020-08-20 17:22:40,020 DEBUG [http-nio-7012-exec-2] org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor: Closing JPA EntityManager in OpenEntityManagerInViewInterceptor
What I see at the moment is that entity gets pushed to database even if there's a UnirestException. But I expect no data be saved to database.
I am using Spring Boot 2.3.3 with MySQL 5.7. This is the code I have for it.
ProcessRecon.java
#Usecase // this custom annotation is derived from #service
public class ProcessRecon {
private final PaymentRepository paymentRepository;
#Autowired
public ProcessRecon(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
#Transactional(rollbackFor = UnirestException.class)
public Reconciliation execute(final Reconciliation reconciliation) throws UnirestException {
PaymentDetails paymentDetails = paymentRepository.getByReqId(reconciliation.getReqId());
if (paymentDetails == null)
throw new EntityNotFoundException(ExceptionMessages.PAYMENT_DETAILS_NOT_FOUND);
reconciliation.setPaymentDetails(paymentDetails);
Long transId = null;
if (paymentDetails.getImmediateResponse() != null)
transId = paymentDetails.getImmediateResponse().getTransId();
if (transId != null)
reconciliation.setTransId(transId);
if (reconciliation.getTransId() == null)
throw new ValidationException("transId should be provided in Reconciliation if there is no immediate" +
" response for a particular reqId!");
// THIS GETS SAVED
Reconciliation savedRecon = paymentRepository.saveRecon(reconciliation);
paymentDetails.setReconciliation(savedRecon);
// IF THROWS SOME ERROR, ROLLBACK
paymentRepository.sendReconAck(reconciliation);
return savedRecon;
}
}
PaymentRepositoryImpl.java
#CleanRepository
public class PaymentRepositoryImpl implements PaymentRepository {
#Override
public String sendReconAck(final Reconciliation recon) throws UnirestException {
// Acknowledge OP
return sendAck(recon.getRequestType(), recon.getTransId());
}
String sendAck(final String requestType, final Long transId) throws UnirestException {
// TODO: Check if restTemplate can work with characters (requestType)
final Map<String, Object> queryParams = new HashMap<String, Object>();
queryParams.put("transId", transId);
queryParams.put("requestType", requestType);
logger.debug("{}", queryParams);
final HttpResponse<String> result = Unirest.get(makeAckUrl()).queryString(queryParams).asString();
logger.debug("Output of ack with queryParams {} is {}", queryParams, result.getBody());
return result.getBody();
}
#Override
public Reconciliation saveRecon(final Reconciliation recon) {
try {
return reconDS.save(recon);
}
catch (DataIntegrityViolationException ex) {
throw new EntityExistsException(ExceptionMessages.CONSTRAINT_VIOLATION);
}
}
}
ReconciliationDatasource.java
#Datasource // extends from #Repository
public interface ReconciliationDatasource extends JpaRepository<Reconciliation, Long> {
List<Reconciliation> findByPaymentDetails_User_Id(Long userId);
}
To make annotations work you have to use interfaces instead of classes for dependency injection.
interface ProcessRecon {
Reconciliation execute(final Reconciliation reconciliation)
throws UnirestException;
}
Then
#Usecase
public class ProcessReconImpl implements ProcessRecon {
private final PaymentRepository paymentRepository;
#Autowired
public ProcessReconImpl(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}
#Transactional(rollbackFor = UnirestException.class)
public Reconciliation execute(final Reconciliation reconciliation) throws UnirestException {
//method implementation...
}
}
Usage
#Autowired
ProcessRecon processRecon;
public void executeServiceMethod(Reconciliation reconciliation) {
processRecon.execute(reconciliation)
}
This way you have got proxy of ProcessReconImpl with provided by annotations additional functionality.
I assumed the default engine for the tables would be InnoDB but to my surpise, the tables were created using MyISAM engine which doesn't support transactions.
I resolved the problem by using the below property as suggested here
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
instead of
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
That was the only change required. Thanks!
I am facing some issue with transactions configured in my code.
Below is the code with transactions which writes data to DB.
Writer.java
class Writer {
#Inject
private SomeDAO someDAO;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void write(){
this.batchWrite();
}
private void batchWrite () {
try {
someDAO.writeToTable1(true);
} catch(Exception ex) {
someDAO.writeToTable1(false);
}
someDAO.writeToTable2();
}
}
SomeDAO.java
class SomeDAO {
#Inject
private JdbcTemplate JdbcTemplate;
public void writeToTable1(boolean flag) {
// Writes data to table 1 using jdbcTemplate
jdbcTemplate.update();
}
pulic void writeToTable2() {
// Writes data to table 2 using jdbcTemplate
jdbcTemplate.update();
}
}
Here data is getting stored into table 1 properly but sometimes, table 2 is getting skipped.
I am not sure how this is happening as both the tables have been written within same transaction.
Either transaction is partially committing the data or partially rolling back.
I have doubt that in the SomeDAO class I am injecting JdbcTemplate object which is creating new connection instead of using existing connection of transaction.
Can anyone please help me here?
Try binding a Transaction Manager bean having your jdbcTemplate inside #Transactional:
//Create a bean
#Bean
public PlatformTransactionManager txnManager() throws Exception {
return new DataSourceTransactionManager(jdbcTemplate().getDataSource());
}
And then use this transaction manager in #Transactional("txnManager").
I imagine that this is a common problem, but after some searching I wasn't able to find anything relevant.
The problem I'm having is that I'm getting a No Hibernate Session bound to thread exception when annotating my resource method with #UnitOfWork and inside my resource method, making an asynchronous DAO call. The idea behind this design is to make the database call on a separate I/O thread so that it frees up the Jersey resource thread.
Unfortunately, as the exception says, this RxIoScheduler-2 thread doesn't have a hibernate session bound to it.
Any suggestions?
Hibernate Session is not thread safe, so we need a strategy how to get the current session for the current thread. Such strategy is called CurrentSessionContext.
The current session is a session which we get by this call:
sessionFactory.getCurrentSession()
Hibernate can be configured with various current session strategies. #UnitOfWork uses this strategy:
hibernate.current_session_context_class = managed
For this strategy you should put a session to the context by an explicit call of the
ManagedSessionContext.bind(session)
So, as we know a Session is not thread safe, you should create a new session for a separate thread and put that session in the ManagedSessionContext. After that you can call your DAO by the same way as in the endpoint methods with #UnitOfWork.
Keep in mind that you should unbind the session before closing it with
ManagedSessionContext.unbind(factory)
You can use this utility class to create a session for a separate thread:
public final class HibernateSessionUtils {
private HibernateSessionUtils() {
}
public static void request(SessionFactory factory, Runnable request) {
request(factory, () -> {
request.run();
return null;
});
}
public static <T> T request(SessionFactory factory, Supplier<T> request) {
Transaction txn = null;
Session session = factory.openSession();
try {
ManagedSessionContext.bind(session);
txn = session.beginTransaction();
T result = request.get();
commit(txn);
return result;
} catch (Throwable th) {
rollback(txn);
throw Throwables.propagate(th);
} finally {
session.close();
ManagedSessionContext.unbind(factory);
}
}
private static void rollback(Transaction txn) {
if (txn != null && txn.isActive()) {
txn.rollback();
}
}
private static void commit(Transaction txn) {
if (txn != null && txn.isActive()) {
txn.commit();
}
}
}
Throwables from guava.
It can be used by this way
List<Campaign> getCampaigns(SessionFactory factory, CampaignDao dao) {
return HibernateSessionUtils.request(
factory,
dao::getCampaigns
);
}
In the dao.getCampaigns() method you can get the session
sessionFactory.getCurrentSession()
You can inject the factory everywhere using Guice.
Other option is to use UnitOfWorkAwareProxyFactory.
In a Stateless Session Bean an EntityManager is injected but I would like to get hold of a Connection object in order to invoke a DB Procedure.
Is there any solution to this ?
This is going to be JPA provider specific code. Typically this is done by invoking unwrap() on the EntityManager class.
If you are using EclipseLink, the following code (from the EclipseLink wiki) will be useful (in the case you are using an application-managed EntityManager) :
JPA 2.0
entityManager.getTransaction().begin();
java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class); // unwraps the Connection class.
...
entityManager.getTransaction().commit();
JPA 1.0
entityManager.getTransaction().begin();
UnitOfWork unitOfWork = (UnitOfWork)((JpaEntityManager)entityManager.getDelegate()).getActiveSession();
unitOfWork.beginEarlyTransaction();
Accessor accessor = unitOfWork.getAccessor();
accessor.incrementCallCount(unitOfWork.getParent());
accessor.decrementCallCount();
java.sql.Connection connection = accessor.getConnection();
...
entityManager.getTransaction().commit();
Note, that the solution provided for JPA 2.0 will fail for Hibernate 3.6.5 with a PersistenceException containing the message
Hibernate cannot unwrap interface java.sql.Connection
Use the code provided by Skaffman to get it to work against Hibernate (verified to work under 3.6.5 even for container managed persistence contexts).
However, the EclipseLink wiki points out one useful bit of info - if you are using JTA managed datasources, you should be injecting it using the #Resource annotation or retrieving it using a JNDI lookup. As long as you need to perform transactional work against the database, it is immaterial as to whether you are obtaining a new connection from the data source or an existing one; most connection pools will anyway provide the same connection that is associated with the current thread (i.e. the one already used by the entity manager). You would therefore avoiding unwrapping the entity manager this way, and also perform transactional activity against the database; do remember that the persistence context cache, and a second-level cache may not be synchronized if you do this.
In Hibernate, the solution posted by skaffman resulted in the following error message:
Hibernate cannot unwrap class org.hsqldb.Session
I did get it to work using SessionImpl rather than Session:
Connection connection = entityManager().unwrap(SessionImpl.class).connection();
An example of solving the problem using Session.doWork() is as follows:
private void executeNative(final String query) {
Session session = entityManager.unwrap(Session.class);
session.doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
Statement s = null;
try {
s = connection.createStatement();
s.executeUpdate(query);
}
finally {
if (s != null) {
s.close();
}
}
}
});
}
The JPA API itself doesn't seem to offer this, not surprisingly, but if you're willing to couple your code to a specific implementation, then you can use something like this (Hibernate):
Session hibernateSession = entityManager.unwrap(Session.class);
Connection jdbcConnection = hibernateSession.connection();
Note that Session.connection() is deprecated for removal in Hibernate 4. Consider using Session.doWork() instead.
You must take the underlying delegate using entitymanager.getDelegate() or entitymanager.unwrap(which is the better way), cast it to the specific implementation(in Hibernate it is called Session). Then you can call the connection() method. Be aware this is deprecated, use the Work class instead. Read more here.
In JPA2.0, if need JDBC is por DTO nomodel or entity for query more
complex. Sometimes JPA is not all...
I hope this will help you:
Statement statement = null;
EntityManager em = null;
em = emf.createEntityManager();
EntityTransaction et = em.getTransaction();
if(!et.isActive()) {
et.begin();
}
java.sql.Connection connection = em.unwrap(java.sql.Connection.class);
String qquerry="SELE ...
try {
statement = connection.createStatement();
ResultSet rs = statement.executeQuery(qquerry);
if (!rs.next()) {
return null;
}
else{
wwwwas=rs.getString(4);
}
statement.close();
}
catch (SQLException e) {
System.out.println("\n b-03:"+e);
throw new RuntimeException(e.getMessage(), e);
}
finally {
try {
// em.getTransaction().commit();
if(connection != null )
connection.close();
}
catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
This works awesomely and you can use the connection object elsewhere if needed
SessionImpl sessionImpl = (SessionImpl) session;
Connection conn = sessionImpl.connection();
Where session is the name of the Hibernate Session object
Below is the code that worked for me. We use jpa 1.0, Apache openjpa implementation.
import java.sql.Connection;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAPersistence;
public final class MsSqlDaoFactory {
public static final Connection getConnection(final EntityManager entityManager) {
OpenJPAEntityManager openJPAEntityManager = OpenJPAPersistence.cast(entityManager);
Connection connection = (Connection) openJPAEntityManager.getConnection();
return connection;
}
}
I'm working on a webapp and I have connection errors after Hibernate throws exceptions :
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
It gave me this exception each time I try to access my db after an exception occured.
I now Hibernate's not supposed to throw errors if my application is well coded but if something happens with the connection to the db, I don't want my application to be stuck with this error.
Here's my HibernateUtil class :
public class HibernateUtil {
private static Logger log = Logger.getLogger(HibernateUtil.class);
private static org.hibernate.SessionFactory sessionFactory;
private static String confFile = "hibernate-test.properties";
private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();
private HibernateUtil() {
}
public static void buildSessionFactory(){
Configuration configuration = new Configuration();
synchronized(HibernateUtil.class){
if(sessionFactory == null){
try {
Properties properties = new Properties();
properties.load(HibernateUtil.class.getClassLoader().getResourceAsStream(confFile));
configuration.setProperties(properties);
} catch (Exception e) {
log.fatal("cannot load the specified hibernate properties file: " + confFile);
throw new RuntimeException("cannot load the specified hibernate properties file : " + confFile, e);
}
sessionFactory = configuration.configure().buildSessionFactory();
}
HibernatePBEEncryptorRegistry registry = HibernatePBEEncryptorRegistry.getInstance();
if(registry.getPBEStringEncryptor("strongHibernateStringEncryptor") == null) {
StandardPBEStringEncryptor strongEncryptor = new StandardPBEStringEncryptor();
strongEncryptor.setAlgorithm("PBEWithMD5AndDES"); // not really needed as it is the default
strongEncryptor.setPassword("aStrongPassword");
registry.registerPBEStringEncryptor("strongHibernateStringEncryptor", strongEncryptor);
}
}
}
public static SessionFactory getSessionFactory() {
if(sessionFactory == null){
buildSessionFactory();
}
return sessionFactory;
}
public static Session getCurrentSession(){
if(!getSessionFactory().getCurrentSession().isOpen())
getSessionFactory().openSession();
return getSessionFactory().getCurrentSession();
}
}
Here's my BaseAction class where initialization and closing of sessions is set :
public class BaseAction extends ActionSupport {
public Session hib_session;
public void initHibSession() {
hib_session = HibernateUtil.getCurrentSession();
hib_session.beginTransaction();
hib_session.clear();
}
public void closeHibSession() {
hib_session.getTransaction().commit();
}
}
Here's an example of an action:
Transaction transaction = new Transaction(user, Transaction.Type.REGISTRATION, new HashSet(domains));
initHibSession();
hib_session.save(transaction);
closeHibSession();
transaction_id = transaction.getId();
Is there a way to avoid the exception above ?
It gave me this exception each time I try to access my db after an exception occurred.
I'm not sure to understand the exact condition. Anyway, after an exception, you should rollback the transaction, close the session and start over. That being said, I have some remarks about your code.
About your HibernateUtil:
why do you have a ThreadLocal, the Session#getCurrentSession() method handle that for you (you don't seem to use the thread local though).
in HibernateUtil.getCurrentSession(), why do you mess with getCurrentSession() and openSession()? Firstly, there is no need to do what you do, getCurrentSession() will return a new session if no session is associated to the current thread. Secondly, both approaches are different and have different semantics (you need to close the session yourself when using openSession()), you should use one or the other.
About your BaseAction:
I wonder why you clear() the session after Session#beginTransaction(). In case you didn't committed an ongoing transaction, you'll loose all the pending changes. Is this really what you want?
PS: I would consider using the Open Session in View pattern to remove all this burden from your code.
Resources
Sessions and transactions
Open Session in View