Cleanups Spring Data JPA example.
Various cleanups to the Spring Data JPA example, including: * Move repositories into service package and make them package private thus only expose the service interfaces to clients. * Merge HotelRepository and HotelSummaryRepository and make service implementations package protected. * Introduce integration test base class to bootstrap the app as SpringAppliation.run would. * Refactor central test case to rather use Spring MVC integration testing framework. * Add integration tests for repositories to execute query methods.
This commit is contained in:
committed by
Phillip Webb
parent
c1344683ab
commit
d2def68602
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.domain.repository;
|
||||
|
||||
import org.springframework.boot.sample.data.jpa.domain.City;
|
||||
import org.springframework.boot.sample.data.jpa.domain.Hotel;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface HotelRepository extends Repository<Hotel, Long> {
|
||||
|
||||
Hotel findByCityAndName(City city, String name);
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.domain.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import org.springframework.boot.sample.data.jpa.domain.City;
|
||||
import org.springframework.boot.sample.data.jpa.domain.Hotel;
|
||||
import org.springframework.boot.sample.data.jpa.domain.HotelSummary;
|
||||
import org.springframework.boot.sample.data.jpa.domain.RatingCount;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class HotelSummaryRepository {
|
||||
|
||||
private static final String AVERAGE_REVIEW_FUNCTION = "avg(r.rating)";
|
||||
|
||||
private static final String FIND_BY_CITY_QUERY = "select new "
|
||||
+ HotelSummary.class.getName() + "(h.city, h.name, "
|
||||
+ AVERAGE_REVIEW_FUNCTION
|
||||
+ ") from Hotel h left outer join h.reviews r where h.city = ?1 group by h";
|
||||
|
||||
private static final String FIND_BY_CITY_COUNT_QUERY = "select count(h) from Hotel h where h.city = ?1";
|
||||
|
||||
private static final String FIND_RATING_COUNTS_QUERY = "select new "
|
||||
+ RatingCount.class.getName() + "(r.rating, count(r)) "
|
||||
+ "from Review r where r.hotel = ?1 group by r.rating order by r.rating DESC";
|
||||
|
||||
private EntityManager entityManager;
|
||||
|
||||
public Page<HotelSummary> findByCity(City city, Pageable pageable) {
|
||||
StringBuilder queryString = new StringBuilder(FIND_BY_CITY_QUERY);
|
||||
applySorting(queryString, pageable == null ? null : pageable.getSort());
|
||||
|
||||
Query query = this.entityManager.createQuery(queryString.toString());
|
||||
query.setParameter(1, city);
|
||||
query.setFirstResult(pageable.getOffset());
|
||||
query.setMaxResults(pageable.getPageSize());
|
||||
|
||||
Query countQuery = this.entityManager.createQuery(FIND_BY_CITY_COUNT_QUERY);
|
||||
countQuery.setParameter(1, city);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<HotelSummary> content = query.getResultList();
|
||||
|
||||
Long total = (Long) countQuery.getSingleResult();
|
||||
|
||||
return new PageImpl<HotelSummary>(content, pageable, total);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<RatingCount> findRatingCounts(Hotel hotel) {
|
||||
Query query = this.entityManager.createQuery(FIND_RATING_COUNTS_QUERY);
|
||||
query.setParameter(1, hotel);
|
||||
return query.getResultList();
|
||||
}
|
||||
|
||||
private void applySorting(StringBuilder query, Sort sort) {
|
||||
if (sort != null) {
|
||||
query.append(" order by");
|
||||
for (Order order : sort) {
|
||||
String aliasedProperty = getAliasedProperty(order.getProperty());
|
||||
query.append(String.format(" %s %s,", aliasedProperty, order
|
||||
.getDirection().name().toLowerCase(Locale.US)));
|
||||
}
|
||||
query.deleteCharAt(query.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAliasedProperty(String property) {
|
||||
if (property.equals("averageRating")) {
|
||||
return AVERAGE_REVIEW_FUNCTION;
|
||||
}
|
||||
return "h." + property;
|
||||
}
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,19 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.domain.repository;
|
||||
package org.springframework.boot.sample.data.jpa.service;
|
||||
|
||||
import org.springframework.boot.sample.data.jpa.domain.City;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface CityRepository extends Repository<City, Long> {
|
||||
interface CityRepository extends Repository<City, Long> {
|
||||
|
||||
Page<City> findAll(Pageable pageable);
|
||||
|
||||
Page<City> findByNameLikeAndCountryLikeAllIgnoringCase(String name, String country,
|
||||
Pageable pageable);
|
||||
Page<City> findByNameContainingAndCountryContainingAllIgnoringCase(String name,
|
||||
String country, Pageable pageable);
|
||||
|
||||
City findByNameAndCountryAllIgnoringCase(String name, String country);
|
||||
|
||||
@@ -14,15 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.service.impl;
|
||||
package org.springframework.boot.sample.data.jpa.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.sample.data.jpa.domain.City;
|
||||
import org.springframework.boot.sample.data.jpa.domain.HotelSummary;
|
||||
import org.springframework.boot.sample.data.jpa.domain.repository.CityRepository;
|
||||
import org.springframework.boot.sample.data.jpa.domain.repository.HotelSummaryRepository;
|
||||
import org.springframework.boot.sample.data.jpa.service.CitySearchCriteria;
|
||||
import org.springframework.boot.sample.data.jpa.service.CityService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -32,31 +28,41 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
@Component("cityService")
|
||||
@Transactional
|
||||
public class CityServiceImpl implements CityService {
|
||||
class CityServiceImpl implements CityService {
|
||||
|
||||
// FIXME deal with null repository return values
|
||||
|
||||
private CityRepository cityRepository;
|
||||
private final CityRepository cityRepository;
|
||||
|
||||
private HotelSummaryRepository hotelSummaryRepository;
|
||||
private final HotelRepository hotelRepository;
|
||||
|
||||
@Autowired
|
||||
public CityServiceImpl(CityRepository cityRepository, HotelRepository hotelRepository) {
|
||||
this.cityRepository = cityRepository;
|
||||
this.hotelRepository = hotelRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<City> findCities(CitySearchCriteria criteria, Pageable pageable) {
|
||||
|
||||
Assert.notNull(criteria, "Criteria must not be null");
|
||||
String name = criteria.getName();
|
||||
|
||||
if (!StringUtils.hasLength(name)) {
|
||||
return this.cityRepository.findAll(null);
|
||||
}
|
||||
|
||||
String country = "";
|
||||
int splitPos = name.lastIndexOf(",");
|
||||
|
||||
if (splitPos >= 0) {
|
||||
country = name.substring(splitPos + 1);
|
||||
name = name.substring(0, splitPos);
|
||||
}
|
||||
name = "%" + name.trim() + "%";
|
||||
country = "%" + country.trim() + "%";
|
||||
return this.cityRepository.findByNameLikeAndCountryLikeAllIgnoringCase(name,
|
||||
country, pageable);
|
||||
|
||||
return this.cityRepository
|
||||
.findByNameContainingAndCountryContainingAllIgnoringCase(name.trim(),
|
||||
country.trim(), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -69,16 +75,6 @@ public class CityServiceImpl implements CityService {
|
||||
@Override
|
||||
public Page<HotelSummary> getHotels(City city, Pageable pageable) {
|
||||
Assert.notNull(city, "City must not be null");
|
||||
return this.hotelSummaryRepository.findByCity(city, pageable);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setCityRepository(CityRepository cityRepository) {
|
||||
this.cityRepository = cityRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setHotelSummaryRepository(HotelSummaryRepository hotelSummaryRepository) {
|
||||
this.hotelSummaryRepository = hotelSummaryRepository;
|
||||
return this.hotelRepository.findByCity(city, pageable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.sample.data.jpa.domain.City;
|
||||
import org.springframework.boot.sample.data.jpa.domain.Hotel;
|
||||
import org.springframework.boot.sample.data.jpa.domain.HotelSummary;
|
||||
import org.springframework.boot.sample.data.jpa.domain.RatingCount;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
interface HotelRepository extends Repository<Hotel, Long> {
|
||||
|
||||
Hotel findByCityAndName(City city, String name);
|
||||
|
||||
@Query("select new org.springframework.boot.sample.data.jpa.domain.HotelSummary(h.city, h.name, avg(r.rating)) "
|
||||
+ "from Hotel h left outer join h.reviews r where h.city = ?1 group by h")
|
||||
Page<HotelSummary> findByCity(City city, Pageable pageable);
|
||||
|
||||
@Query("select new org.springframework.boot.sample.data.jpa.domain.RatingCount(r.rating, count(r)) "
|
||||
+ "from Review r where r.hotel = ?1 group by r.rating order by r.rating DESC")
|
||||
List<RatingCount> findRatingCounts(Hotel hotel);
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.service.impl;
|
||||
package org.springframework.boot.sample.data.jpa.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -27,11 +27,6 @@ import org.springframework.boot.sample.data.jpa.domain.Rating;
|
||||
import org.springframework.boot.sample.data.jpa.domain.RatingCount;
|
||||
import org.springframework.boot.sample.data.jpa.domain.Review;
|
||||
import org.springframework.boot.sample.data.jpa.domain.ReviewDetails;
|
||||
import org.springframework.boot.sample.data.jpa.domain.repository.HotelRepository;
|
||||
import org.springframework.boot.sample.data.jpa.domain.repository.HotelSummaryRepository;
|
||||
import org.springframework.boot.sample.data.jpa.domain.repository.ReviewRepository;
|
||||
import org.springframework.boot.sample.data.jpa.service.HotelService;
|
||||
import org.springframework.boot.sample.data.jpa.service.ReviewsSummary;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -40,15 +35,20 @@ import org.springframework.util.Assert;
|
||||
|
||||
@Component("hotelService")
|
||||
@Transactional
|
||||
public class HotelServiceImpl implements HotelService {
|
||||
class HotelServiceImpl implements HotelService {
|
||||
|
||||
// FIXME deal with null repository return values
|
||||
|
||||
private HotelRepository hotelRepository;
|
||||
private final HotelRepository hotelRepository;
|
||||
|
||||
private HotelSummaryRepository hotelSummaryRepository;
|
||||
private final ReviewRepository reviewRepository;
|
||||
|
||||
private ReviewRepository reviewRepository;
|
||||
@Autowired
|
||||
public HotelServiceImpl(HotelRepository hotelRepository,
|
||||
ReviewRepository reviewRepository) {
|
||||
this.hotelRepository = hotelRepository;
|
||||
this.reviewRepository = reviewRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hotel getHotel(City city, String name) {
|
||||
@@ -78,26 +78,10 @@ public class HotelServiceImpl implements HotelService {
|
||||
|
||||
@Override
|
||||
public ReviewsSummary getReviewSummary(Hotel hotel) {
|
||||
List<RatingCount> ratingCounts = this.hotelSummaryRepository
|
||||
.findRatingCounts(hotel);
|
||||
List<RatingCount> ratingCounts = this.hotelRepository.findRatingCounts(hotel);
|
||||
return new ReviewsSummaryImpl(ratingCounts);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setHotelRepository(HotelRepository hotelRepository) {
|
||||
this.hotelRepository = hotelRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setHotelSummaryRepository(HotelSummaryRepository hotelSummaryRepository) {
|
||||
this.hotelSummaryRepository = hotelSummaryRepository;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setReviewRepository(ReviewRepository reviewRepository) {
|
||||
this.reviewRepository = reviewRepository;
|
||||
}
|
||||
|
||||
private static class ReviewsSummaryImpl implements ReviewsSummary {
|
||||
|
||||
private Map<Rating, Long> ratingCount;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.sample.data.jpa.domain.repository;
|
||||
package org.springframework.boot.sample.data.jpa.service;
|
||||
|
||||
import org.springframework.boot.sample.data.jpa.domain.Hotel;
|
||||
import org.springframework.boot.sample.data.jpa.domain.Review;
|
||||
@@ -22,7 +22,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
public interface ReviewRepository extends Repository<Review, Long> {
|
||||
interface ReviewRepository extends Repository<Review, Long> {
|
||||
|
||||
Page<Review> findByHotel(Hotel hotel, Pageable pageable);
|
||||
|
||||
@@ -20,6 +20,6 @@ import org.springframework.boot.sample.data.jpa.domain.Rating;
|
||||
|
||||
public interface ReviewsSummary {
|
||||
|
||||
public long getNumberOfReviewsWithRating(Rating rating);
|
||||
long getNumberOfReviewsWithRating(Rating rating);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user