View Javadoc

1   package org.springframework.samples.petclinic.jdbc;
2   
3   import java.sql.ResultSet;
4   import java.sql.SQLException;
5   import java.util.ArrayList;
6   import java.util.Collection;
7   import java.util.List;
8   
9   import javax.sql.DataSource;
10  
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  import org.springframework.beans.factory.annotation.Autowired;
14  import org.springframework.dao.DataAccessException;
15  import org.springframework.dao.EmptyResultDataAccessException;
16  import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
17  import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
18  import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
19  import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
20  import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
21  import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
22  import org.springframework.jmx.export.annotation.ManagedOperation;
23  import org.springframework.jmx.export.annotation.ManagedResource;
24  import org.springframework.orm.ObjectRetrievalFailureException;
25  import org.springframework.samples.petclinic.Clinic;
26  import org.springframework.samples.petclinic.Owner;
27  import org.springframework.samples.petclinic.Pet;
28  import org.springframework.samples.petclinic.PetType;
29  import org.springframework.samples.petclinic.Specialty;
30  import org.springframework.samples.petclinic.Vet;
31  import org.springframework.samples.petclinic.Visit;
32  import org.springframework.samples.petclinic.util.EntityUtils;
33  import org.springframework.stereotype.Service;
34  import org.springframework.transaction.annotation.Transactional;
35  
36  /**
37   * A simple JDBC-based implementation of the {@link Clinic} interface.
38   *
39   * <p>This class uses Java 5 language features and the {@link SimpleJdbcTemplate}
40   * plus {@link SimpleJdbcInsert}. It also takes advantage of classes like
41   * {@link BeanPropertySqlParameterSource} and
42   * {@link ParameterizedBeanPropertyRowMapper} which provide automatic mapping
43   * between JavaBean properties and JDBC parameters or query results.
44   *
45   * <p>SimpleJdbcClinic is a rewrite of the AbstractJdbcClinic which was the base
46   * class for JDBC implementations of the Clinic interface for Spring 2.0.
47   *
48   * @author Ken Krebs
49   * @author Juergen Hoeller
50   * @author Rob Harrop
51   * @author Sam Brannen
52   * @author Thomas Risberg
53   * @author Mark Fisher
54   */
55  @Service
56  @ManagedResource("petclinic:type=Clinic")
57  public class SimpleJdbcClinic implements Clinic, SimpleJdbcClinicMBean {
58  
59  	private final Logger logger = LoggerFactory.getLogger(getClass());
60  
61  	private SimpleJdbcTemplate simpleJdbcTemplate;
62  
63  	private SimpleJdbcInsert insertOwner;
64  	private SimpleJdbcInsert insertPet;
65  	private SimpleJdbcInsert insertVisit;
66  
67  	private final List<Vet> vets = new ArrayList<Vet>();
68  
69  
70  	@Autowired
71  	public void init(DataSource dataSource) {
72  		this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
73  
74  		this.insertOwner = new SimpleJdbcInsert(dataSource)
75  			.withTableName("owners")
76  			.usingGeneratedKeyColumns("id");
77  		this.insertPet = new SimpleJdbcInsert(dataSource)
78  			.withTableName("pets")
79  			.usingGeneratedKeyColumns("id");
80  		this.insertVisit = new SimpleJdbcInsert(dataSource)
81  			.withTableName("visits")
82  			.usingGeneratedKeyColumns("id");
83  	}
84  
85  
86  	/**
87  	 * Refresh the cache of Vets that the Clinic is holding.
88  	 * @see org.springframework.samples.petclinic.Clinic#getVets()
89  	 */
90  	@ManagedOperation
91  	@Transactional(readOnly = true)
92  	public void refreshVetsCache() throws DataAccessException {
93  		synchronized (this.vets) {
94  			this.logger.info("Refreshing vets cache");
95  
96  			// Retrieve the list of all vets.
97  			this.vets.clear();
98  			this.vets.addAll(this.simpleJdbcTemplate.query(
99  					"SELECT id, first_name, last_name FROM vets ORDER BY last_name,first_name",
100 					ParameterizedBeanPropertyRowMapper.newInstance(Vet.class)));
101 
102 			// Retrieve the list of all possible specialties.
103 			final List<Specialty> specialties = this.simpleJdbcTemplate.query(
104 					"SELECT id, name FROM specialties",
105 					ParameterizedBeanPropertyRowMapper.newInstance(Specialty.class));
106 
107 			// Build each vet's list of specialties.
108 			for (Vet vet : this.vets) {
109 				final List<Integer> vetSpecialtiesIds = this.simpleJdbcTemplate.query(
110 						"SELECT specialty_id FROM vet_specialties WHERE vet_id=?",
111 						new ParameterizedRowMapper<Integer>() {
112 							public Integer mapRow(ResultSet rs, int row) throws SQLException {
113 								return Integer.valueOf(rs.getInt(1));
114 							}},
115 						vet.getId().intValue());
116 				for (int specialtyId : vetSpecialtiesIds) {
117 					Specialty specialty = EntityUtils.getById(specialties, Specialty.class, specialtyId);
118 					vet.addSpecialty(specialty);
119 				}
120 			}
121 		}
122 	}
123 
124 
125 	// START of Clinic implementation section *******************************
126 
127 	@Transactional(readOnly = true)
128 	public Collection<Vet> getVets() throws DataAccessException {
129 		synchronized (this.vets) {
130 			if (this.vets.isEmpty()) {
131 				refreshVetsCache();
132 			}
133 			return this.vets;
134 		}
135 	}
136 
137 	@Transactional(readOnly = true)
138 	public Collection<PetType> getPetTypes() throws DataAccessException {
139 		return this.simpleJdbcTemplate.query(
140 				"SELECT id, name FROM types ORDER BY name",
141 				ParameterizedBeanPropertyRowMapper.newInstance(PetType.class));
142 	}
143 
144 	/**
145 	 * Loads {@link Owner Owners} from the data store by last name, returning
146 	 * all owners whose last name <i>starts</i> with the given name; also loads
147 	 * the {@link Pet Pets} and {@link Visit Visits} for the corresponding
148 	 * owners, if not already loaded.
149 	 */
150 	@Transactional(readOnly = true)
151 	public Collection<Owner> findOwners(String lastName) throws DataAccessException {
152 		List<Owner> owners = this.simpleJdbcTemplate.query(
153 				"SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE last_name like ?",
154 				ParameterizedBeanPropertyRowMapper.newInstance(Owner.class),
155 				lastName + "%");
156 		loadOwnersPetsAndVisits(owners);
157 		return owners;
158 	}
159 
160 	/**
161 	 * Loads the {@link Owner} with the supplied <code>id</code>; also loads
162 	 * the {@link Pet Pets} and {@link Visit Visits} for the corresponding
163 	 * owner, if not already loaded.
164 	 */
165 	@Transactional(readOnly = true)
166 	public Owner loadOwner(int id) throws DataAccessException {
167 		Owner owner;
168 		try {
169 			owner = this.simpleJdbcTemplate.queryForObject(
170 					"SELECT id, first_name, last_name, address, city, telephone FROM owners WHERE id=?",
171 					ParameterizedBeanPropertyRowMapper.newInstance(Owner.class),
172 					id);
173 		}
174 		catch (EmptyResultDataAccessException ex) {
175 			throw new ObjectRetrievalFailureException(Owner.class, new Integer(id));
176 		}
177 		loadPetsAndVisits(owner);
178 		return owner;
179 	}
180 
181 	@Transactional(readOnly = true)
182 	public Pet loadPet(int id) throws DataAccessException {
183 		JdbcPet pet;
184 		try {
185 			pet = this.simpleJdbcTemplate.queryForObject(
186 					"SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE id=?",
187 					new JdbcPetRowMapper(),
188 					id);
189 		}
190 		catch (EmptyResultDataAccessException ex) {
191 			throw new ObjectRetrievalFailureException(Pet.class, new Integer(id));
192 		}
193 		Owner owner = loadOwner(pet.getOwnerId());
194 		owner.addPet(pet);
195 		pet.setType(EntityUtils.getById(getPetTypes(), PetType.class, pet.getTypeId()));
196 		loadVisits(pet);
197 		return pet;
198 	}
199 
200 	@Transactional
201 	public void storeOwner(Owner owner) throws DataAccessException {
202 		if (owner.isNew()) {
203 			Number newKey = this.insertOwner.executeAndReturnKey(
204 					new BeanPropertySqlParameterSource(owner));
205 			owner.setId(newKey.intValue());
206 		}
207 		else {
208 			this.simpleJdbcTemplate.update(
209 					"UPDATE owners SET first_name=:firstName, last_name=:lastName, address=:address, " +
210 					"city=:city, telephone=:telephone WHERE id=:id",
211 					new BeanPropertySqlParameterSource(owner));
212 		}
213 	}
214 
215 	@Transactional
216 	public void storePet(Pet pet) throws DataAccessException {
217 		if (pet.isNew()) {
218 			Number newKey = this.insertPet.executeAndReturnKey(
219 					createPetParameterSource(pet));
220 			pet.setId(newKey.intValue());
221 		}
222 		else {
223 			this.simpleJdbcTemplate.update(
224 					"UPDATE pets SET name=:name, birth_date=:birth_date, type_id=:type_id, " +
225 					"owner_id=:owner_id WHERE id=:id",
226 					createPetParameterSource(pet));
227 		}
228 	}
229 
230 	@Transactional
231 	public void storeVisit(Visit visit) throws DataAccessException {
232 		if (visit.isNew()) {
233 			Number newKey = this.insertVisit.executeAndReturnKey(
234 					createVisitParameterSource(visit));
235 			visit.setId(newKey.intValue());
236 		}
237 		else {
238 			throw new UnsupportedOperationException("Visit update not supported");
239 		}
240 	}
241 
242 	public void deletePet(int id) throws DataAccessException {
243 		this.simpleJdbcTemplate.update("DELETE FROM pets WHERE id=?", id);
244 	}
245 
246 	// END of Clinic implementation section ************************************
247 
248 
249 	/**
250 	 * Creates a {@link MapSqlParameterSource} based on data values from the
251 	 * supplied {@link Pet} instance.
252 	 */
253 	private MapSqlParameterSource createPetParameterSource(Pet pet) {
254 		return new MapSqlParameterSource()
255 			.addValue("id", pet.getId())
256 			.addValue("name", pet.getName())
257 			.addValue("birth_date", pet.getBirthDate())
258 			.addValue("type_id", pet.getType().getId())
259 			.addValue("owner_id", pet.getOwner().getId());
260 	}
261 
262 	/**
263 	 * Creates a {@link MapSqlParameterSource} based on data values from the
264 	 * supplied {@link Visit} instance.
265 	 */
266 	private MapSqlParameterSource createVisitParameterSource(Visit visit) {
267 		return new MapSqlParameterSource()
268 			.addValue("id", visit.getId())
269 			.addValue("visit_date", visit.getDate())
270 			.addValue("description", visit.getDescription())
271 			.addValue("pet_id", visit.getPet().getId());
272 	}
273 
274 	/**
275 	 * Loads the {@link Visit} data for the supplied {@link Pet}.
276 	 */
277 	private void loadVisits(JdbcPet pet) {
278 		final List<Visit> visits = this.simpleJdbcTemplate.query(
279 				"SELECT id, visit_date, description FROM visits WHERE pet_id=?",
280 				new ParameterizedRowMapper<Visit>() {
281 					public Visit mapRow(ResultSet rs, int row) throws SQLException {
282 						Visit visit = new Visit();
283 						visit.setId(rs.getInt("id"));
284 						visit.setDate(rs.getTimestamp("visit_date"));
285 						visit.setDescription(rs.getString("description"));
286 						return visit;
287 					}
288 				},
289 				pet.getId().intValue());
290 		for (Visit visit : visits) {
291 			pet.addVisit(visit);
292 		}
293 	}
294 
295 	/**
296 	 * Loads the {@link Pet} and {@link Visit} data for the supplied
297 	 * {@link Owner}.
298 	 */
299 	private void loadPetsAndVisits(final Owner owner) {
300 		final List<JdbcPet> pets = this.simpleJdbcTemplate.query(
301 				"SELECT id, name, birth_date, type_id, owner_id FROM pets WHERE owner_id=?",
302 				new JdbcPetRowMapper(),
303 				owner.getId().intValue());
304 		for (JdbcPet pet : pets) {
305 			owner.addPet(pet);
306 			pet.setType(EntityUtils.getById(getPetTypes(), PetType.class, pet.getTypeId()));
307 			loadVisits(pet);
308 		}
309 	}
310 
311 	/**
312 	 * Loads the {@link Pet} and {@link Visit} data for the supplied
313 	 * {@link List} of {@link Owner Owners}.
314 	 *
315 	 * @param owners the list of owners for whom the pet and visit data should be loaded
316 	 * @see #loadPetsAndVisits(Owner)
317 	 */
318 	private void loadOwnersPetsAndVisits(List<Owner> owners) {
319 		for (Owner owner : owners) {
320 			loadPetsAndVisits(owner);
321 		}
322 	}
323 
324 	/**
325 	 * {@link ParameterizedRowMapper} implementation mapping data from a
326 	 * {@link ResultSet} to the corresponding properties of the {@link JdbcPet} class.
327 	 */
328 	private class JdbcPetRowMapper implements ParameterizedRowMapper<JdbcPet> {
329 
330 		public JdbcPet mapRow(ResultSet rs, int rownum) throws SQLException {
331 			JdbcPet pet = new JdbcPet();
332 			pet.setId(rs.getInt("id"));
333 			pet.setName(rs.getString("name"));
334 			pet.setBirthDate(rs.getDate("birth_date"));
335 			pet.setTypeId(rs.getInt("type_id"));
336 			pet.setOwnerId(rs.getInt("owner_id"));
337 			return pet;
338 		}
339 	}
340 
341 }