Understanding Second Level Cache in Hibernate using Ehcache

Introduction :

Caching is all about application performance optimization and it sits between your application and the database to avoid the number of database hits as many as possible to give a better performance.Hibernate provide the lots of other features, one of the major benefit of using Hibernate in large application is it’s support for caching, hence reducing database queries and better performance.

Hibernate provide caching functionality in three layers,

1) First Level Caching: 

Fist level cache in hibernate is enabled by default and you do not need to do anything to get this functionality working. In fact, you can not disable it even forcefully.

Its easy to understand the first level cache if we understand the fact that it is associated with Session object. As we know session object is created on demand from session factory and it is lost, once the session is closed. Similarly, first level cache associated with session object is available only till session object is live. It is available to session object only and is not accessible to any other session object in any other part of application.

2) Second Level Caching:


The second level cache is responsible for caching objects across sessions. When this is turned on, objects will first be searched  in the session if it not found then delegates searching in the cache and if they are not found, a database query will be fired. Second level cache will be used when the objects are loaded using their primary key. This includes fetching of associations. Second level cache objects are constructed and reside in different memory locations.

I will explain more on second level cache with a example.

3) Query Caching:


Query Cache is used to cache the results of a query. When the query cache is turned on, the results of the query are stored against the combination query and parameters. Every time the query is fired the cache manager  checks for the combination of parameters and query. If the results are found in the cache, they are returned, otherwise a database transaction is initiated.  As you can see, it is not a good idea to cache a query if it has a number of parameters, because then a single parameter can take a number of values. For each of these combinations the results are stored in the memory. This  can lead to extensive memory usage.
We have to change the hibernate configuration to enable the query cache. This is done by adding the following line to the Hibernate configuration.

 <property name="hibernate.cache.use_query_cache">true</property>

Read this page to find pitfalls on query second level cache.

Understanding Second level caching:



When ever hibernate session try to load an entity, the very first place it look for cached copy of entity in first level cache (i,e hibernate session).If cached copy of entity is present in first level, it is returned as result of load method.If there is no cached entity in first level cache, then second level cache is looked up for cached entity.If second level cache has cached entity, it is returned as result of load method. But, before returning the entity, it is stored in first level cache also so that next invocation to load method for entity will return the entity from first level cache itself, and there will not be need to go to second level cache again.If entity is not found in first level cache and second level cache also, then database query is executed and entity is stored in both cache levels, before returning as response of load() method.Second level cache validate itself for modified entities, if modification has been done through hibernate session APIs.

If some user or process make changes directly in database, the there is no way that second level cache update itself until “timeToLiveSeconds” duration has passed for that cache region. In this case, it is good idea to invalidate whole cache and let hibernate build its cache once again.


Let me explain second level caching with a simple app.


Maven dependencies required:

    <properties>
        <hibernate.version>4.3.5.Final</hibernate.version>
        <ehcache-version>2.4.3</ehcache-version>
        <db.mysql.driver.version>5.1.30</db.mysql.driver.version>
        <slf4j-version>1.7.7</slf4j-version>
    </properties>


    <dependencies>
        <!-- hibernate dependencies-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- ehcache dependencies-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>${ehcache-version}</version>
        </dependency>

        <!-- database driver dependencies-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${db.mysql.driver.version}</version>
        </dependency>

        <!-- slf4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j-version}</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>hibernate-second-level-cache-example</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


For hibernate second level cache, they are many caching providers.Among Ehcache is popular caching framework.So we would need to add ehcache-core and hibernate-ehcache dependencies in our application. EHCache uses slf4j for logging, so I have also added slf4j for logging purposes.

Model Class: 

Lets take a simple User entity java class which helps in understanding second level cache using CRUD operations on User entity.

User.java

@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "user")
@Entity
@Table(name = "USER")
public class User implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "USERID", nullable = false)
    private Integer userid;

    @Column(name = "USERNAME", length = 20)
    private String username;

    @Column(name = "PASSWORD", length = 20)
    private String password;

    @Column(name = "ROLE", length = 20)
    private String role;

    @Column(name = "ACTIVE")
    private boolean active;

