I am trying to open Hibernate Session on each request and close it at the end.
This seems like it could work, but I have now idea how am I supposed to put my Session object in ThreadLocal and answer does not explain that.
Is there any Struts2 specific way to do this?
You can add a HttpFilter that is in front of the Struts2 Servlet. In the filter:
public class SessionProviderFilter implements Filter {
private static ThreadLocal<Session> sessionStore = new ThreadLocal<Session>();
public void doFilter(...) {
Session session = ... // get the session
sessionStore.set(session);
chain.doFilter(...);
sessionStore.set(null);
}
public static Session getCurrentSession() {
return sessionStore.get();
}
}
Now from any code, to get current hibernate session, you call SessionProviderFilter.getCurrentSession().
Related
I have a Jakarta EE8 ( CDI 2 / Weld / JSF 2.3 / Wildfly ) app where I need to execute some cleanup code when the user logs out, manual logout are fine however I now need to fire an event when logout happens automatically due to session timeout, to handle this I have tried the following two methods already...
#Named
#SessionScoped
public class HttpSessionObservers implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject private Logger log;
#Inject SecurityContext securityContext;
#PreDestroy
public void processSessionScopedDestroying() {
log.debug("Http Session predestroy");
Principal principal = securityContext.getCallerPrincipal(); //<----is null
//...do some cleanup/logging operations on user account
}
}
The above #PreDestroy callback fires when the session times out but the logged in user (principal) is always null so it seems they have already been logged out so I cannot obtain the user.
#Named
public class HttpSessionObservers implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Inject private Logger log;
#Inject private Event<UserLogoutEvent> userLogoutEvent;
public void processSessionScopedInit(#Observes #Initialized(SessionScoped.class) HttpSession payload) {
log.debug("Http Session initialised");
}
public void processSessionScopedDestroying(#Observes #BeforeDestroyed(SessionScoped.class) HttpSession payload) {
//Never fires
log.debug("Http Session predestroy");
Principal principal = securityContext.getCallerPrincipal();
//...do some cleanup/logging operations on user account
}
public void processSessionScopedDestroyed(#Observes #Destroyed(SessionScoped.class) HttpSession payload) {
log.debug("Http Session destroyed");
}
}
Here, the #BeforeDestroyed event is never fired, cannot seem to find any examples of this working, it does work ok for other scopes.
Implement your own HttpSessionListener and annotate it with #WebListener The container should recognize this and will call the the sessionDestroyed() method in your implementation. Your class is eligible for CDI Injection so in sessionDestroyed(), you can fire an event and listen for it with a CDI Observer.
Ref: https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpSessionListener.html
EDIT:
Try this then: Scope your HttpSessionListener impl to #ApplicationScoped. Have a synchronized Map of sessionId/username. In sessionCreated() insert into the map, and on sessionDestroyed() remove it in a finally block.
The container will also have several implicit HttpSessionListeners, so there's a chance yours is being called last long after the session is invalidated. If thats the case, there's another technique you can use to insert your HttpSessionListener impl first. Try the above technique first then we can dive into this.
I am facing a problem due which is unknown to me, can you one have faced this problem?
JSON mapping problem: <package>ApiResponse["data"]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: possible non-threadsafe access to the session (through reference chain: <package>.ApiResponse["data"])
I have a standard API response pojo. Which I return every time with ResponseEntity. Everything is working fine, but sometimes I got that above error. I don't why this error occurred .
I got the below log from console
an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: possible non-threadsafe access to the session
org.hibernate.AssertionFailure: possible non-threadsafe access to the session
I think you are trying to share same Hibernate session within multiple threads. That's illegal.
Hibernate Sessions are not thread-safe whereas Hibernate SessionFactory is thread-safe.
So, make a separate DAO layer. Create single sessionfactory object and share it among the DAO classes.
Get a session for a single-threaded DB operation and close the session in that thread.
For example :
#Repository
public class DAO {
#Autowired
private SessionFactory sessionFactory;
public class performDBOperation(Object obj) {
Session session = sessionFactory.currentSession();
session.save(obj);
session.close();
}
}
Now, I have looked at your github code.
I saw the code Exec.java
#Service
public interface Exec {
#Async
#Transactional
public void run();
}
This is incorrect.
Updated :
public interface Exec {
public void run();
}
Update ExecImpl to this :
#Service
public class ExecImpl implements Exec {
#Autowired
private ExecDAO execDAO;
#Override
#Async
#Transactional
public void run() {
// example : create an object to save it.
Object object = ...;
execDAO.saveItem(object);
}
}
Create DAO layer :
Suppose ExecDAO interface and implementation ExecDAOImpl :
public interface ExecDAO {
public void saveItem(Object obj);
// keep here abstract method to perform DB operation
}
#Repository
public class ExecDAOImpl implements ExecDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
public void saveItem(Object obj) {
Session session = sessionFactory.currentSession();
session.save(obj);
session.close();
}
}
Looking at the code at the link you shared in the comment, I think that
#Async
#Transactional
is a dangerous thing.
I would suggest you to extract a method to do the transactions and try
what I mean is that,
interface ExecImpl{
#Async
void run(){
someThingElse.doTransaction();
}
}
interface SomeThingElse{
#Transactional
void doTransaction();
}
I am still not convinced this will help you. But this is something you can try.
I would also suggest to use readonly transactions for getting data and not have a single transaction for all purposes.
This blog explains why its not good to use these two annotations together whether on a class or on an interface
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.
I have now 2 tables in database:
User
user_database
In user I store login, password,role
In user_database i store database driver,url,password and user.
Diagram database
I want user login to my page and next connection what he done will be sent to user database. Why i need what? I planing map popular e commerce and create android application where user login and see store data, can add and view product orders.
Now time to go practice, my knowledge in spring technology is small please explain me something when I doing wrong.
All examples on web for AbstractRoutingDataSource have declaration datasource in persistence file or create datasource bean and start using AbstractRoutingDataSource.
In my project I don't now user connection and i need get this from database. I was try get using repository and this example
https://stackoverflow.com/a/17575648/3037869
but i getting null on #Autowired in controller, i think connection for repository is null. How to set connection for this repository and set Route? Defect this method is when i add user i need restart server to refresh connection.
Next try what i using now is class User implement UserDetails after user login i can get user connection from getPrincipal() and add to map.
private void setDataSources() {
HashMap<Object, Object> targetDataSources = new HashMap<>();
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
targetDataSources.put("auth", dataSourceBuilder.build());
setDefaultTargetDataSource(dataSourceBuilder.build());
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
User user=(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(user.getUserDatabase().getDriver());
dataSourceBuilder.driverClassName(user.getUserDatabase().getDriver());
dataSourceBuilder.url(user.getUserDatabase().getUrl());
dataSourceBuilder.username("3450_Menadzer");
dataSourceBuilder.password(user.getUserDatabase().getPassword());
targetDataSources.put("user", dataSourceBuilder.build());
}
setTargetDataSources(targetDataSources);
afterPropertiesSet(); //map is refresh when i add this
}
I run this method on constuctor and determineCurrentLookupKey
protected Object determineCurrentLookupKey() {
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
setDataSources();
return "user";
}
return "auth";
}
This is working but when i refresh 3-4 times request for user database i getting
User 3450_Menadzer already has more than 'max_user_connections' active connections
Setting connection map manualy and not refreshing every method determineCurrentLookupKey run i don't have this problem. I think my method is not clossing connection. How i can clean this? This is possible to better method to route connection?
EDIT
#SergeBallesta i change some code from your examples
This is my class for map
#Component
#Scope(value = "singleton")
public class DataSourceMap {
private Map<Object,Object> dataSourceMap;
public DataSourceMap()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
dataSourceMap=new HashMap<Object,Object>();
dataSourceMap.put("auth",dataSourceBuilder.build());
}
public void addDataSource(String session,DataSource dataSource)
{
this.dataSourceMap.put(session,dataSource);
}
public Map<Object,Object> getDataSourceMap()
{
return dataSourceMap;
}
public void removeSource(String session)
{
dataSourceMap.remove(session);
}
}
For AbstractRoutingDataSource i done some changes, i was add afterPropertiesSet() beacuse datasource not refresh. I done some refresh and i not getting error i think this is working. I need test this for more databases in future
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource{
#Autowired
DataSourceMap dataSources;
#Override
protected Object determineCurrentLookupKey() {
setDataSources(dataSources);
afterPropertiesSet();
System.out.println("test");
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession().getId();
}
return "auth";
}
#Autowired
public void setDataSources(DataSourceMap dataSources) {
System.out.println("data adding");
setTargetDataSources(dataSources.getDataSourceMap());
}
}
First, per user database is a very uncommon design. If all those databases will end with same structure, please do not do that in a real world application, but just add user_id in your tables and queries.
Next, I found another (not full) example of a dynamic AbstractRoutingDataSource in another answer of mine.
And one big difference between my code (beware never tested) and your question is that I use a SessionListener to close the databases to avoid that the number of open database grows indefinitively.
If you to this to learn Spring, you could try the following pattern (bottom-up description) :
a session scoped bean that would hold the actual database connection for a user, the connection should be created on first request (to be sure that user id is present in session) and cached for subsequent uses. A destroy method (automaticaly called by Spring when session is closed) should close the connection.
an AbstractRoutingDataSource, that would be injected with a proxy to above holder, and that would ask actual datasource to the holder
As in the other answer, if same user is likely to have many simultaneous sessions, you could have a singleton been injected in session holders that would keep the actual database connections along with the number of active sessions. That way you would get one single connection per user, no matter how many concurrent sessions he could have.
I'm trying to get my head around how Hibernate session management works. I'm trying to define a simple session-per-request model in a web application, but it just doesn't seem to be working. So far I have this:
#WebListener
public class HibernateDataAccess implements ServletRequestListener
{
Configuration configuration;
SessionFactory sessionFactory;
public HibernateDataAccess ()
{
configuration = new Configuration ().configure ();
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder ().applySettings (
configuration.getProperties ()).build ();
sessionFactory = configuration.buildSessionFactory (serviceRegistry);
}
public List<Customer> getAllCustomers () throws SQLException
{
Session session = sessionFactory.getCurrentSession ();
return (List<Customer>) session.createQuery ("select c from Customer c").list ();
}
#Override
public void requestDestroyed (ServletRequestEvent arg0)
{
ManagedSessionContext.unbind (sessionFactory).close ();
}
#Override
public void requestInitialized (ServletRequestEvent arg0)
{
System.out.println ("requestInitialized called!");
Session session = sessionFactory.openSession ();
ManagedSessionContext.bind (session);
session.beginTransaction ();
}
}
My problem is that despite the fact that I get the message "requestInitialized called!" on my server's console output, I get the following exception when I try to call getAllCustomers from within a servlet request (inside a doGet() method):
org.hibernate.HibernateException: No session currently bound to execution context
at org.hibernate.context.internal.ManagedSessionContext.currentSession(ManagedSessionContext.java:75)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1013)
at net.meridiandigital.binco.web.HibernateDataAccess.getAllCustomers(HibernateDataAccess.java:38)
at net.meridiandigital.binco.web.CustomerServlet.doGetList(CustomerServlet.java:80)
What am I doing wrong?
Have you set the hibernate.current_session_context_class configuration parameter to a Hibernate.Context.ICurrentSessionContext implementation?
The problem was quite obvious -- the instance of HibernateDataAccess I was using wasn't the same on the server was calling the listener methods on, so the session factory that was having the current session registered was a different one to the one my code was trying to use. The solutions was to separate the two concerns: have an entirely separate #WebListener class that called to my singleton HibernateDataAccess to start and close sessions.