How to maintain Hibernate session opened per request? - java

As far as I know session per request means we should use only one hibernate session per servlet request, not one session per transaction.
Consider this helper class:
public class HibernateUtil {
private static SessionFactory sessionFactory;
// this is called by the servlet context listener
public static void buildSessionFactory() {
// build session factory
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
// check if the entity user already exist in the database
public static User getUser(String email) {
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
User user = (User)session.createQuery("from User c where c.emailAddress like :email").setParameter("email", email).uniqueResult();
session.getTransaction().commit();
return user;
}
// if getUser returns null, insert this user in the database
public static void insertUser(User user) {
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
}
}
Now each method has it's own session object, but when the register servlet gets called I have to do this:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
User user = // initialise user
if (getUser(request.getParameter("email")) == null) { // session
insertUser(user); // another session
request.setAttribute("user", user);
request.getRequestDispatcher("homepage.jsp").forward(request, response);
} else {
request.setAttribute("message", "This account already exist");
populateFields(request);
request.getRequestDispatcher("register.jsp").forward(request, response);
}
}
Now, my register servlet effectively used two sessions, vilating the pattern. I've thought of possible ways to fix:
Set a global Session object in the hibernateUtil class, then prevent getUser from committing the transaction. I think this means every method can now use this session, even in multiple request if the transaction is not committed. My problem is what is my only operation is to get a user and nothing more?
Let getUser return the session and pass it to insertUser, I don't know, I'm not sure if it's good to pass session objects around.
Just get rid of the helper class and write the code directly in the servlet and remove the helper class. I really think it would simplify the management of sessions, but I might not be able to reuse some transactions.
What should I do? And are there better code that does what I want to do?

The best option in this case is to have a design like this:
Have an abstract Dao that will have a Session session as field and will allow the injection of this via constructor or setter (up to you). All your Daos will be subclasses of this class.
The service classes can create a Session session in their methods or receive it as parameter. In this way, the service may inject the session to the necessary daos used in its methods. This also enables the service to start and commit/rollback a transaction.
In case you want/need that services should be unaware of opening/closing hibernate sessions, then you may create a wrapper for the interfaces that will create a session, inject it to a service, and then execute the proper methods of the service(s). After executing the method, this wrapper will commit/rollback the operations done in the session.
If you use a framework like Spring that provides integration with Hibernate, you will see that they have a similar design:
Dao classes must allow the injection of HibernateTemplate via constructor parameter or setter.
Services will use dao interfaces as needed.
Use of <transactional> or #Transactional will create a proxy class that wraps your service to open the session, execute the proper method of the service, and commit/rollback the operations, depending on the configuration of the transaction management done there.

You can implement a filter that creates the session and then stores it in the request, so you can reuse it anywhere you have access to the request object:
public class SessionPerRequestFilter implements Filter {
public static final String SESSION_ATTRIBUTE_NAME = "myapp.hibernate.session";
private static SessionFactory sessionFactory;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// Locate the session on the request object
Session session = (Session) request.getAttribute(SESSION_ATTRIBUTE_NAME);
if(session == null) {
// Create a new session
session = HibernateUtil.getSessionFactory().openSession();
request.setAttribute(SESSION_ATTRIBUTE_NAME, session);
}
try {
// Continue the filter chain
chain.doFilter(request, response);
}
finally {
// Close the session
session.close();
}
}
#Override
public void destroy() {
}
}
In your servlets you then get the session by
Session session = (Session) request.getAttribute(SessionPerRequestFilter.SESSION_ATTRIBUTE_NAME);
Make sure you only close your transactions and not the session in your helper classes though.

Related

Singleton DAO instance keeps old closed session between HTTP transactions