//constructor,setters & getter,hashcode & equals,toString

}

If you observe we use concurrency strategy in the above model,so it is time we should discuss about concurrency strategy. A concurrency strategy is a mediator which responsible for storing items of data in the cache and retrieving them from the cache. If you are going to enable a second-level cache, you will have to decide, for each persistent class and collection, which cache concurrency strategy to use.

Four Types of Concurrency Strategies: 

Transactional: 

Use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions,in the rare case of an update.

Read-write: 

Again use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions,in the rare case of an update.

Nonstrict-read-write: 

This strategy makes no guarantee of consistency between the cache and the database. Use this strategy if data hardly ever changes and a small likelihood of stale data is not of critical concern.

Read-only:

 A concurrency strategy suitable for data which never changes. Use it for reference data only.

Hibernate Configuration:

<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost:3306/demoDB?createDatabaseIfNotExist=true
        </property>
        <property name="hibernate.connection.username">
            root
        </property>
        <property name="hibernate.connection.password">
            pramati
        </property>
        <property name="hibernate.show_sql">true</property>
        <property name="hbm2ddl.auto">create</property>

        <!-- you can make it false to disable second level cache -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

        <!-- to enable query cache uncomment below property
        <property name="hibernate.cache.use_query_cache">true</property> -->

        <!-- to provide ehcache configuration file with cache configuration
        <property name="net.sf.ehcache.configurationResourceName">/myehcache.xml</property> -->

        <mapping class="com.hibernate.cache.model.User" />

    </session-factory>
</hibernate-configuration>


In above xml, four properties are related to caching

hibernate.cache.use_second_level_cache is true then second level caching is enabled.
hibernate.cache.use_query_cache is true then query caching is enabled.
hibernate.cache.region.factory_class : 
Two values allowed to this property
The non-singleton EhCacheRegionFactory allows you to configure EHCache separately for each Hibernate instance using net.sf.ehcache.configurationResourceName property.
Where as SingletonEhCacheRegionFactory shares the same EHCache configuration among all Hibernate session factories.

net.sf.ehcache.configurationResourceName : is used to define the EHCache configuration file location, it’s an optional parameter and if it’s not present EHCache will try to locate ehcache.xml file in the application classpath.If ehcache.xml does not find in the class path it will load default ehcache-failsafe.xml which have default caching properties availble in ehcache-core.jar

           <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />

For more explination on each and every property on ehcache,please go with my previous article on Ehcache.

Second Level Cache Demo :

public class HibernateSecondLevelCacheDemo {

    public static void main(String[] args) {

        SessionFactory sessionFactory = buildSessionFactory();

        //enabling statistics
        final Statistics statistics = sessionFactory.getStatistics();
        statistics.setStatisticsEnabled(true);

        //Adding entries to user table
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        User userOne = new User(1, "user1", "user1", "user", true);
        User userTwo = new User(2, "user2", "user2", "user", false);
        session.save(userOne);
        session.save(userTwo);
        session.getTransaction().commit();
        session.close();



        Session sessionOne = sessionFactory.openSession();
        Session sessionTwo = sessionFactory.openSession();

        Transaction transactionOne = sessionOne.beginTransaction();
        Transaction transactionTwo = sessionTwo.beginTransaction();

        //printing initial statistics
        printStatistics(statistics,null,0);

        User user1=(User)sessionOne.load(User.class, 1);
        printStatistics(statistics,user1,1);

        User user2=(User)sessionOne.load(User.class, 1);
        printStatistics(statistics,user2,2);

        User user3=(User)sessionOne.load(User.class, 2);
        printStatistics(statistics,user3,3);

        User user4=(User)sessionTwo.load(User.class, 1);
        printStatistics(statistics,user4,4);

        User user5=(User)sessionTwo.load(User.class, 2);
        printStatistics(statistics,user5,5);

        transactionOne.commit();
        transactionTwo.commit();
        sessionOne.close();
        sessionTwo.close();
        sessionFactory.close();

    }



