In today’s interconnected world, location data plays a pivotal role in numerous applications, from e-commerce and logistics to travel and social networking. Efficiently managing and accessing this wealth of geographical information is crucial for developers. This article delves into the realm of location data management using Spring JPA (Java Persistence API), a powerful framework for object-relational mapping in Java applications.

We’ll embark on a journey to build a robust Spring service capable of handling intricate location data, encompassing countries, states, and cities. Our focus will be on structuring JPA entities, crafting repositories for seamless data access, and implementing a service layer to orchestrate the loading and retrieval of location information.

The data should be loaded using the following URL: https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/master/countries%2Bstates%2Bcities.json, or a similar URL adhering to the same data schema. This loading process should be incorporated within a change set in your application to avoid redundant data loading attempts.

1. Entities

  • Country.java
import javax.persistence.*;
import java.util.List;

@Entity
public class Country {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String iso3;
    private String iso2;
    private String numericCode;
    private String phoneCode;
    private String capital;
    private String currency;
    private String currencyName;
    private String currencySymbol;
    private String tld;
    private String nativeName;
    private String region;
    private String regionId;
    private String subregion;
    private String subregionId;
    private String nationality;
    
    @OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Timezone> timezones;

    @Embedded 
    private Translations translations;

    private String latitude;
    private String longitude;
    private String emoji;
    private String emojiU;

    @OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<State> states;

    // Getters and setters for all attributes
}
  • State.java
import javax.persistence.*;
import java.util.List;

@Entity
public class State {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String stateCode;
    private String latitude;
    private String longitude;
    private String type;

    @ManyToOne
    @JoinColumn(name = "country_id") 
    private Country country;

    @OneToMany(mappedBy = "state", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<City> cities;

    // Getters and setters
}
  • City.java
import javax.persistence.*;

@Entity
public class City {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String latitude;
    private String longitude;

    @ManyToOne
    @JoinColumn(name = "state_id")
    private State state;

    // Getters and setters
}
  • Timezone.java
import javax.persistence.*;

@Entity
public class Timezone {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String zoneName;
    private int gmtOffset;
    private String gmtOffsetName;
    private String abbreviation;
    private String tzName;

    @ManyToOne
    @JoinColumn(name = "country_id") 
    private Country country;

    // Getters and setters
}
  • Translations.java
import javax.persistence.Embeddable;

@Embeddable
public class Translations {
    private String kr;
    private String ptBR;
    private String pt;
    private String nl;
    private String hr;
    private String fa;
    private String de;
    private String es;
    private String fr;
    private String ja;
    private String it;
    private String cn;
    private String tr;
    private String ru;
    private String uk;
    private String pl;

    // Getters and setters
}

2. Repositories

  • CountryRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CountryRepository extends JpaRepository<Country, Long> {
    List<Country> findByRegion(String region);
}
  • StateRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface StateRepository extends JpaRepository<State, Long> {
    List<State> findByCountryId(Long countryId);
}
  • CityRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CityRepository extends JpaRepository<City, Long> {
    List<City> findByStateId(Long stateId);
}

3. Service

  • LocationService.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.List;

@Service
public class LocationService {

    @Autowired
    private CountryRepository countryRepository;

    @Autowired
    private StateRepository stateRepository;

    @Autowired
    private CityRepository cityRepository;

    @Autowired
    private RestTemplate restTemplate; 

    public void load(String url) {
        try {
            // 1. Fetch JSON from URL
            String jsonData = restTemplate.getForObject(url, String.class);

            // 2. Deserialize JSON into Java objects
            ObjectMapper objectMapper = new ObjectMapper();
            List<Country> countries = objectMapper.readValue(jsonData, 
                                            objectMapper.getTypeFactory().constructCollectionType(List.class, Country.class));

            // 3. Save to the database
            countryRepository.saveAll(countries);

        } catch (IOException e) {
            // Handle JSON parsing exceptions
            e.printStackTrace(); 
        }
    }

    public List<Country> getCountries() {
        return countryRepository.findAll();
    }

    public List<State> getStatesByCountry(Long countryId) {
        return stateRepository.findByCountryId(countryId);
    }

    public List<City> getCitiesByState(Long stateId) {
        return cityRepository.findByStateId(stateId);
    }

    public List<Country> getCountriesByRegion(String region) {
        return countryRepository.findByRegion(region);
    }
}

Integration into a Spring Boot Project

  1. Place these files:

    • Place the entity classes (Country.java, State.java, City.java, Timezone.java, Translations.java) in your project’s src/main/java/.../model directory.
    • Place the repository interfaces (CountryRepository.java, StateRepository.java, CityRepository.java) in your project’s src/main/java/.../repository directory.
    • Place the service class (LocationService.java) in your project’s src/main/java/.../service directory.
  2. Configure Spring Data JPA:

    • Ensure you have Spring Data JPA dependency in your pom.xml or build.gradle.
    • Configure your database connection properties in your application.properties or application.yml.
  3. Use the LocationService:

    • Autowire the LocationService into your controller or other components.
    • Call the load method to populate the database from the JSON URL.
    • Use the other methods (getCountries, getStatesByCountry, etc.) to retrieve data as needed.

Example Usage in a Controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class LocationController {

    @Autowired
    private LocationService locationService;

    @GetMapping("/load-data")
    public String loadData() {
        locationService.load("https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/master/countries%2Bstates%2Bcities.json");
        return "Data loading initiated!";
    }

    @GetMapping("/countries")
    public List<Country> getAllCountries() {
        return locationService.getCountries();
    }

    // ... other endpoints for states, cities, etc.
}

Enhancements

  • PDF Parsing: Implement the readJsonFromPdf method in the LocationService using a PDF parsing library to extract the JSON data from “location.pdf.”
  • Error Handling: Add comprehensive error handling and logging to the load method to gracefully manage potential exceptions.
  • Data Validation: Consider adding validation logic to the entities or service to ensure data integrity before saving to the database.
  • API Documentation: Use tools like Swagger to document your

Discover more from GhostProgrammer - Jeff Miller

Subscribe to get the latest posts sent to your email.

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.