I'm trying to implement the "One-session-per-http-request" pattern with Hibernate, and it works for the 1st request : The servlet's doGet() method opens the session, gets some stuff, and closes the session.
But when I refresh the browser, My DAO Singleton instance (whose constructor gets the session from the SessionFactory) gets called a second time, but still uses the old session object (the singleton constructor is NOT called again). I then obtain a "Session is closed" error.
I guess that the singleton instance must be kept in cache between HTTP requests, so : How can I get the DAO singleton constructor called again ? (or another elegant solution to have the fresh SessionFactory session object ?)
Thank you very much
The servlet :
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
// Gets the session, eventually creates one
Session s = HibernateUtil.currentSession();
// Gets data from singleton DAO instance
MySingletonDAO o = MySingletonDAO.getInstance();
List<Stuff> stuff = o.getAllTheStuff();
// send it to the view
request.setAttribute("foo",stuff);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(vue);
dispatcher.forward(request, response);
}
/* error handling blah blah */
finally {
// closing the session
HibernateUtil.closeSession();
}
MySingletonDAO.java :
public class MySingletonDAO {
// Usual singleton syntax
private static MySingletonDAO INSTANCE = new MySingletonDAO();
public static MySingletonDAO getInstance() { return INSTANCE;}
private Session session;
private MySingletonDAO() {
session = HibernateUtil.currentSession();
System.out.println("This constructor is called only on the first HTTP transaction");
}
public List<Stuff> getAllTheStuff() {
try {
session.beginTransaction();
Query q = session.createQuery("FROM StuffDBTable");
session.getTransaction().commit();
return (List<Stuff>) q.list();
}
}
}
A classical thread-safe HibernateUtil.java :
public class HibernateUtil {
private static final SessionFactory sessionFactory;
public static final ThreadLocal session = new ThreadLocal();
static {
try {
// Creates the SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (HibernateException he) {
throw new RuntimeException("Conf problem : "+ he.getMessage(), he);
}
}
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// Opens a new Session, if this Thread has none
if (s == null || !s.isOpen() ) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
What you're asking for doesn't make sense: if the constructor of the singleton was called at each request, it wouldn't be a singleton anymore. The session is indeed closed at the end of the request, but the DAO keeps a reference to the session, instead of getting it from your util class every time it's called.
Your DAO code should be
public class MySingletonDAO {
private static MySingletonDAO INSTANCE = new MySingletonDAO();
public static MySingletonDAO getInstance() { return INSTANCE;}
private MySingletonDAO() {
}
public List<Stuff> getAllTheStuff() {
Session session = HibernateUtil.currentSession();
try {
session.beginTransaction();
Query q = session.createQuery("FROM StuffDBTable");
session.getTransaction().commit();
return (List<Stuff>) q.list();
}
}
}
That said, transactions should be handled declaratively, and should be handled at the service layer rather than the DAO layer: a transaction typically uses deveral DAOs, the entities returned by the DAO should stay managed, and all the accesses and modifications made to these entities should be made inside the transaction.
I strongly recommend using a Java EE container, or Spring, to handle the transactions and the session handling for you. You should also use the standard JPA API rather than the proprietary Hibernate API.

Spring Boot, Spring Security, session-timeout issue with session scoped Bean, #PreDestroy

Fisrt, I need to say that I'm using session scoped bean. So before session is closed the preDestroy() method is invoked
#Component
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "session")
public class MySessionBean {
#PreDestroy
public void preDestroy() {
//Do Smth with using Security principal
}
}
When I logout by using Spring Security utils everything goes fine, the preDestroy() method is called.
The main problems come when I use
server.session-timeout = 60 or = 1 in application.properties
preDestroy() is called approximately in 2.5 minutes after session has opened.
And much more interesting is that SecurityContextHolder.getContext().getAuthentication().getPrincipal(); is null. BUT I've successfully loged out.
Also I've tried a
#Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
return (ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) ->
configurableEmbeddedServletContainer.setSessionTimeout(1, TimeUnit.MINUTES);
}
I have the same result.
Also the problem exists while using Provided Tomcat
UPDATE:
The weird thing is that if I manually after 1 minute check the session
existence the method preDestroy() is called immediately. But
Security Principal is already null
Thanks in Advance!
When a session does timeout the SecurityContextHolder.getContext().getAuthentication().getPrincipal() will always return null. The SecurityContext is only filled when a request comes in, one of the filters does that. When a session times out the filters will of course not be invoked and as such the SecurityContext not filled.
Instead create a bean that implements ApplicationListener<HttpSessionDestroyedEvent>. The HttpSessionDestroyedEvent has a method getSecurityContexts that returns the SecurityContexts as originally in the HttpSession.
public class YourListener implements ApplicationListener<HttpSessionDestroyedEvent> {
public void onApplicationEvent(HttpSessionDestroyedEvent evt) {
for (SecurityContext ctx : evt.getSecurityContexts() ) {
Authentication auth = ctx.getAuthentication();
Object principal = auth.getPrincipal();
// Do your thing with the principal.
}
}
}
As M. Deinum said:
There is a thread which check about every x seconds if sessions are
invalid. So when your set your timeout to 1 minute it is 1 minute +
a bit more before your sessions is actually cleared. When you check
the session yourself, the invalid session is already cleaned as then
it it is forcefully checked.
So delay of preDestroy() invocation has been explained.
The next problem was how to get Security Principal after SESSION-TIMEOUT
NOTE that by implementing
ApplicationListener<HttpSessionDestroyedEvent>
HttpSessionListener
Session scope bean
the
SecurityContextHolder.getContext().getAuthentication() == null when appropriate destroy method is called
To get principal visit relatedStackPost
After you'll do that implement HttpSessionListener
#Component
public class MySessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
...
}
#Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
HttpSession httpSession = httpSessionEvent.getSession();
SecurityContext securityContext = (SecurityContextImpl) httpSession.getAttribute("SPRING_SECURITY_CONTEXT");
}
}

is it possible to use #transactional annotation with a transaction opened by hand (spring/hibernate)

I have a spring application which works with hibernate and annotations.
I open a session by hand which is done like in this How do I manually open a hibernate session? stack overflow answer.
Now i want to use a method of a service in the method that opens the session itself. This service however is annotated with the transactional statement. Is it possible to tell the method to use the transaction i just opened by hand?
e.g.
#Transactional("TransactionManager")
class Service1 {
public LazyObject someMethod();
}
class MetaService {
#Autowired
SessionFactory sf;
Service1 s1;
public someMethod() {
Session s = sf.openSession();
s.beginTransaction();
// tell this method to use s's transaction
// without annotating someMethod() with #transactional
LazyObject lo = s1.someMethod();
for ( LazyAtt la : lo.getLazyAtt() ) {
la.doSomething();
}
s.flush();
s.getTransaction().commit();
s.close();
}
}
For those wondering why I want to do it, check this question:
https://stackoverflow.com/questions/29363634/how-to-open-two-sessions-in-one-function-in-hibernate-spring

Access and modify property(ies) of an Application-Scoped Managed Bean from Session Listener

I need to access an application-scoped managed bean to modify certain properties from within an HttpSessionListener.
I already used something like the following:
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
ApplicationScopedBean appBean = (ApplicationScopedBean) externalContext.getApplicationMap().get("appBean");
appBean.getConnectedUsers().remove(user);
}
externalContext = FacesContext.getCurrentInstance().getExternalContext() causes a null pointer exception here already, and even if it didn't I'm not sure appBean could be accessible the above way.
Any ideas?
The FacesContext is only available in the thread serving the HTTP request initiated by the webbrowser which has invoked the FacesServlet. During a session destroy there's not necessarily means of a HTTP request. Sessions are usually destroyed by a background thread managed by the container. This does not invoke a HTTP request through the FacesServlet. So you should not expect the FacesContext to be always there during the session destroy. Only when you call session.invalidate() inside a JSF managed bean, then the FacesContext is available.
If your application scoped managed bean is managed by JSF #ManagedBean, then it's good to know that JSF stores it under the covers as an attribute of the ServletContext. The ServletContext in turn is available in the session listener by HttpSession#getServletContext().
So, this should do:
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
ApplicationScopedBean appBean = (ApplicationScopedBean) session.getServletContext().getAttribute("appBean");
appBean.getConnectedUsers().remove(user);
}
If you're running a Servlet 3.0 capable container, an alternative is to just let your application scoped bean implement HttpSessionListener and register itself as such upon construction. This way you have direct reference to the connectedUsers property.
#ManagedBean
#ApplicationScoped
public class AppBean implements HttpSessionListener {
public AppBean() {
ServletContext context = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
context.addListener(this);
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
User user = userService.findBySessionId(session.getId());
connectedUsers.remove(user);
}
// ...
}
Again another alternative is to keep the User in the session scope as a session scoped managed bean. You can then use the #PreDestroy annotation to mark a method which should be invoked when the session is destroyed.
#ManagedBean
#SessionScoped
public class User {
#ManagedProperty("#{appBean}")
private AppBean appBean;
#PreDestroy
public void destroy() {
appBean.getConnectedUsers().remove(this);
}
// ...
}
This has the additional benefit that the User is in EL context available as #{user}.

Spring - get used TransactionManager

I have two transaction managers and was curious if there is some possibility to get the one that has been used.
To be more concrete, how could underlyingMethod(..) find out which transactionManager was used (without sending it an additional parameter "transactionManagerName/Ref"):
#Transactional("transactionManager1")
public void transactionFromFirstTM() {
someClass.underlyingMethod()
}
#Transactional("transactionManager2")
public void transactionFromSecondTM() {
someClass.underlyingMethod()
}
?
ok I have used this to get the hibernate Session from actual transaction manager:
protected Session getSession() {
Map<Object, Object> resourceMap = TransactionSynchronizationManager.getResourceMap();
Session session = null;
for (Object value : resourceMap.values()) {
if (value instanceof SessionHolder) {
session = ((SessionHolder) value).getSession();
break;
}
}
return session;
}
I don't think you can, but you shouldn't do anything with the transaction manager. Some actions on the current transaction are available in TransactionSynchronizationManager
Another useful class is the TransactionAspectUtils. But not that both are meant to be used internally, and you should not rely on them in many places in your code.

Categories

Resources