The Spring PetClinic application is a sample Spring Boot application that demonstrates the use of Spring's core features, particularly in the context of data access. Currently, it uses a relational database (H2, HSQLDB, MySQL or PostgreSQL) with Spring Data JPA.
-
Entity Model:
Owner
: Pet owners with contact informationPet
: Pets owned by ownersVisit
: Record of a pet's visit to the clinicVet
: Veterinarians working at the clinicSpecialty
: Specialties that vets may have
-
Repositories:
- JPA repositories for each entity
- Custom query methods for specific business requirements
-
Database Configuration:
- Spring Data JPA configuration
- Relational database schema with tables for each entity
- Foreign key relationships between tables
When migrating from a relational database to MongoDB, we need to rethink our data model. MongoDB's document model allows for embedded documents and references between documents.
// Owner Collection
{
_id: ObjectId("..."),
firstName: "John",
lastName: "Doe",
address: "123 Main St",
city: "Anytown",
telephone: "555-1234",
pets: [
{
_id: ObjectId("..."),
name: "Fluffy",
birthDate: ISODate("2018-01-01"),
type: {
_id: ObjectId("..."),
name: "cat"
},
visits: [
{
_id: ObjectId("..."),
date: ISODate("2023-01-15"),
description: "Annual checkup",
vet: ObjectId("...") // Reference to vet
}
]
}
]
}
// Vets Collection
{
_id: ObjectId("..."),
firstName: "Jane",
lastName: "Smith",
specialties: [
{
_id: ObjectId("..."),
name: "radiology"
},
{
_id: ObjectId("..."),
name: "surgery"
}
]
}
// PetTypes Collection (reference data)
{
_id: ObjectId("..."),
name: "cat"
}
// Specialties Collection (reference data)
{
_id: ObjectId("..."),
name: "radiology"
}
Design Decisions:
-
Embedding vs. Referencing:
- Pet documents are embedded within Owner documents since they have a strong parent-child relationship and are often accessed together.
- Visit documents are embedded within Pet documents for the same reason.
- Vet references in Visit documents use references rather than embedding to avoid data duplication.
- PetTypes and Specialties are stored in separate collections as they're reference data.
-
Indexing Strategy:
- Create indexes on commonly queried fields like Owner.lastName, Owner.telephone
- Create compound indexes for multi-field queries
- Replace JPA annotations with MongoDB annotations:
File: src/main/java/org/springframework/samples/petclinic/owner/Owner.java
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.annotation.Id;
@Document(collection = "owners")
public class Owner {
@Id
private String id;
@Field("first_name")
private String firstName;
@Field("last_name")
private String lastName;
// Other fields...
// Embed pets directly
private List<Pet> pets;
}
File: src/main/java/org/springframework/samples/petclinic/owner/Pet.java
// No @Document annotation needed as it will be embedded
public class Pet {
@Id
private String id;
private String name;
@Field("birth_date")
private LocalDate birthDate;
// Reference to PetType
@DBRef
private PetType type;
// Embed visits
private List<Visit> visits;
}
File: src/main/java/org/springframework/samples/petclinic/visit/Visit.java
// No @Document annotation needed as it will be embedded
public class Visit {
@Id
private String id;
private LocalDate date;
private String description;
// Reference to Vet
@DBRef
private Vet vet;
}
File: src/main/java/org/springframework/samples/petclinic/vet/Vet.java
@Document(collection = "vets")
public class Vet {
@Id
private String id;
@Field("first_name")
private String firstName;
@Field("last_name")
private String lastName;
@DBRef
private Set<Specialty> specialties;
}
- Replace JPA repositories with MongoDB repositories:
File: src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;
public interface OwnerRepository extends MongoRepository<Owner, String> {
// Find owners by last name
@Query("{ 'lastName': { $regex: ?0, $options: 'i' } }")
Collection<Owner> findByLastName(String lastName);
// Find owner by id
@Override
Optional<Owner> findById(String id);
}
File: src/main/java/org/springframework/samples/petclinic/vet/VetRepository.java
import org.springframework.data.mongodb.repository.MongoRepository;
public interface VetRepository extends MongoRepository<Vet, String> {
}
- Update Spring configuration for MongoDB:
File: src/main/resources/application.properties
# MongoDB Configuration
spring.data.mongodb.uri=mongodb://localhost:27017/petclinic
# Disable JPA
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
- Create MongoDB Configuration class:
File: src/main/java/org/springframework/samples/petclinic/config/MongoConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@Configuration
@EnableMongoRepositories(basePackages = "org.springframework.samples.petclinic")
public class MongoConfig extends AbstractMongoClientConfiguration {
@Override
protected String getDatabaseName() {
return "petclinic";
}
}
- Create MongoDB initialization script:
File: src/main/resources/db/mongodb/init-mongodb.js
// Create collections and indexes
db.createCollection("owners");
db.createCollection("vets");
db.createCollection("petTypes");
db.createCollection("specialties");
// Create indexes
db.owners.createIndex({ "lastName": 1 });
db.owners.createIndex({ "telephone": 1 });
// Insert pet types
db.petTypes.insertMany([
{ name: "cat" },
{ name: "dog" },
{ name: "lizard" },
{ name: "snake" },
{ name: "bird" },
{ name: "hamster" }
]);
// Insert specialties
db.specialties.insertMany([
{ name: "radiology" },
{ name: "surgery" },
{ name: "dentistry" }
]);
- Create data import utility:
File: src/main/java/org/springframework/samples/petclinic/config/MongoDataInitializer.java
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.mongodb.core.MongoTemplate;
@Configuration
@Profile("init-mongodb")
public class MongoDataInitializer {
@Bean
public CommandLineRunner initMongoDb(MongoTemplate mongoTemplate) {
return args -> {
// Initialize MongoDB with sample data
// ...
};
}
}
- Update service classes to work with the new document model:
File: src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
// Update controller methods to handle the new document structure
// Especially for adding pets and visits which are now embedded documents
File: src/main/java/org/springframework/samples/petclinic/owner/PetController.java
// Update to handle embedded documents
-
Set up MongoDB environment:
- Install MongoDB (local or Docker)
- Configure Spring Boot to connect to MongoDB
-
Create MongoDB domain model:
- Update entity classes with MongoDB annotations
- Implement embedded document structure
-
Update repository interfaces:
- Replace JPA repositories with MongoDB repositories
- Update query methods with MongoDB queries
-
Update service layer:
- Modify service logic to handle the new document model
- Update transaction boundaries
-
Create data migration script:
- Import existing SQL data to MongoDB
-
Update tests:
- Modify integration tests to use MongoDB
- Add tests for MongoDB-specific functionality
-
Update application configuration:
- Remove JPA configuration
- Add MongoDB configuration
- Use appropriate indexes for queries
- Consider read patterns when designing embedded documents
- Batch operations for large data sets
- Create a one-time migration script to move data from SQL to MongoDB
- Validate data integrity after migration
- MongoDB supports multi-document transactions since version 4.0
- Update service layer to use MongoDB transactions where needed
Add these dependencies to the pom.xml
file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
-
Unit tests:
- Test repository methods with MongoDB test containers
- Test service methods with mocked repositories
-
Integration tests:
- Use embedded MongoDB for testing
- Test the entire flow from controllers to repositories
@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureDataMongo
class OwnerRepositoryTests {
// Integration tests with MongoDB
}