Browse Source
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.pull/10/head
12 changed files with 243 additions and 259 deletions
@ -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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -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); |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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; |
||||||
|
|
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.springframework.boot.SpringApplication; |
||||||
|
import org.springframework.boot.context.initializer.ConfigFileApplicationContextInitializer; |
||||||
|
import org.springframework.test.context.ContextConfiguration; |
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; |
||||||
|
import org.springframework.test.context.web.WebAppConfiguration; |
||||||
|
|
||||||
|
/** |
||||||
|
* Base class for integration tests. Mimics the behavior of |
||||||
|
* {@link SpringApplication#run(String...)}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
@RunWith(SpringJUnit4ClassRunner.class) |
||||||
|
@WebAppConfiguration |
||||||
|
@ContextConfiguration(classes = SampleDataJpaApplication.class, initializers = ConfigFileApplicationContextInitializer.class) |
||||||
|
public abstract class AbstractIntegrationTests { |
||||||
|
|
||||||
|
} |
||||||
@ -1,87 +1,37 @@ |
|||||||
/* |
|
||||||
* 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; |
package org.springframework.boot.sample.data.jpa; |
||||||
|
|
||||||
import java.io.IOException; |
import org.junit.Before; |
||||||
import java.util.concurrent.Callable; |
|
||||||
import java.util.concurrent.Executors; |
|
||||||
import java.util.concurrent.Future; |
|
||||||
import java.util.concurrent.TimeUnit; |
|
||||||
|
|
||||||
import org.junit.AfterClass; |
|
||||||
import org.junit.BeforeClass; |
|
||||||
import org.junit.Test; |
import org.junit.Test; |
||||||
import org.springframework.boot.SpringApplication; |
import org.springframework.beans.factory.annotation.Autowired; |
||||||
import org.springframework.context.ConfigurableApplicationContext; |
import org.springframework.test.web.servlet.MockMvc; |
||||||
import org.springframework.http.HttpStatus; |
import org.springframework.test.web.servlet.setup.MockMvcBuilders; |
||||||
import org.springframework.http.ResponseEntity; |
import org.springframework.web.context.WebApplicationContext; |
||||||
import org.springframework.http.client.ClientHttpResponse; |
|
||||||
import org.springframework.web.client.DefaultResponseErrorHandler; |
|
||||||
import org.springframework.web.client.RestTemplate; |
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals; |
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; |
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||||
|
|
||||||
/** |
/** |
||||||
* Basic integration tests for service demo application. |
* Integration test to run the application. |
||||||
* |
* |
||||||
* @author Dave Syer |
* @author Oliver Gierke |
||||||
*/ |
*/ |
||||||
public class SampleDataJpaApplicationTests { |
public class SampleDataJpaApplicationTests extends AbstractIntegrationTests { |
||||||
|
|
||||||
private static ConfigurableApplicationContext context; |
@Autowired |
||||||
|
private WebApplicationContext context; |
||||||
|
|
||||||
@BeforeClass |
private MockMvc mvc; |
||||||
public static void start() throws Exception { |
|
||||||
Future<ConfigurableApplicationContext> future = Executors |
|
||||||
.newSingleThreadExecutor().submit( |
|
||||||
new Callable<ConfigurableApplicationContext>() { |
|
||||||
@Override |
|
||||||
public ConfigurableApplicationContext call() throws Exception { |
|
||||||
return (ConfigurableApplicationContext) SpringApplication |
|
||||||
.run(SampleDataJpaApplication.class); |
|
||||||
} |
|
||||||
}); |
|
||||||
context = future.get(30, TimeUnit.SECONDS); |
|
||||||
} |
|
||||||
|
|
||||||
@AfterClass |
@Before |
||||||
public static void stop() { |
public void setUp() { |
||||||
if (context != null) { |
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); |
||||||
context.close(); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
@Test |
@Test |
||||||
public void testHome() throws Exception { |
public void testHome() throws Exception { |
||||||
ResponseEntity<String> entity = getRestTemplate().getForEntity( |
|
||||||
"http://localhost:8080", String.class); |
|
||||||
assertEquals(HttpStatus.OK, entity.getStatusCode()); |
|
||||||
assertEquals("Bath", entity.getBody()); |
|
||||||
} |
|
||||||
|
|
||||||
private RestTemplate getRestTemplate() { |
|
||||||
RestTemplate restTemplate = new RestTemplate(); |
|
||||||
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { |
|
||||||
@Override |
|
||||||
public void handleError(ClientHttpResponse response) throws IOException { |
|
||||||
} |
|
||||||
}); |
|
||||||
return restTemplate; |
|
||||||
|
|
||||||
|
this.mvc.perform(get("/")).andExpect(status().isOk()) |
||||||
|
.andExpect(content().string("Bath")); |
||||||
} |
} |
||||||
|
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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 org.junit.Test; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.boot.sample.data.jpa.AbstractIntegrationTests; |
||||||
|
import org.springframework.boot.sample.data.jpa.domain.City; |
||||||
|
import org.springframework.data.domain.Page; |
||||||
|
import org.springframework.data.domain.PageRequest; |
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is; |
||||||
|
import static org.junit.Assert.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Integration tests for {@link CityRepository}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public class CityRepositoryIntegrationTests extends AbstractIntegrationTests { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
CityRepository repository; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void findsFirstPageOfCities() { |
||||||
|
|
||||||
|
Page<City> cities = this.repository.findAll(new PageRequest(0, 10)); |
||||||
|
assertThat(cities.getTotalElements(), is(21L)); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
/* |
||||||
|
* Copyright 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.junit.Test; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.boot.sample.data.jpa.AbstractIntegrationTests; |
||||||
|
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.Rating; |
||||||
|
import org.springframework.boot.sample.data.jpa.domain.RatingCount; |
||||||
|
import org.springframework.data.domain.Page; |
||||||
|
import org.springframework.data.domain.PageRequest; |
||||||
|
import org.springframework.data.domain.Sort.Direction; |
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.hasSize; |
||||||
|
import static org.hamcrest.Matchers.is; |
||||||
|
import static org.junit.Assert.assertThat; |
||||||
|
|
||||||
|
/** |
||||||
|
* Integration tests for {@link HotelRepository}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public class HotelRepositoryIntegrationTests extends AbstractIntegrationTests { |
||||||
|
|
||||||
|
@Autowired |
||||||
|
CityRepository cityRepository; |
||||||
|
@Autowired |
||||||
|
HotelRepository repository; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void executesQueryMethodsCorrectly() { |
||||||
|
City city = this.cityRepository |
||||||
|
.findAll(new PageRequest(0, 1, Direction.ASC, "name")).getContent() |
||||||
|
.get(0); |
||||||
|
assertThat(city.getName(), is("Atlanta")); |
||||||
|
|
||||||
|
Page<HotelSummary> hotels = this.repository.findByCity(city, new PageRequest(0, |
||||||
|
10, Direction.ASC, "name")); |
||||||
|
Hotel hotel = this.repository.findByCityAndName(city, hotels.getContent().get(0) |
||||||
|
.getName()); |
||||||
|
assertThat(hotel.getName(), is("Doubletree")); |
||||||
|
|
||||||
|
List<RatingCount> counts = this.repository.findRatingCounts(hotel); |
||||||
|
assertThat(counts, hasSize(1)); |
||||||
|
assertThat(counts.get(0).getRating(), is(Rating.AVERAGE)); |
||||||
|
assertThat(counts.get(0).getCount(), is(2L)); |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue