Cascading Dropdown List with Ajax in Spring MVC Framework and Spring Data JPA

On the Eclipse, create a Spring MVC project

Enter Project Information:

  • Name: LearnSpringMVCWithRealApps
  • Group: com.demo
  • Artifact: LearnSpringMVCWithRealApps
  • Description: Learn Spring MVC with Real Apps
  • Package: com.demo

Select the technologies and libraries to be used:

  • Web
  • JPA
  • MySQL

Click Next button to show Site Information for project

Click Finish button to finish create Spring MVC project




<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.10.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.demo</groupId>
	<artifactId>CascadingDropDownListWithSpringMVC</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>CascadingDropDownListWithSpringMVC</name>
	<description>Cascading Drop Down List with Spring MVC and Spring Data JPA</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
			<scope>provided</scope>
		</dependency>

		<!-- JSTL tag lib -->
		<dependency>
			<groupId>javax.servlet.jsp.jstl</groupId>
			<artifactId>javax.servlet.jsp.jstl-api</artifactId>
			<version>1.2.1</version>
		</dependency>

		<dependency>
			<groupId>taglibs</groupId>
			<artifactId>standard</artifactId>
			<version>1.1.2</version>
		</dependency>

		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>




Create a database with the name is learn_spring_mvc_with_real_apps. This database have 3 table: Country, State and City table

--
-- Table structure for table `city`
--

