Overview of This Lesson

Refresh this page because I am probably still making changes to it.

So far we've learned to write programs that are capable of storing data in memory during a single request-response cycle, but what if we want to access data between request-response cycles? Sessions can be used to store and retrieve data during a user's session, even if there are multiple requests and responses during that session.

API Used in this lesson

Check the documentation for the HttpSession Class to see what else you can do with sessions in a Java EE application.

Other Resources

The following additional resources are available if you would like more information:

Intro to Sessions in JavaEE and Spring

We've learned up to this point that we can pass data back and forth between the view and the controller by using model attributes. However, model attributes are only accessible during a single request-response cycle. We call this level of scope request-level scope.

There are actually 4 levels of scope in a Java web application:

  1. Page-level scope: any data/objects at page-level scope are only visible/accessible within the current page/file.
    • Page-level data is typically used for values/objects that are only needed within the current page, such as a variable to hold a calculation or to contain a collection element during iteration in a th:each="".
  2. Request-level scope: any data/objects at request-level scope are only visible/accessible during the current request-response cycle.
    • Request-level data is typically used for values/objects needed between a request and response, such as a new object the user just created with form inputs that needs to be displayed on an output page.
  3. Session-level scope: any data/objects at session-level scope are only visible/accessible while the user's current session is active.
    • Session-level data is typically used for values/objects that are needed between different request-response cycles. For example, a list of items you want to display on a form and the output page (like a shopping cart) or information about the user that's currently logged in.
  4. Application-level scope: any data/objects at application-level scope are visible/accessible throughout all pages/classes/files in the application from the time the application starts until the application terminates.
    • Application-level data is typically used for values/objects that are needed everywhere in the application while the application is running. These are usually static values such as the name of the company, or the properties for a database connection, or an array or collection of items that is displayed on several pages of the application.

We've used page-level scope when we create a parameter variable to iterate through a collection in Thymeleaf: that variable is only accessible in that page. We've also used request-level scope when we use the Model to store attributes during a single request-response cycle. Now it's time to discuss session-level scope and see how we can use standard HTTP Sessions to keep track of data while a user is using our application.

A Session begins when a user accesses your application (any page, generally). A Session ends when one of the following happens:

When a Session is first created, a unique session ID is generated for that session. The session ID is stored on the user's computer as a cookie and sent along with each request the client makes. The server then reads the session ID in the request and matches it to table of session data it has: the session data with the same session ID belongs to that specific user during this current session.

HTTP Sessions in Java EE/Spring

Sessions are especially useful in a Java web application if you need to store data or objects to use elsewhere in the application while the session is active. For example, if you want to keep track of the user that is currently logged in between page requests, or you want to store a list of static items that is displayed on multiple pages while the user's session is active.

In a Java Web Application, most of the session activity is managed by the part of the server called the container. The Container is responsible for managing the life cycle of your application, your application's security, executing the Java-code parts of your application, session management, and several other things. It is the Container that generates a new Session ID and Session object when the session is first needed:

The session ID is stored as a session cookie in the client browser so, as with regular applications, sessions won't work if a user has disabled session cookies. In the past we could encode the session ID into the request URL to get around this problem, but this is now no longer considered a secure practice. For now, if your client has session cookies disabled, they will not be able to use portions of your application, in which case you should make sure you include some work-arounds.

HttpSession Class

Sessions are modeled by the javax.servlet.http.HttpSession class. When you create a handler method that needs to use the current session object and its data, you simply add an HttpSession parameter to your method. The HttpSession object has already been created and set up by Spring, so this will cause it to be injected into your handler method:

@GetMapping("/")
public String index(HttpSession session) {
    // do things
}

HttpSession contains a map of attributes, just like a Model does, so you can add attributes to the HttpSession just like you do with Model attributes, except you use the setAttribute() method:

@GetMapping("/")
public String index(HttpSession session, @RequestParam String userName) {
    // do things
    session.setAttribute("username", userName);
}

In a Thymeleaf template, you can access session attributes using the implicit session object variable:

<h1 th:text="|Welcome, ${session.username}!|">Welcome!</h1>

The HttpSession class contains some useful methods:

Demonstration

Start up a new project and add dependencies for Spring Web, Dev Tools, Lombok, Thymeleaf, and Spring Session. The Spring Session dependency allows you to use sessions in a Spring Boot application: adding this dependency will cause Spring to inject an HttpSession object for you to use in your Java and HTML/Thymeleaf code.

If you want to add sessions to an existing project, just add the following dependency to the POM:

<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-core</artifactId>
</dependency>

Add an index.html Thymeleaf page and a Controller to your application.

In the controller, add a handler method that loads the index.html page. Include an HttpSession parameter. Add the code that sets a new session attribute with a message if the session is new, and a different message if the session is not new:

@GetMapping("/")
public String index(HttpSession session) {

    if (session.isNew()) {
        session.setAttribute("message", "Welcome!");
    } else {
        session.setAttribute("message", "Welcome Back!");
    }
    return "index.html";
}

In your index.html page, add a title, heading, and a DIV or paragraph element with text that includes the session attribute you added:

<p th:text="${session.message}"></p>

Now try your program out. You should see your first message on the screen. Then refresh/reload the page, and you'll see the second message!

What happens if you close your browser and then re-open it and reload the page? You should still see your second message because the session is still active!

You can also view the Session cookie and the session's ID. If you know how to view the cookies for the current page in your browser, do so:

Exercise

Add the Inventory bean to your project:

Inventory
- itemId: int
- itemName : String
- quantity : int
- price : double
+ Inventory()
+ Inventory(itemId : long, itemName : String, quantity : int, price : double)
+ Inventory(itemName : String, quantity : int, price : double)
+ getItemId() : long
+ setItemId(id : long) : void
+ getItemName() : String
+ setItemName(name : String) : void
+ getQuantity() : int
+ setQuantity(qty : int) : void
+ getPrice() : double
+ setPrice(price : double) : void
+ equals(object : Object) : boolean
+ hashCode() : int
+ toString() : String

The only validation required is that quantity and price must both be greater than 0. Default values are all the default null values for the data type.

Replace the code in your index page with a form that allows a user to input the data for an inventory item. Bind the form to an Inventory object.

Add a handler method to load the index page.

Add a second handler method that processes the form data into an Inventory object. In this method, we will add the newly created Inventory object into a CopyOnWriteArrayList of Inventory objects, that we're going to store in the Session data:

Add the inventory.html page that lists all the items in the inventory list:

Add a third handler that only loads the inventory.html page. We can use this to go to inventory.html without having to add an item first, and that will allow you to debug an empty list.

Test your program by visiting the inventory list first, and make sure it shows the "No Inventory Items" message, then go and add some new inventory items to make sure the list of items appears.

message wendi if you use a screen reader
Handler method that processes the form
message wendi if you use a screen reader
Inventory List

Exercises

1.Add a form to your project that asks the user for their name.

form with Enter Name field
Form where user enters a name

Write a handler method that processes the form by retrieving the user-entered name and storing it in a session attribute. Then load an output page.

The output page should display the user's name in a heading: if the name exists in the session, display "Welcome, " followed by the saved name. Otherwise, display just "Welcome!".

output page with Welcome, Foo Bar
Output page displays name in a heading

Include a link back to the form so the user can change their name if they wish. The form's field should automatically be populated with any name the user already has stored in the session.

the input form again with Foo Bar in the name field
The name stored in the session should appear automatically

Also include a "Log Out" link that maps to another handler method: this method should invalidate the session and reload the form page (you can tell it worked if the text field is empty after clicking Log Out).

2. Sessions with JDBC: Modify the Players program from the CRUD lessons - we did way too many database accesses to get the list of teams. Modify all the necessary code so that the teams list is stored in the session object. All code that would have read the teams data from the database now only reads that data if the session attribute for the teams list is empty.

Don't forget that you'll need to add the Spring Session dependency to your POM file!

3. Sessions with JDBC: Start a new project and add the dependencies for Spring Web, Dev Tools, Lombok, Thymeleaf, H2 Database, Spring Data JDBC, and Spring Session.

Copy your Inventory bean over to this new project. Add an integer member to your bean called "category". It will contain the Category ID for a specific category that your inventory item belongs to (e.g. if you're selling computer parts, you might have categories for "motherboards", "power supplies", and "keyboards/mice".

Add a bean called Category:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Category {

    private int id;
    private String description;
    private int aisle;  // aisle number in the store

}

Add a schema.sql that creates a Category table (the id field is an auto-increment primary key). Add a data.sql that adds whatever categories you like.

Add the code to the schema.sql file that creates a table for your Inventory records. Make sure you have columns for each data member!

Add a form that allows a user to enter data for an inventory record. The category should be selectable from a drop-down list. The list's data comes from a session attribute.

The categories list should only be retrieved from the database once (ideally, only when it doesn't already exist in the session - every time you need to use the list, just check and make sure it's in the session and if it doesn't already exist, retrieve it from the database).

Write all the necessary code insert the new inventory record (using data entered by the user on the form) into the inventory table. After inserting a record, load a fresh list of inventory data into a session attribute and then load an output page.

Add the output page that lists all the inventory items sorted by category and then by name. The category name should be displayed instead of the category number. The user should also be able to view this page directly instead of having to add a record first.

Store the list of inventory items in a session attribute as well, so that it only needs to be retrieved when a new inventory record is added.

TIP: Use the same techniques for the inventory list with category name that you used in the players/teams exercise:

4. Sessions with JDBC: Now add an EDIT link for each inventory item. Don't forget the path variable! Add the handler method that processes a click on an EDIT link.

As in previous exercises, we are able to re-use the handler method for inserting records, but we need a way to determine if this handler is being invoked on an insert or an update.

In a previous exercise, we used a hidden form field to hide the record ID: if the record ID was empty or 0, we knew that this was a new record (an insert), so we would call the insert method. Otherwise it was an update and we would call the update method.

However, that won't work this time because the ID for our inventory records is manually entered! So it should never be empty. How then, do we tell if we're inserting or updating? Use a session attribute! For example, I created an attribute called "edit" that was true when I was editing, and false when I was adding. See if you can make it work!