    public static void printStatistics(Statistics statistics, User user , int count)
    {
        System.out.println("***************");
        System.out.println("Hit : "+ count);
        if(user != null)
            System.out.println("User Details :" + user.toString());
        System.out.println("Entity fetch count :" + statistics.getEntityFetchCount());
        System.out.println("Second level cache hit count : "+ statistics.getSecondLevelCacheHitCount());
        System.out.println("Second level cache put count : " + statistics.getSecondLevelCachePutCount());
        System.out.println("Second level cache miss count : " + statistics.getSecondLevelCacheMissCount());
        System.out.println("***************");
    }

    public static SessionFactory buildSessionFactory() {
        URL url = HibernateSecondLevelCacheDemo.class.getClassLoader().getResource("configuration/hibernate.cfg.xml");
        Configuration configuration = new Configuration();
        configuration.configure(url);
        StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
        return configuration.buildSessionFactory(ssrb.build());
    }

}

Output :


Hibernate: insert into USER (ACTIVE, PASSWORD, ROLE, USERNAME) values (?, ?, ?, ?)
Hibernate: insert into USER (ACTIVE, PASSWORD, ROLE, USERNAME) values (?, ?, ?, ?)
***************
Hit : 0
Entity fetch count :0
Second level cache hit count : 0
Second level cache put count : 0
Second level cache miss count : 0
***************
***************
Hit : 1
Hibernate: select user0_.USERID as USERID1_0_0_, user0_.ACTIVE as ACTIVE2_0_0_, user0_.PASSWORD as PASSWORD3_0_0_, user0_.ROLE as ROLE4_0_0_, user0_.USERNAME as USERNAME5_0_0_ from USER user0_ where user0_.USERID=?
User Details :User{userid=1, username='user1'}
Entity fetch count :1
Second level cache hit count : 0
Second level cache put count : 1
Second level cache miss count : 1
***************
***************
Hit : 2
User Details :User{userid=1, username='user1'}
Entity fetch count :1
Second level cache hit count : 0
Second level cache put count : 1
Second level cache miss count : 1
***************
***************
Hit : 3
Hibernate: select user0_.USERID as USERID1_0_0_, user0_.ACTIVE as ACTIVE2_0_0_, user0_.PASSWORD as PASSWORD3_0_0_, user0_.ROLE as ROLE4_0_0_, user0_.USERNAME as USERNAME5_0_0_ from USER user0_ where user0_.USERID=?
User Details :User{userid=2, username='user2'}
Entity fetch count :2
Second level cache hit count : 0
Second level cache put count : 2
Second level cache miss count : 2
***************
***************
Hit : 4
User Details :User{userid=1, username='user1'}
Entity fetch count :2
Second level cache hit count : 1
Second level cache put count : 2
Second level cache miss count : 2
***************
***************
Hit : 5
User Details :User{userid=2, username='user2'}
Entity fetch count :2
Second level cache hit count : 2
Second level cache put count : 2
Second level cache miss count : 2
***************

Where as

Fetch Count : if a entity is retervied from database using sql query.
Second level cache hit count : If a entity is retervied from Second level cache.
Second level cache put count : If a entity is added into second level cache.
Second level cache miss count : If a entity tried to retervie from Second level cache,but that entity is not exist in cache.

Hit0 : we can see all are 0,it means no entity is tried to retrive from cache.

Hit1 : user1 is loading from sessionOne.First it tries to load from First level cache(Session), as it is not present in session then tries to load from Second level cache, as it's also not exist in second level cache, so "second level cache miss count" will increase by 1.Then entity will load from Database and put in first level cache & second level cache,So "second level cache put count" is increased by 1.As entry is retervied from database so fetch count will increase by 1.

Hit2 : Again User1 is requested from SessionOne.As it is there in first level cache so retervied from Session.

Hit3 : user2 is loading from sessionOne,Same as Hit1.

