Create a rest-api with the Spring Boot Java framework, part two

Hi. In the previous post I showed one way to get a Spring Boot project up and running. Then added a single endpoint to display some text.

The source code for this project is available from github.

Now we’ll connect to a postgresql-server. I chose postgresql because I am familiar with it but mysql would work just as well. I use JPA and Hibernate to abstract the database.
Configuring Spring Boot to use postgresql it very easy. This guide was helpful.

Add the following code to pom.xml for postgresql. And to prepare Spring Boot for the rest-protocol. I forgot to select this during the initial configuration of the program.

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

Spring Boot should download any dependencies automatically. Then add the following lines to the file application.properties. If you don’t use a password you can omit the third line.


spring.datasource.url= jdbc:postgresql://localhost:5432/worldcities
spring.datasource.username=worldcities
spring.datasource.password=postgres@123

Just make sure postgresql have been installed and configured. I use macports on os x, others use homebrew.

Then we’re ready to add our first class called City. This is not the complete class, constructors and getters are not included for brevity. Other than that it includes the @Entity and @Table annotation to tell Spring Boot what the table in the database looks like.

I have an old habit of using lowercase names for table names. And class names start, as an convention, with an uppercase letter and would look for a table called City if I didn’t override it in @Table.


@Entity
@Table(name = "city")
public class City implements Serializable {
    
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", columnDefinition = "serial")
    private final Integer id;
    private final String countrycode;
    private final String name_lowercase;
    private final String name;
    private final String region;
    private Integer population;
    private Float latitude;
    private Float longitude;
}

You define the endpoints for the rest-api in CityController. Lets define an endpoint to get a cityname.

@Autowired
private CityRepository cityInterface;

@RequestMapping(value = "/city/{name}", method = RequestMethod.GET)
public List<City> cityByName(@PathVariable("name") String name) {
 return cityInterface.findByName(name);
}

The database-calls are handled via the interface defined in CityRepository.

public interface CityRepository extends CrudRepository<City, Long> {
 List<City> findByName(String name);
}

So pretty easy to follow it turns out. Extract the cityname  from the path with @PathVariable and assign it to the string variable name. The call to findByName() returns zero or more City objects in a ArrayList.

I had an issue with a null-pointer exception when I called findByName() without @Autowired prepended to the CityRepository interface. I’m not sure why since it was not mentioned in the examples I looked at on the Spring Boot site. After some digging a Stackoverflow thread helped.

To get cities/locations within a radius I add the endpoint /cities/latitude/longitude/radius.

@RequestMapping(value = "/cities/{latitude}/{longitude}/{radius}", method = RequestMethod.GET)
public List<City> cityByLatitudeLongitudeRadius(
 @PathVariable("latitude") String latitude,
 @PathVariable("longitude") String longitude,
 @PathVariable("radius") String string_radius)
{}

First I define a variable with a default value for latitude, longitude and radius. And try to parse the string. Then call get_locations() that returns a List<City>.

Float lat = (float) 61.7428745;

try {
 lat = Float.parseFloat(latitude);
} catch (NumberFormatException e) { }

return get_locations(lat, lng, radius);

In get_locations() I use SimpleLatLng to calculate the latitude west and east and longitude south and north of the center. Then select all locations with this square. And lastly iterate through this list and remove locations outside the radius and return the remaining items.

List<City> cities = cityInterface.findByLatitudeBetweenAndLongitudeBetween(lat_west, lat_east, lng_south, lng_north); 

for (Iterator<City> iterator = cities.iterator(); iterator.hasNext();) {
 City city = iterator.next(); 
 LatLng p = new LatLng(city.getLatitude(), city.getLongitude());
 double d = LatLngTool.distance(center, p, LengthUnit.KILOMETER);
 if (d > radius) {
 iterator.remove();
 }
}

return cities;