基于Spring Boot+REST+Hibernate实现获取指定国家的所有城市
Alright, let's break down how to build the feature to fetch all cities for a specific country using Spring Boot, REST, and Hibernate, working with your existing entity classes.
We'll split this into three standard Spring Boot layers to keep the code clean and maintainable: Repository (Data Access), Service (Business Logic), and REST Controller (API Endpoint).
1. Data Access Layer: City Repository
First, create a Spring Data JPA repository for the City entity, adding a method to query cities by their associated country ID. Spring Data will auto-generate the SQL for this method.
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface CityRepository extends JpaRepository<City, Long> { // Fetch all cities linked to a specific country ID List<City> findByCountryId(Long countryId); }
You'll also need a basic repository for the Country entity to validate the country exists before fetching cities:
import org.springframework.data.jpa.repository.JpaRepository; public interface CountryRepository extends JpaRepository<Country, Long> { }
2. Business Logic Layer: City Service
Wrap the data access with a service layer to add validation and business rules (like checking if the requested country actually exists).
import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service public class CityService { private final CityRepository cityRepository; private final CountryRepository countryRepository; // Constructor-based dependency injection (preferred over @Autowired) public CityService(CityRepository cityRepository, CountryRepository countryRepository) { this.cityRepository = cityRepository; this.countryRepository = countryRepository; } public List<City> getCitiesByCountryId(Long countryId) { // Validate the country exists first Optional<Country> country = countryRepository.findById(countryId); if (country.isEmpty()) { // For production, use a custom exception like CountryNotFoundException instead of RuntimeException throw new RuntimeException("Country with ID " + countryId + " does not exist"); } return cityRepository.findByCountryId(countryId); } }
3. REST Controller: Expose the API Endpoint
Create a controller to expose the endpoint that clients will call to get cities for a country.
import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/api/countries") public class CountryCityController { private final CityService cityService; public CountryCityController(CityService cityService) { this.cityService = cityService; } // Endpoint: GET /api/countries/{countryId}/cities @GetMapping("/{countryId}/cities") public ResponseEntity<List<City>> getCitiesForCountry(@PathVariable Long countryId) { List<City> cities = cityService.getCitiesByCountryId(countryId); return ResponseEntity.ok(cities); } }
4. Optional: Fixing Serialization Issues
Your City entity uses @JsonIgnore on the country field, which means the country data won't be included in the JSON response. If you want to return partial country details (like the country name) without causing circular serialization issues, use a DTO (Data Transfer Object):
Create a City Response DTO
public class CityResponseDTO { private Long id; private String name; private String countryName; // Map City entity to DTO public CityResponseDTO(City city) { this.id = city.getId(); this.name = city.getName(); this.countryName = city.getCountry().getName(); } // Getters (setters optional unless you need them) public Long getId() { return id; } public String getName() { return name; } public String getCountryName() { return countryName; } }
Update the Service to Return DTOs
public List<CityResponseDTO> getCitiesByCountryId(Long countryId) { Optional<Country> country = countryRepository.findById(countryId); if (country.isEmpty()) { throw new RuntimeException("Country with ID " + countryId + " does not exist"); } return cityRepository.findByCountryId(countryId) .stream() .map(CityResponseDTO::new) .toList(); }
Update the Controller
@GetMapping("/{countryId}/cities") public ResponseEntity<List<CityResponseDTO>> getCitiesForCountry(@PathVariable Long countryId) { List<CityResponseDTO> cities = cityService.getCitiesByCountryId(countryId); return ResponseEntity.ok(cities); }
内容的提问来源于stack exchange,提问作者Manu