Hit4 : user1 is requested from sessionTwo.Then first it tries in Frist level cache,So it does not exist then tries in second level cache.As User1 is already existing in second level cache so "Second level cache hit count" increase by 1.

Hit5 : user2 is requested from sessionTwo,It same as Hit4.

That’s all about Hibernate second level caching using EHCache example, I hope it will help you in configuring EHCache in your hibernate applications and gaining better performance.

you can download source from GitHub.

Spring Security custom login using MySQL DB and Hibernate

Introduction :

This is a step-by-step tutorial that helps you build a Spring security-Hibernate application easily in a clear and concise way.This article is all about implementing Spring Security with custom login in your Spring MVC web application to secure a URL access with  database authentication using hibernate.
In my previous article,I explained Spring Security by placing user credentials in XML configuration. How ever it is not safe to keep user details in XML file.So let us use database to secure & maintain authentication and authorization details.

Technologies used :

java 1.7
tomcat 8
spring 4.1.6.RELEASE
spring-security 4.0.1.RELEASE
Hibernate 4.3.11.Final
Mysql DB (driver version 5.1.30)
Maven 3

Step 1: Maven dependencies

    <properties>
        <spring-version>4.1.6.RELEASE</spring-version>
        <spring-orm-version>4.1.1.RELEASE</spring-orm-version>
        <spring-security-version>4.0.1.RELEASE</spring-security-version>
        <hibernate.version>4.3.11.Final</hibernate.version>
        <mysql.driver.version>5.1.30</mysql.driver.version>
    </properties>

    <dependencies>

        <!-- spring dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring-orm-version}</version>
        </dependency>

        <!-- spring security dependencies -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring-security-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring-security-version}</version>
        </dependency>

        <!-- hibernate dependencies -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- mysql driver -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.driver.version}</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>spring-security-custom-login-using-hibernate</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
We use maven-compiler-plugin to compile source using java 1.7 and finalName represents war file name.

Step 2: Login Database Diagram &  Script's :

At first we use two tables USER & ROLE.As single user can have multiple roles and each role can be part of multiple users, so there is MANY to MANY association between USER and ROLE.And database diagram looks like 


Database scripts for above diagram 


create table USER (
 USER_ID varchar(255) not null, 
 ACTIVE integer not null, 
 NAME varchar(32) not null, 
 PASSWORD varchar(64) not null,
 primary key (USER_ID))

create table ROLE (
 ROLE_ID integer not null auto_increment, 
 NAME varchar(32) not null, 
 primary key (ROLE_ID))

create table USER_ROLE (
 USER_ID varchar(255) not null, 
 ROLE_ID integer not null, 
 primary key (USER_ID, ROLE_ID))

alter table USER_ROLE add constraint FK_oqmdk7xj0ainhxpvi79fkaq3y foreign key (ROLE_ID) references ROLE (ROLE_ID)

alter table USER_ROLE add constraint FK_j2j8kpywaghe8i36swcxv8784 foreign key (USER_ID) references USER (USER_ID)

Step 3: Defining Hibernate Entity Model's :

For the above database table, Hibernate entity models look's like

User.java :

@Entity
@Table(name = "USER")
public class User implements Serializable{

    @Id
    @Column(name = "USER_ID", unique = true, nullable = false)
    private String userId;

    @Column(name = "NAME", nullable = false, length = 32)
    private String name;

    @Column(name = "PASSWORD", nullable = false, length = 64)
    private String password;

    @Column(name = "ACTIVE", nullable = false, length = 1)
    private Integer active;

    @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
    @JoinTable(
            name = "USER_ROLE",
            joinColumns = @JoinColumn(name = "USER_ID"),
            inverseJoinColumns = @JoinColumn(name = "ROLE_ID")
    )
    private Set<Role> roles = new HashSet<>();

//setters and getters

}

Role.java : 

@Entity
@Table(name = "ROLE")
public class Role implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ROLE_ID", unique = true, nullable = false)
    private Integer roleId;

    @Column(name = "NAME", nullable = false, length = 32)
    private String name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

