Skip to content
Snippets Groups Projects
Commit 83ff9a50 authored by Dave Syer's avatar Dave Syer
Browse files

Modularize and migrate to aggregate-oriented domain

Vet, Owner, Visit. The Visit "aggregate" is a little artificial
but it demonstrates a useful point about not holding on to
references of "parent" (reference data) objects, i.e. the Visit has
an Integer petId, instead of a Pet field. In principle this app is
now almost ready to migrate to multiple services if anyone wanted
to do that.
parent 8c859929
No related branches found
No related tags found
No related merge requests found
Showing
with 94 additions and 250 deletions
......@@ -21,6 +21,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* PetClinic Spring Boot Application.
*
* @author Dave Syer
*
*/
@SpringBootApplication
......
/**
* The classes in this package represent PetClinic's business layer.
*/
package org.springframework.samples.petclinic.model;
/**
* The classes in this package represent utilities used by the domain.
*/
package org.springframework.samples.petclinic.model;
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
package org.springframework.samples.petclinic.owner;
import java.util.ArrayList;
import java.util.Collections;
......@@ -32,6 +32,7 @@ import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator;
import org.springframework.core.style.ToStringCreator;
import org.springframework.samples.petclinic.model.Person;
/**
* Simple JavaBean domain object representing an owner.
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.owner;
import java.util.Collection;
import java.util.Map;
......@@ -21,8 +21,6 @@ import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
......@@ -40,15 +38,15 @@ import org.springframework.web.servlet.ModelAndView;
* @author Michael Isvy
*/
@Controller
public class OwnerController {
class OwnerController {
private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
private final ClinicService clinicService;
private final OwnerRepository owners;
@Autowired
public OwnerController(ClinicService clinicService) {
this.clinicService = clinicService;
public OwnerController(OwnerRepository clinicService) {
this.owners = clinicService;
}
@InitBinder
......@@ -68,7 +66,7 @@ public class OwnerController {
if (result.hasErrors()) {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} else {
this.clinicService.saveOwner(owner);
this.owners.save(owner);
return "redirect:/owners/" + owner.getId();
}
}
......@@ -88,7 +86,7 @@ public class OwnerController {
}
// find owners by last name
Collection<Owner> results = this.clinicService.findOwnerByLastName(owner.getLastName());
Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
if (results.isEmpty()) {
// no owners found
result.rejectValue("lastName", "notFound", "not found");
......@@ -106,7 +104,7 @@ public class OwnerController {
@RequestMapping(value = "/owners/{ownerId}/edit", method = RequestMethod.GET)
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
Owner owner = this.clinicService.findOwnerById(ownerId);
Owner owner = this.owners.findById(ownerId);
model.addAttribute(owner);
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
}
......@@ -117,7 +115,7 @@ public class OwnerController {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} else {
owner.setId(ownerId);
this.clinicService.saveOwner(owner);
this.owners.save(owner);
return "redirect:/owners/{ownerId}";
}
}
......@@ -131,7 +129,7 @@ public class OwnerController {
@RequestMapping("/owners/{ownerId}")
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
ModelAndView mav = new ModelAndView("owners/ownerDetails");
mav.addObject(this.clinicService.findOwnerById(ownerId));
mav.addObject(this.owners.findById(ownerId));
return mav;
}
......
......@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.repository;
package org.springframework.samples.petclinic.owner;
import java.util.Collection;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.query.Param;
import org.springframework.samples.petclinic.model.Owner;
import org.springframework.transaction.annotation.Transactional;
/**
* Repository class for <code>Owner</code> domain objects All method names are compliant with Spring Data naming
......@@ -41,6 +41,7 @@ public interface OwnerRepository extends Repository<Owner, Integer> {
* found)
*/
@Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%")
@Transactional(readOnly = true)
Collection<Owner> findByLastName(@Param("lastName") String lastName);
/**
......@@ -49,6 +50,7 @@ public interface OwnerRepository extends Repository<Owner, Integer> {
* @return the {@link Owner} if found
*/
@Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id")
@Transactional(readOnly = true)
Owner findById(@Param("id") Integer id);
/**
......
......@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
package org.springframework.samples.petclinic.owner;
import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.samples.petclinic.model.NamedEntity;
import org.springframework.samples.petclinic.visit.Visit;
import javax.persistence.CascadeType;
import javax.persistence.Column;
......@@ -62,10 +64,9 @@ public class Pet extends NamedEntity {
@JoinColumn(name = "owner_id")
private Owner owner;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "pet", fetch = FetchType.EAGER)
@OneToMany(cascade = CascadeType.ALL, mappedBy="petId", fetch = FetchType.EAGER)
private Set<Visit> visits = new LinkedHashSet<>();
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
......@@ -109,11 +110,7 @@ public class Pet extends NamedEntity {
public void addVisit(Visit visit) {
getVisitsInternal().add(visit);
visit.setPet(this);
visit.setPetId(this.id);
}
}
......@@ -13,23 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.owner;
import java.util.Collection;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Collection;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @author Juergen Hoeller
......@@ -38,24 +38,26 @@ import java.util.Collection;
*/
@Controller
@RequestMapping("/owners/{ownerId}")
public class PetController {
class PetController {
private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
private final ClinicService clinicService;
private final PetRepository pets;
private final OwnerRepository owners;
@Autowired
public PetController(ClinicService clinicService) {
this.clinicService = clinicService;
public PetController(PetRepository pets, OwnerRepository owners) {
this.pets = pets;
this.owners = owners;
}
@ModelAttribute("types")
public Collection<PetType> populatePetTypes() {
return this.clinicService.findPetTypes();
return this.pets.findPetTypes();
}
@ModelAttribute("owner")
public Owner findOwner(@PathVariable("ownerId") int ownerId) {
return this.clinicService.findOwnerById(ownerId);
return this.owners.findById(ownerId);
}
@InitBinder("owner")
......@@ -86,14 +88,14 @@ public class PetController {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else {
owner.addPet(pet);
this.clinicService.savePet(pet);
this.pets.save(pet);
return "redirect:/owners/{ownerId}";
}
}
@RequestMapping(value = "/pets/{petId}/edit", method = RequestMethod.GET)
public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) {
Pet pet = this.clinicService.findPetById(petId);
Pet pet = this.pets.findById(petId);
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
......@@ -105,7 +107,7 @@ public class PetController {
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else {
owner.addPet(pet);
this.clinicService.savePet(pet);
this.pets.save(pet);
return "redirect:/owners/{ownerId}";
}
}
......
......@@ -13,14 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.repository;
package org.springframework.samples.petclinic.owner;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType;
import org.springframework.transaction.annotation.Transactional;
/**
* Repository class for <code>Pet</code> domain objects All method names are compliant with Spring Data naming
......@@ -38,6 +37,7 @@ public interface PetRepository extends Repository<Pet, Integer> {
* @return a Collection of {@link PetType}s.
*/
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
@Transactional(readOnly = true)
List<PetType> findPetTypes();
/**
......@@ -45,6 +45,7 @@ public interface PetRepository extends Repository<Pet, Integer> {
* @param id the id to search for
* @return the {@link Pet} if found
*/
@Transactional(readOnly = true)
Pet findById(Integer id);
/**
......
......@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
package org.springframework.samples.petclinic.owner;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity;
/**
* @author Juergen Hoeller
* Can be Cat, Dog, Hamster...
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.owner;
import java.text.ParseException;
......@@ -22,8 +22,6 @@ import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.Formatter;
import org.springframework.samples.petclinic.model.PetType;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.stereotype.Component;
/**
......@@ -32,7 +30,6 @@ import org.springframework.stereotype.Component;
* Spring ref doc: http://static.springsource.org/spring/docs/current/spring-framework-reference/html/validation.html#format-Formatter-SPI
* - A nice blog entry from Gordon Dickens: http://gordondickens.com/wordpress/2010/09/30/using-spring-3-0-custom-type-converter/
* <p/>
* Also see how the bean 'conversionService' has been declared inside /WEB-INF/mvc-core-config.xml
*
* @author Mark Fisher
* @author Juergen Hoeller
......@@ -41,12 +38,12 @@ import org.springframework.stereotype.Component;
@Component
public class PetTypeFormatter implements Formatter<PetType> {
private final ClinicService clinicService;
private final PetRepository pets;
@Autowired
public PetTypeFormatter(ClinicService clinicService) {
this.clinicService = clinicService;
public PetTypeFormatter(PetRepository pets) {
this.pets = pets;
}
@Override
......@@ -56,7 +53,7 @@ public class PetTypeFormatter implements Formatter<PetType> {
@Override
public PetType parse(String text, Locale locale) throws ParseException {
Collection<PetType> findPetTypes = this.clinicService.findPetTypes();
Collection<PetType> findPetTypes = this.pets.findPetTypes();
for (PetType type : findPetTypes) {
if (type.getName().equals(text)) {
return type;
......
......@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.owner;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
......
......@@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.owner;
import java.util.Map;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.Visit;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.samples.petclinic.visit.Visit;
import org.springframework.samples.petclinic.visit.VisitRepository;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
......@@ -37,16 +36,19 @@ import org.springframework.web.bind.annotation.RequestMethod;
* @author Ken Krebs
* @author Arjen Poutsma
* @author Michael Isvy
* @author Dave Syer
*/
@Controller
public class VisitController {
class VisitController {
private final ClinicService clinicService;
private final VisitRepository visits;
private final PetRepository pets;
@Autowired
public VisitController(ClinicService clinicService) {
this.clinicService = clinicService;
public VisitController(VisitRepository visits, PetRepository pets) {
this.visits = visits;
this.pets = pets;
}
@InitBinder
......@@ -65,8 +67,9 @@ public class VisitController {
* @return Pet
*/
@ModelAttribute("visit")
public Visit loadPetWithVisit(@PathVariable("petId") int petId) {
Pet pet = this.clinicService.findPetById(petId);
public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) {
Pet pet = this.pets.findById(petId);
model.put("pet", pet);
Visit visit = new Visit();
pet.addVisit(visit);
return visit;
......@@ -84,7 +87,7 @@ public class VisitController {
if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm";
} else {
this.clinicService.saveVisit(visit);
this.visits.save(visit);
return "redirect:/owners/{ownerId}";
}
}
......
/*
* Copyright 2002-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.samples.petclinic.service;
import java.util.Collection;
import org.springframework.dao.DataAccessException;
import org.springframework.samples.petclinic.model.Owner;
import org.springframework.samples.petclinic.model.Pet;
import org.springframework.samples.petclinic.model.PetType;
import org.springframework.samples.petclinic.model.Vet;
import org.springframework.samples.petclinic.model.Visit;
/**
* Mostly used as a facade so all controllers have a single point of entry
*
* @author Michael Isvy
*/
public interface ClinicService {
Collection<PetType> findPetTypes() throws DataAccessException;
Owner findOwnerById(int id) throws DataAccessException;
Pet findPetById(int id) throws DataAccessException;
void savePet(Pet pet) throws DataAccessException;
void saveVisit(Visit visit) throws DataAccessException;
Collection<Vet> findVets() throws DataAccessException;
void saveOwner(Owner owner) throws DataAccessException;
Collection<Owner> findOwnerByLastName(String lastName) throws DataAccessException;
Collection<Visit> findVisitsByPetId(int petId);
}
/*
* Copyright 2002-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.samples.petclinic.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.samples.petclinic.model.*;
import org.springframework.samples.petclinic.repository.OwnerRepository;
import org.springframework.samples.petclinic.repository.PetRepository;
import org.springframework.samples.petclinic.repository.VetRepository;
import org.springframework.samples.petclinic.repository.VisitRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.cache.annotation.CacheResult;
import java.util.Collection;
/**
* Mostly used as a facade for all Petclinic controllers Also a placeholder
* for @Transactional and @CacheResult annotations
*
* @author Michael Isvy
*/
@Service
public class ClinicServiceImpl implements ClinicService {
private PetRepository petRepository;
private VetRepository vetRepository;
private OwnerRepository ownerRepository;
private VisitRepository visitRepository;
@Autowired
public ClinicServiceImpl(PetRepository petRepository, VetRepository vetRepository, OwnerRepository ownerRepository,
VisitRepository visitRepository) {
this.petRepository = petRepository;
this.vetRepository = vetRepository;
this.ownerRepository = ownerRepository;
this.visitRepository = visitRepository;
}
@Override
@Transactional(readOnly = true)
public Collection<PetType> findPetTypes() throws DataAccessException {
return petRepository.findPetTypes();
}
@Override
@Transactional(readOnly = true)
public Owner findOwnerById(int id) throws DataAccessException {
return ownerRepository.findById(id);
}
@Override
@Transactional(readOnly = true)
public Collection<Owner> findOwnerByLastName(String lastName) throws DataAccessException {
return ownerRepository.findByLastName(lastName);
}
@Override
@Transactional
public void saveOwner(Owner owner) throws DataAccessException {
ownerRepository.save(owner);
}
@Override
@Transactional
public void saveVisit(Visit visit) throws DataAccessException {
visitRepository.save(visit);
}
@Override
@Transactional(readOnly = true)
public Pet findPetById(int id) throws DataAccessException {
return petRepository.findById(id);
}
@Override
@Transactional
public void savePet(Pet pet) throws DataAccessException {
petRepository.save(pet);
}
@Override
@Transactional(readOnly = true)
@CacheResult(cacheName = "vets")
public Collection<Vet> findVets() throws DataAccessException {
return vetRepository.findAll();
}
@Override
public Collection<Visit> findVisitsByPetId(int petId) {
return visitRepository.findByPetId(petId);
}
}
package org.springframework.samples.petclinic.config;
package org.springframework.samples.petclinic.system;
import java.util.concurrent.TimeUnit;
import javax.cache.CacheManager;
......@@ -23,7 +23,7 @@ import org.springframework.context.annotation.Profile;
@Configuration
@EnableCaching
@Profile("production")
public class CacheConfig {
class CacheConfig {
@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.system;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -27,7 +27,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
* Also see how a view that resolves to "error" has been added ("error.html").
*/
@Controller
public class CrashController {
class CrashController {
@RequestMapping(value = "/oups", method = RequestMethod.GET)
public String triggerException() {
......
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.system;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class WelcomeController {
class WelcomeController {
@RequestMapping("/")
public String welcome() {
......
......@@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
package org.springframework.samples.petclinic.vet;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity;
/**
* Models a {@link Vet Vet's} specialty (for example, dentistry).
*
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.model;
package org.springframework.samples.petclinic.vet;
import java.util.ArrayList;
import java.util.Collections;
......@@ -31,6 +31,7 @@ import javax.xml.bind.annotation.XmlElement;
import org.springframework.beans.support.MutableSortDefinition;
import org.springframework.beans.support.PropertyComparator;
import org.springframework.samples.petclinic.model.Person;
/**
* Simple JavaBean domain object representing a veterinarian.
......
......@@ -13,13 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.web;
package org.springframework.samples.petclinic.vet;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.samples.petclinic.model.Vets;
import org.springframework.samples.petclinic.service.ClinicService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
......@@ -31,13 +29,13 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Arjen Poutsma
*/
@Controller
public class VetController {
class VetController {
private final ClinicService clinicService;
private final VetRepository vets;
@Autowired
public VetController(ClinicService clinicService) {
this.clinicService = clinicService;
public VetController(VetRepository clinicService) {
this.vets = clinicService;
}
@RequestMapping(value = { "/vets.html" })
......@@ -45,7 +43,7 @@ public class VetController {
// Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for Object-Xml mapping
Vets vets = new Vets();
vets.getVetList().addAll(this.clinicService.findVets());
vets.getVetList().addAll(this.vets.findAll());
model.put("vets", vets);
return "vets/vetList";
}
......@@ -55,7 +53,7 @@ public class VetController {
// Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for JSon/Object mapping
Vets vets = new Vets();
vets.getVetList().addAll(this.clinicService.findVets());
vets.getVetList().addAll(this.vets.findAll());
return vets;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment