Spring-Session with a relational database(Part 2)

Preface:

I recently had to work on a project where we had to turn an existing Java (Springboot) application whom was served by multiple web servers sitting behind a load balancer and required sticky sessions to an application that could be deployed to an environment using non-sticky sessions.

I wrote a blog article about how we setup our proof of concept using Spring-Session with our application here.  We initially wrote our tutorial using Redis as our session store, but it was decided to use a relational DB instead(Oracle).  Documentation was pretty sparse about how to exactly configure Spring-Session to use a different session store so I decided to write up a little blog post outlining what we did.

Spring-Session

Spring-Session provides a container portable method of persisting Http session data.   I won’t go into great detail on how to set it up in this post,  but full instructions to setup Spring-session can be found on their official website here.   For us,  we are going to configure Spring-session to use our own custom session store back by a MySQL database!  We do this by:

  1. Implementing our own SessionRepository and
  2. Injecting the custom SessionRepository to SessionRepositoryFilter

 

Create the SessionRepository

We first have to implement the SessionRepository interface.   It has four methods and they are all pretty self explanatory on what they do.   It’s worthwhile noting that the Session type here is not the container provided javax.servlet.http.HttpSession, but a org.springframework.session.Session instance instead.   In the context of a web application,  it does still represent a HttpSession in that the getId() method will return a jSessionId and attributes will be its session data.

  
public interface SessionRepository<S extends Session> {
           S createSession();
           void save(S session);
           S getSession(String id);
           void delete(String id);
}

For our “MySQL” storage sample,  the repository makes use of a “SessionStoreDao”.   This is simply our DAO object responsible doing CRUD operations on a table called “SessionStore”, which will store our user session data.  I won’t provide code for the DAO (as that depends on if you are using a ORM or stright JDBC-SQL) or the table.   We also have a SessionStore  “domain object”, which is just used to pass around information in our application.   This POJO contains information such as the jsessionId, timestamp and the serialized session data.  Let’s start:

public class MysqlSessionRepository implements SessionRepository {
	   @Autowired
	    private SessionStoreDao sessionStoreDao;

	    /**
	     * @return A new instance of MapSession
	     */
	    @Override
	    public Session createSession() {
	        Session s = new MapSession();
	        return s;
	    }

createSession() simply creates a new MapSession instance.  Spring-session will invoke this method whenever it needs to create a brand new session, for example, the first time the application invokes request.getSession().   You can do any additional initialization steps here, but in our simple example we simply return a empty map.

  


	    /**
	     * Update or create a new SessionStore to save the session to the DB.
	     * @param session This will be serialized and stored in the DB.
	     */
	    @Override
	    public void save(Session session) {
	        String id = session.getId();
	        Timestamp currentTime = new Timestamp(System.currentTimeMillis());
	        SessionStore sessionStore = sessionStoreDao.findOneBySessionId(id);
	        if (session != null && sessionStore == null ) {
	            sessionStore = new SessionStore();
	            sessionStore.setSessionId(session.getId());
	            sessionStore.setCreatedDate(currentTime);
	        }

	        if (session != null){
	        	// searlize the object
	            byte[] bytes = Util.objectToByteArray(session);
	            sessionStore.setData(bytes);
	            sessionStore.setLastModified(currentTime);
	        }

	        // save
	        sessionStoreDao.save(sessionStore);
	    }

The save() method is where you will have to do any work required to persist your session data to the database.   You have to first check if the user session already exists in the database.  For web applications, a natural key for the session record (in the database) is the jsessionId.     In our example, we first try to obtain the existing data (domain object) from the database.   If it exists we then  serialize the data contained in the provided in the Session object.   The session data can be represented in any way you want it in the database, but for simplicity sake, we just serialized the entire Session object.  This of course assumes everything stored in the session serializable.  Here, we also save additional information along with the persisted session (such as last modified date).

The SessionStoreDao.save() method simply takes in our domain object and persists it to the MySQL database.  Of course, if this is the first time the session is being persisted to the DB, we’ll have to create a brand new row to save the user data (hence the null check).

  

	    /**
	     * Load the seralizied session from the DB.
	     * @param id The session id (jsessionId)
	     * @return the Session instance correspoinding to the jsessionid
	     */
	    @Override
	    public Session getSession(String id) {
	        SessionStore sessionStore = sessionStoreDao.findOneBySessionId(id);
	        if (sessionStore == null ) {
	            return null;
	        }
	        byte[] blobData = sessionStore.getData();
	        MapSession s = (MapSession)Util.byteArrayToObject(blobData);
	        return s;
	    }

getSession() is responsible for deserializing/re-creating a the org.springframework.session.Session if it exists in the database.  Our SessionStoreDao is used to query the database based on the “session id” provided by Spring-Session.  In the context of a web application, the id parameter will always be the jsessionId.

In our save() method example above, we simply serialized the entire Session object and stored it into the database so to recreate it,  we simply deserialize it back into the Session object.  If session is brand new, there should be no such record in the database yet.  For brand new user sessions, Spring-session will determine on your behalf when to call load() and create() .  To signify to Spring-session that the user session is new (or not found) and a brand new session needs to be created, getSession() should return null.

 

  

	    /**
	     * Handles requests to invalidate the session (ie logout).
	     * Delete the session from the DB.
	     * @param id The session id (jsessionId)
	     */
	    @Override
	    public void delete(String id) {
	        SessionStore sessionStore = sessionStoreDao.findOneBySessionId(id);
	        if (sessionStore != null ) {
	            sessionStoreDao.delete(sessionStore);
	        }
	    }
}

delete()  The final method that you need to implement is the delete() method.  A example of when Spring-session will invoke this method is if you invalidate the HttpSession.  In our simple example, we simply have to delete the SessionStore record from the MySQL database.