CREATE TABLE `city` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(250) NOT NULL,
  `stateId` int(11) NOT NULL REFERENCES state(id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `city`
--

INSERT INTO `city` (`id`, `name`, `stateId`) VALUES
(1, 'City 1', 1),
(2, 'City 2', 1),
(3, 'City 3', 2),
(4, 'City 4', 2),
(5, 'City 5', 2),
(6, 'City 6', 3),
(7, 'City 7', 3);

-- --------------------------------------------------------

--
-- Table structure for table `country`
--

CREATE TABLE `country` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(250) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `country`
--

INSERT INTO `country` (`id`, `name`) VALUES
(1, 'Country 1'),
(2, 'Country 2'),
(3, 'Country 3'),
(4, 'Country 4');

-- --------------------------------------------------------

--
-- Table structure for table `state`
--

CREATE TABLE `state` (
  `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
  `name` varchar(250) NOT NULL,
  `countryId` int(11) NOT NULL REFERENCES country(id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `state`
--

INSERT INTO `state` (`id`, `name`, `countryId`) VALUES
(1, 'State 1', 1),
(2, 'State 2', 1),
(3, 'State 3', 1),
(4, 'State 5', 2),
(5, 'State 6', 2),
(6, 'State 7', 3),
(7, 'State 8', 3);




spring.mvc.view.prefix = /WEB-INF/views/
spring.mvc.view.suffix = .jsp
spring.mvc.static-path-pattern=/resources/**

server.port=9596

spring.datasource.url= jdbc:mysql://localhost:3306/demo5
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

Create new folder named js in src\main\resources\static folder. Copy jquery file to this folder

Create new package, named com.demo.entities. In this package, create entities class as below:

Create new java class, named Country.java

package com.demo.entities;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "country")
public class Country implements java.io.Serializable {

	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;
	private Set<State> states = new HashSet<State>(0);

	public Country() {
	}

	public Country(String name) {
		this.name = name;
	}

	public Country(String name, Set<State> states) {
		this.name = name;
		this.states = states;
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "id", unique = true, nullable = false)
	public Integer getId() {
		return this.id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Column(name = "name", nullable = false, length = 250)
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "country")
	public Set<State> getStates() {
		return this.states;
	}

	public void setStates(Set<State> states) {
		this.states = states;
	}

}

Create new java class, named State.java

package com.demo.entities;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "state")
public class State implements java.io.Serializable {

	private static final long serialVersionUID = 1L;
	private Integer id;
	private Country country;
	private String name;
	private Set<City> cities = new HashSet<City>(0);

	public State() {
	}

	public State(Country country, String name) {
		this.country = country;
		this.name = name;
	}

	public State(Country country, String name, Set<City> cities) {
		this.country = country;
		this.name = name;
		this.cities = cities;
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "id", unique = true, nullable = false)
	public Integer getId() {
		return this.id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "countryId", nullable = false)
	public Country getCountry() {
		return this.country;
	}

	public void setCountry(Country country) {
		this.country = country;
	}

	@Column(name = "name", nullable = false, length = 250)
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@OneToMany(fetch = FetchType.LAZY, mappedBy = "state")
	public Set<City> getCities() {
		return this.cities;
	}

	public void setCities(Set<City> cities) {
		this.cities = cities;
	}
}




Create new java class, named StateEntity.java

package com.demo.entities;

import java.io.Serializable;

public class StateEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;

	public StateEntity() {
	}

	public StateEntity(Integer id, String name) {
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

Create new java class, named City.java

package com.demo.entities;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "city")
public class City implements java.io.Serializable {

	private static final long serialVersionUID = 1L;
	private Integer id;
	private State state;
	private String name;

	public City() {
	}

	public City(State state, String name) {
		this.state = state;
		this.name = name;
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "id", unique = true, nullable = false)
	public Integer getId() {
		return this.id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "stateId", nullable = false)
	public State getState() {
		return this.state;
	}

	public void setState(State state) {
		this.state = state;
	}

	@Column(name = "name", nullable = false, length = 250)
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

Create new java class, named CityEntity.java

package com.demo.entities;

import java.io.Serializable;

public class CityEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	private Integer id;
	private String name;

	public CityEntity() {
	}

	public CityEntity(Integer id, String name) {
		this.id = id;
		this.name = name;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}




Create new package named com.demo.repositories. In this package, create new repositories as below.

In com.demo.repositories package, create new interface named CountryRepository.java as below:

package com.demo.repositories;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.demo.entities.Country;

@Repository("countryRepository")
public interface CountryRepository extends CrudRepository<Country, Integer> {

}

In com.demo.repositories package, create new interface named StateRepository.java as below:

package com.demo.repositories;

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.demo.entities.State;
import com.demo.entities.StateEntity;

@Repository("stateRepository")
public interface StateRepository extends CrudRepository<State, Integer> {

	@Query("select new com.demo.entities.StateEntity(id, name) from State where country.id = :id")
	public List<StateEntity> findByCountry(@Param("id") int id);

}

In com.demo.repositories package, create new interface named CityRepository.java as below:

package com.demo.repositories;

import java.util.List;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.demo.entities.City;
import com.demo.entities.CityEntity;

@Repository("cityRepository")
public interface CityRepository extends CrudRepository<City, Integer> {

	@Query("select new com.demo.entities.CityEntity(id, name) from City where state.id = :id")
	public List<CityEntity> findByState(@Param("id") int id);

}

Create new package named com.demo.services. In this package, create country service as below:

In com.demo.services package, create new interface named CountryService.java as below:

package com.demo.services;

import com.demo.entities.Country;

public interface CountryService {

	public Iterable<Country> findAll();

	public Country find(int id);

}




In com.demo.services package, create new class named CountryServiceImpl.java implements from CountryService interface as below:

package com.demo.services;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.demo.entities.Country;
import com.demo.repositories.CountryRepository;

@Service("countryService")
public class CountryServiceImpl implements CountryService {

	@Autowired
	private CountryRepository countryRepository;

	@Override
	public Iterable<Country> findAll() {
		return countryRepository.findAll();
	}

	@Override
	public Country find(int id) {
		return countryRepository.findById(id).get();
	}

}

Create new package named com.demo.services. In this package, create state service as below:

In com.demo.services package, create new interface named StateService.java as below:

package com.demo.services;

import java.util.List;
import com.demo.entities.StateEntity;

public interface StateService {

	public List<StateEntity> findByCountry(int id);

}

In com.demo.services package, create new class named StateServiceImpl.java implements from StateService interface as below:

package com.demo.services;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.demo.entities.StateEntity;
import com.demo.repositories.StateRepository;

@Service("stateService")
public class StateServiceImpl implements StateService {

	@Autowired
	private StateRepository stateRepository;

	@Override
	public List<StateEntity> findByCountry(int id) {
		return stateRepository.findByCountry(id);
	}

}

Create new package named com.demo.services. In this package, create city service as below:

In com.demo.services package, create new interface named CityService.java as below:

package com.demo.services;

import java.util.List;

import com.demo.entities.CityEntity;

public interface CityService {

	public List<CityEntity> findByState(int id);

}




In com.demo.services package, create new class named CityServiceImpl.java implements from CityService interface as below:

package com.demo.services;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.demo.entities.CityEntity;
import com.demo.repositories.CityRepository;

@Service("cityService")
public class CityServiceImpl implements CityService {

	@Autowired
	private CityRepository cityRepository;

	@Override
	public List<CityEntity> findByState(int id) {
		return cityRepository.findByState(id);
	}

}

Create new package named com.demo.controllers. In this package, create new controller as below:

Create new java class, named DemoController.java

package com.demo.controllers;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demo.services.CityService;
import com.demo.services.CountryService;
import com.demo.services.StateService;
import com.google.gson.Gson;

@Controller
@RequestMapping(value = { "", "demo" })
public class DemoController {

	@Autowired
	private CountryService countryService;

	@Autowired
	private StateService stateService;

	@Autowired
	private CityService cityService;

	@RequestMapping(method = RequestMethod.GET)
	public String index(ModelMap modelMap) {
		modelMap.put("countries", countryService.findAll());
		return "demo/index";
	}

	@ResponseBody
	@RequestMapping(value = "loadStatesByCountry/{id}", method = RequestMethod.GET)
	public String loadStatesByCountry(@PathVariable("id") int id) {
		Gson gson = new Gson();
		return gson.toJson(stateService.findByCountry(id));
	}

	@ResponseBody
	@RequestMapping(value = "loadCitiesByState/{id}", method = RequestMethod.GET)
	public String loadCitiesByState(@PathVariable("id") int id) {
		Gson gson = new Gson();
		return gson.toJson(cityService.findByState(id));
	}

}




Create new folders with path webapp\WEB-INF\views in src\main. In views folder, create views as below:

Create new folder named demo. Create new jsp file named index.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1" isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>Cascading Dropdown List with Ajax in Spring MVC Framework and Spring Data JPA</title>
<script src="${pageContext.request.contextPath }/resources/js/jquery-1.7.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){

	$('#comboboxCountry').on('change', function(){
		var countryId = $(this).val();
		$.ajax({
			type: 'GET',
			url: '${pageContext.request.contextPath }/demo/loadStatesByCountry/' + countryId,
			success: function(result) {
				var result = JSON.parse(result);
				var s = '';
				for(var i = 0; i < result.length; i++) {
					s += '<option value="' + result[i].id + '">' + result[i].name + '</option>';
				}
				$('#comboboxState').html(s);
			}
		});
	});


	$('#comboboxState').on('change', function(){
		var stateId = $(this).val();
		$.ajax({
			type: 'GET',
			url: '${pageContext.request.contextPath }/demo/loadCitiesByState/' + stateId,
			success: function(result) {
				var result = JSON.parse(result);
				var s = '';
				for(var i = 0; i < result.length; i++) {
					s += '<option value="' + result[i].id + '">' + result[i].name + '</option>';
				}
				$('#comboboxCity').html(s);
			}
		});
	});



});
</script>
</head>
<body>

	<form>
		<table>
			<tr>
				<td>Country</td>
				<td>
					<select id="comboboxCountry" style="width:200px">
						<c:forEach var="country" items="${countries }">
							<option value="${country.id }">${country.name }</option>
						</c:forEach>
					</select>
				</td>
			</tr>
			<tr>
				<td>State</td>
				<td>
					<select id="comboboxState" style="width:200px"></select>
				</td>
			</tr>
			<tr>
				<td>City</td>
				<td>
					<select id="comboboxCity" style="width:200px"></select>
				</td>
			</tr>
		</table>
	</form>

</body>
</html>




Select LearnSpringMVCWithRealAppsApplication.java file in com.demo package, right click and select Run As/Spring Boot App menu

Access index method in demo controller with following url: http://localhost:9596

Output