//setters and getters

}

Step 4: Configure Custom Login  & intercept URL's :

If you want to use a custom login page for your application, then you can configure spring-security to use your custom login page instead. You can use the <security:form-login> tag to define your custom login form page within the <security:http> … </security:http> tag. Below is the configuration example.

    <security:http auto-config="true" use-expressions="true">
        <!-- Interceptor urls -->
        <security:intercept-url pattern="/" access="permitAll" />
        <security:intercept-url pattern="/userpage" access="hasRole('USER')" />
        <security:intercept-url pattern="/admin**" access="hasRole('ADMIN')" />

        <security:form-login login-page="/login"
                             default-target-url="/userpage"
                             login-processing-url="/j_spring_security_check"
                             authentication-failure-url="/accessdenied"
                             username-parameter="username"
                             password-parameter="password"
                />

        <!-- Logout -->
        <security:logout logout-success-url="/logout" />

        <!-- enable csrf protection -->
        <security:csrf/>
    </security:http>


login-page: Custom login page url path.

default-target-url : url where the user should navigate after successful login.

authentication-failure-url: url where the user should navigate after a login failure.

username-parameter and password-parameter : username-parameter and password-parameter - These two are optional. By default spring-security accepts the parameter names “j_username” and “j_password” as username and password in the login form. If you want to given any other name to the username and password input fields in the login form, then you can specify the custom parameter names in these two attributes in tag.

Enable the Cross Site Request Forgery (CSRF) protection, refer to this link. In XML, by default, CSRF protection is disabled.

In “logout-success-url” you can specify the page url path which should execute when user uses spring-security logout process.


Step 5: Configuring LoginService in Spring Security XML:

Spring security have AuthenticationManager and it delegate to a collection of AuthenticationProvider instances.An AuthenticationProvider will call an object that implements the UserDetailsService interface. A UserDetailsService looks up the user data and returns a UserDetails object fully populated. If the UserDetailsService cannot find this user then a UsernameNotFoundException is thrown.UserDetailsService interface has just one method loadUserByUsername(String username) and  returns a UserDetails object, then the AuthenticationProvider will check that the password matches the password the user entered. If it does not match, then the AuthenticationProvider will throw an AuthenticationException.

    <security:authentication-manager>
        <security:authentication-provider user-service-ref="loginService">
        </security:authentication-provider>
    </security:authentication-manager>



Below LoginService implemented from UserDetailsService and overriding method loadUserByUsername(String username).And referring loginService to authentication-provider in the XML configuration.And LoginService looks as


@Service("loginService")
public class LoginService implements UserDetailsService {

    @Autowired
    private HibernateTemplate hibernateTemplate;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
        User user = hibernateTemplate.get(User.class, username);
        if (user == null) {
            return null;
        }
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = buildSimpleGrantedAuthorities(user);
        UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(), user.getActive() == 1 ? true : false, true
                , true, true, simpleGrantedAuthorities);
        return userDetails;

    }

    private List<SimpleGrantedAuthority> buildSimpleGrantedAuthorities(final User user) {
        List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
        if (user.getRoles() != null) {
            for (Role role : user.getRoles()) {
                simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
            }
        }
        return simpleGrantedAuthorities;
    }
}

Step 6: Database Configuration

In the above Login service we autowired hibernate template to intract to database table.Below is the configuration to build Hibernate template
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/LoginDB"></property>
        <property name="username" value="root"></property>
        <property name="password" value="admin"></property>
        <property name="connectionProperties">
            <props>
                <prop key="hibernate.show_sql">
                    true
                </prop>
                <prop key="hibernate.enable_lazy_load_no_trans">
                    true
                </prop>
            </props>
        </property>
    </bean>

    <bean id="loginSessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="com.spring.security" />
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="loginSessionFactory"/>
    </bean>
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
        <property name="sessionFactory" ref="loginSessionFactory"/>
    </bean>

Step 7: Adding Controller

@Controller
public class HelloWorldController {