Inject it into SessionRepositoryFilter

After creating our MysqlSessionRepository implementation, we need to inject into SessionRepositoryFilter so that it will be used by Spring-session.  There many ways to do this, I will present one method here.

@Configuration
public class MysqlSessionStoreConfiguration {	

    @Bean
    public MysqlSessionRepository sessionRepository() {
        return new MysqlSessionRepository();
    }
    
    @Bean
    public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
        SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter<S>(sessionRepository);     
        return sessionRepositoryFilter;
    }
}

The code is a bear-bones implementation of a configuration class that creates a MySqlSessionRepository bean and injects into a SpringRepsoitoryFilter.  Note the @Configuration annotation indicating this is a configuration class.

And that is it.  We’ve created a MysqlSessionRepository that implements SessionRepository.  This custom repository stores user session data into a MySQL database.  As your application handles http requests while storing data into the HttpSession object, Spring-session will invoke create/save/delete/load methods as required which will save the user session data to the MySQL database without the application knowing.  The changes on application developers is very minimal as Spring-session provides an almost seamless layer to route the session data anywhere.  Developers can more or less continue to use the application HttpSession as normal, but we do make an assumption here that the data stored in the session is serializable.

 

Non-sticky sessions with Spring-Session (Part 1)

Preface:

I recently had to work on a project where we had to turn an existing Java (Springboot) application whom was served by multiple web servers sitting behind a load balancer and required sticky sessions to an application that could be deployed to an environment using non-sticky sessions.

The application was not stateless and was using the session to store data between requests.  After doing some analysis, changing the application to become stateless was not an option for us.  So we needed to save the session data somewhere, on disk, using Redis or a database.

Spring-Session

Spring-Session provided an API layer to persist session data without having to significantly change application code.  It basically provided an HttpSession implementation that saved data to disk and allowed application developers to continue freely using the Session object as before (as long as the data was serializable).

I won’t go into huge detail on how Spring-Session works, but it basically replaces the container provided HttpSession object with its own (via a Filter) and at some point, Spring-session will persist the data to someplace (as configured by us) without the application itself needing to know what is going on behind the scenes.

When the user’s request ends, the session data is persisted and when that same user makes another request, as expected, Spring-session will fetch the user’s session data and desearialize it.  The data will then be available using the HttpSession’s getParameter() methods as a conventional HttpSession object.

Proof of Concept and Session Store
Spring-Session comes with support using Redis as the session store (using Jedis).   So our first step in evaluating Spring-Session is to get a our application integrated with Spring-session,  setup a Redis and Redis backup (Redis slave) servers. This way we can determine if Redis is a good choice for our session store and if not, can we still use Spring-session with another session store (such as a relational DB).

One can argue if the redundancy of session data is actually required here.  That is, the data we are using Redis to store is only session data as the user navigates though our application and not actual user data that requires persistence.   I’ll leave that as a argument for a later time, but for our sake, we needed to see some redundancy features of Redis as a matter of “policy”.

Our PoC arcitecture looked like the following:

Setting Up With Maven
Spring-Session with Redis requires 2 dependencies,  i) the Spring-Session dependency and   ii) the Redis connector.   If you are are using a custom session store, you do not need to include the Spring-Session data-redis dependency.

  
      <dependency>
         <groupId>org.springframework.session</groupId>
         <artifactId>spring-session</artifactId>
         <version>1.0.2.RELEASE</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.session</groupId>
         <artifactId>spring-session-data-redis</artifactId>
         <version>1.0.2.RELEASE</version>
         <type>pom</type>
      </dependency>

Springboot
If you are using Springboot, you have to create the JedisConnectionFactory bean that will be injected into the SessionRepositoryFilter.  Many of the configuration properties of a JedisConnectionFactory have default values, so the example below is pretty sparse.  I only specify the URL of my Redis master  instance, but it is in here where you can specify other properties such as password, etc.

  
    @Bean
    public JedisConnectionFactory connectionFactory() {
    	JedisConnectionFactory redisConnectionFactory= new JedisConnectionFactory();
        redisConnectionFactory.setHostName("redis.masterserver.com");
        redisConnectionFactory.afterPropertiesSet();
        return redisConnectionFactory;
    }

Testing the Setup
To test our Poc, we wanted to see that 1) our setup is truly session-sticky-less and 2) Redis data redundancy was working properly.

1) Session Stickiness
To test session non-stickiness, we send a HTTP request from a computer though the load balancer.   We find out it is being handled by application server A and the application puts some data into the session.  We then shut down server A and send another request from the same browser.  The request is now handled by server B, but because we’ve externally stored the session data, the user’s session (and data) are still alive.

2) Redis Redundancy
To test Redis redundancy, we sent a HTTP request to our app though the load balancer, but we don’t care which server handles the request.  We use the Redis client (redis-cli) check that the session data being stored in the master Redis instance.  We then connected the client to the slave instance and check that the same data is also in the slave.

 

Conclusion and More Work
It turned out that integrating our application with Spring-Session was very straight forward. Our PoC Redis and replication process was also easy to get going. A architectural decision was made on not to use Redis as the session store but we determined that we still wanted to use Spring-Session with a relational database as our storage. This leads us to part 2 of our Spring-Session introduction, using Spring-Session with a relational DB (Oracle or MySql).

References and other tutorials
If you are interested in more details on how to setup Spring-Session with Redis, you can look at these sites below. They helped us a lot.

  1. Spring-Session Project Page
  2. Video: Spring-Session by Josh Long
  3. Basic Tutorial
  4. Spring-Data: Jedis
  5. Managing Users Data with Spring Session
  6. Another Redis Replication Tutorial