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:
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:
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="".
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.
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.
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:
The session times out. A session times out after 30 minutes of
no activity, when there are no new requests made with a
specific session ID. The timeout value of 30 minutes can
be changed programmatically in your application.
Your code invokes the session's invalidate() method. This
destroys the session and any data stored with it.
The application or server shuts down.
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:
When a new request is made, the Container checks
to see if there is a Session ID inside the request.
If there is, then
it uses that ID to look up the matching session
object.
If there is no Session ID in the request,
it generates a new one and creates a new Session object.
Regardless of whether or not the session exists
or has to be created, the Session ID is then sent
back to the client in the Response object.
The client stores this Session ID as a
session cookie (a cookie that is deleted automatically when the
session ends).
For subsequent requests, the Session ID is sent
along with the request object. The server then
checks the Session ID it received and matches it up
with the corresponding Session object.
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:
The HttpSession class contains some useful methods:
isNew()
returns true if the client has not yet received a session ID
(meaning that the session has not yet been created/started)
or if the client has disabled session cookies.
This means you can't tell if the session is actually new,
if the user's browser is just refusing the session :P
getSession()
will create and return a new
HttpSession object, if a session doesn't already exist.
If a session does already exist, then the method returns
that session.
It's also possible to access an existing session without
creating a new one if the existing one doesn't exist: If you use
the getSession(false) method, it will return an HttpSession
object ONLY if it already exists. If the session doesn't
exist, it will return null, and it won't create a new
session.
setAttribute(key, value)
adds a session attribute to the HttpSession object. The value
can be any type of object.
getAttribute(key) retrieves
a session attribute value by it's unique key and returns that object.
removeAttribute(key)
removes an attribute from the HttpSession object by its
key. If the object doesn't exist, then nothing happens.
invalidate() destroys the session
and any objects stored within it. This is a method you would
typically call when you want the current session to end, such
as when the user clicks a "Log Out" link/button.
getId() returns a String value containing
the session ID for the current session. Sometimes you might
want to get the session ID and store it.
setMaxInactiveInterval(interval)
allows you to change the number of seconds for the session timeout
(a session times out when the user is inactive for a certain amount
of time, i.e. when no new requests are made to the server during
the session).
If you pass a value that is 0 or less, the session
will never timeout
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:
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:
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:
In Chrome:
In the address bar, there should be an "information" icon to the
immediate left of the URL.
On a normal web page (not one loaded from localhost) it probably
looks more like a padlock icon or a warning triangle if you're
on a page using http instead of https.
Click the icon and select "Cookies.."
You can expand whatever nodes you see to find the cookies
for the current site you're visiting. Click the cookie to see the
session ID.
In Firefox:
View the developer tools by pressing Shift-F9 (or Fn-Shift-F9, depending
on your keyboard). Or, go to the Firefox menu and select Web
Developer > Web Developer Tools and then click the Storage tab.
Exercise
Add the Inventory bean to your project:
Inventory
- itemId: int
- itemName : String
- quantity : int
- price : double
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:
If there is currently a List of inventory object in the session,
retrieve it. Otherwise, create a brand new, empty list of
inventory objects. Remember that attributes are stored as Object so
you'll have to cast it:
Object o = session.getAttribute("inventory");
List<Inventory> list = null;
if (o == null) {
list = new CopyOnWriteArrayList<Inventory>();
} else {
list = (CopyOnWriteArrayList<Inventory>)o;
}
Add the new inventory object to the list.
Replace the old list in the session with the new list that has
the newly added inventory item in it.
Load an inventory.html page.
Add the inventory.html page that lists all the items in the inventory
list:
If the list of inventory objects in the session is null or
empty, display "No Inventory Items".
If the list of inventory objects in the session exists and
is not empty, display them in a table with columns
for the ID, name, quantity, and price.
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.
Exercises
1.Add a form to your project that
asks the user for their 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!".
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.
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:
Use an inner join to get the list of inventory records with
the category data you need.
Have the method that retrieves all inventory records return a
String HashMap where key is the category ID and the value
is the category name.
Alternatively, have the method that returns all inventory
records return a Map<Inventory, Category>, where the
Category object is the category for the inventory object's
matching ID.
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!