    @RequestMapping(value = {"/"}, method = RequestMethod.GET)
    public ModelAndView homePage() {
        ModelAndView model = new ModelAndView();
        model.addObject("greeting", "Hi, Welcome to hello world app. ");
        model.setViewName("welcome");
        return model;
    }

    @RequestMapping(value = {"/login"}, method = RequestMethod.GET)
    public ModelAndView loginPage() {
        ModelAndView model = new ModelAndView();
        model.addObject("greeting", "Hi, Welcome to login page");
        model.setViewName("login");
        return model;
    }

    @RequestMapping(value = {"/accessdenied"}, method = RequestMethod.GET)
    public ModelAndView accessDeniedPage() {
        ModelAndView model = new ModelAndView();
        model.addObject("message", "Either username or password is incorrect.");
        model.setViewName("accessdenied");
        return model;
    }

    @RequestMapping(value = {"/userpage"}, method = RequestMethod.GET)
    public ModelAndView userPage() {

        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            Collection<? extends GrantedAuthority> authorities = ((UserDetails) principal).getAuthorities();
            if (authorities.size() == 1) {
                final Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
                GrantedAuthority grantedAuthority = iterator.next();
                if (grantedAuthority.getAuthority().equals("ADMIN")) {
                    return adminPage();
                }
            }
        }
        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Hello World");
        model.addObject("user", getUser());
        model.setViewName("user");
        return model;
    }

    @RequestMapping(value = "/adminpage", method = RequestMethod.GET)
    public ModelAndView adminPage() {

        ModelAndView model = new ModelAndView();
        model.addObject("title", "Spring Security Hello World");
        model.addObject("user", getUser());
        model.setViewName("admin");
        return model;
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ModelAndView logoutPage(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return homePage();
    }

    private String getUser() {
        String userName = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof UserDetails) {
            userName = ((UserDetails) principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }
}

Step 8: Adding Views  



1) welcome.jsp :
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>HelloWorld page</title>
</head>
<body>
    ${title}<br/><br/>
    Dear ${user}, you are successfully logged into this application.
    <br/>
    <a href="<c:url value="/logout" />">Logout</a>
</body>
</html>

2) login.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Login</title>
</head>
<body>
<h1>Spring Security Custom Login Form</h1>
 <div id="logindiv">
    <form name='loginForm' action="<c:url value='j_spring_security_check' />" method='POST'>
        <table>
         <tr>
       <td>User:</td>
       <td><input type='text' name='username' value=''></td>
      </tr>
      <tr>
       <td>Password:</td>
       <td><input type='password' name='password' /></td>
      </tr>
      <tr>
       <td colspan='2'><input name="submit" type="submit"
         value="submit" /></td>
      </tr>
     </table>
     <input type="hidden" name="${_csrf.parameterName}"
                 value="${_csrf.token}" />
    </form>
    </div>
</body>
</html>

3) user.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>HelloWorld page</title>
</head>
<body>
    ${title}<br/><br/>
    Dear ${user}, you are successfully logged into this application.
    <br/>
    <a href="<c:url value="/logout" />">Logout</a>
</body>
</html>

4) admin.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>HelloWorld page</title>
</head>
<body>
    ${title}<br/><br/>
    Dear ${user}, you are successfully logged into this application as admin.
    <br/>
    <a href="<c:url value="/logout" />">Logout</a>
</body>
</html>

5) AccessDenied.jsp :

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Access Denied Page</title>
</head>
<body>
    <h2> Access Denied </h2>
    <br/>${message}<br/>

    Click here for<a href="<c:url value="/login" />"> Login</a>
</body>
</html>

Step 9 : Screens  


After deploying above application(spring-security-custom-login-using-hibernate.war) in web server(tomcat) and you can find below screens

1) welcome page :


2) Login page :


3) User Page :


4) Admin Page :


5) AccessDenied Page : 


Download Source : 

Please download source from GitHub.

Conclusion :

After reading this article you will get an idea how Spring Security works and how you can integrate spring security with database using hibernate with custom login form.