Skip to content

Commit 6ab78d3

Browse files
committed
feat: migrate from in-memory storage to Spring Data JPA with H2 database
- Add spring-data-jpa dependency and H2 database configuration - Create JPA entities: PetEntity, CategoryEntity, TagEntity with proper relationships - Implement repository layer with custom queries for random selection - Add service layer for business logic and entity-to-model conversion - Refactor controllers to use services instead of in-memory storage - Create DataLoader component for startup data initialization (50 sample pets) - Fix JPQL query property name after entity refactoring All REST endpoints remain unchanged while gaining data persistence and proper database relationships.
1 parent 4a32491 commit 6ab78d3

File tree

15 files changed

+639
-153
lines changed

15 files changed

+639
-153
lines changed

build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import org.springframework.boot.gradle.tasks.run.BootRun
2-
31
plugins {
42
application
53
id("org.openapi.generator") version "7.10.0"
@@ -18,6 +16,8 @@ dependencies {
1816
implementation(libs.spring.boot.starter.web)
1917
implementation(libs.spring.boot.starter.webflux)
2018
implementation(libs.spring.boot.starter.security)
19+
implementation("org.springframework.boot:spring-boot-starter-data-jpa:3.4.3")
20+
runtimeOnly("com.h2database:h2:2.2.224")
2121
testImplementation(libs.spring.boot.starter.test)
2222

2323
// Jackson dependencies - required for OpenAPI client
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.sourcegraph.petstore.config;
2+
3+
import com.sourcegraph.petstore.entity.CategoryEntity;
4+
import com.sourcegraph.petstore.entity.PetEntity;
5+
import com.sourcegraph.petstore.entity.TagEntity;
6+
import com.sourcegraph.petstore.repository.CategoryRepository;
7+
import com.sourcegraph.petstore.repository.PetRepository;
8+
import com.sourcegraph.petstore.repository.TagRepository;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.ApplicationArguments;
11+
import org.springframework.boot.ApplicationRunner;
12+
import org.springframework.stereotype.Component;
13+
14+
import java.util.Arrays;
15+
import java.util.List;
16+
import java.util.Random;
17+
import java.util.stream.Collectors;
18+
import java.util.stream.IntStream;
19+
20+
@Component
21+
public class DataLoader implements ApplicationRunner {
22+
23+
private final PetRepository petRepository;
24+
private final CategoryRepository categoryRepository;
25+
private final TagRepository tagRepository;
26+
private final Random random = new Random();
27+
28+
private final List<String> petNames = Arrays.asList(
29+
"Buddy", "Max", "Charlie", "Lucy", "Cooper", "Bella", "Luna", "Daisy",
30+
"Rocky", "Sadie", "Milo", "Bailey", "Jack", "Oliver", "Chloe", "Pepper",
31+
"Zeus", "Rex", "Duke", "Bear", "Tucker", "Murphy", "Bentley", "Gus",
32+
"Oscar", "Louie", "Felix", "Simba", "Jasper", "Buster", "Toby", "Finn"
33+
);
34+
35+
private final List<String> categories = Arrays.asList(
36+
"Dog", "Cat", "Bird", "Fish", "Reptile", "Rodent", "Exotic"
37+
);
38+
39+
private final List<String> tagNames = Arrays.asList(
40+
"Friendly", "Playful", "Trained", "Young", "Adult", "Senior",
41+
"Vaccinated", "Neutered", "Spayed", "Rescue", "Purebred", "Hypoallergenic",
42+
"Energetic", "Calm", "Social", "Independent", "Loyal", "Protective"
43+
);
44+
45+
@Autowired
46+
public DataLoader(PetRepository petRepository, CategoryRepository categoryRepository, TagRepository tagRepository) {
47+
this.petRepository = petRepository;
48+
this.categoryRepository = categoryRepository;
49+
this.tagRepository = tagRepository;
50+
}
51+
52+
@Override
53+
public void run(ApplicationArguments args) throws Exception {
54+
if (categoryRepository.count() == 0 && tagRepository.count() == 0 && petRepository.count() == 0) {
55+
loadSampleData();
56+
}
57+
}
58+
59+
private void loadSampleData() {
60+
System.out.println("Loading sample data...");
61+
62+
// Load categories first
63+
List<CategoryEntity> categoryEntities = categories.stream()
64+
.map(CategoryEntity::new)
65+
.collect(Collectors.toList());
66+
categoryRepository.saveAll(categoryEntities);
67+
System.out.println("Loaded " + categoryEntities.size() + " categories");
68+
69+
// Load tags
70+
List<TagEntity> tagEntities = tagNames.stream()
71+
.map(TagEntity::new)
72+
.collect(Collectors.toList());
73+
tagRepository.saveAll(tagEntities);
74+
System.out.println("Loaded " + tagEntities.size() + " tags");
75+
76+
// Load pets
77+
List<PetEntity> pets = IntStream.range(0, 50)
78+
.mapToObj(i -> createRandomPetEntity())
79+
.collect(Collectors.toList());
80+
81+
petRepository.saveAll(pets);
82+
System.out.println("Loaded " + pets.size() + " pets");
83+
84+
System.out.println("Sample data loading completed!");
85+
}
86+
87+
private PetEntity createRandomPetEntity() {
88+
CategoryEntity category = getRandomExistingCategory();
89+
List<TagEntity> tags = getRandomExistingTags();
90+
91+
PetEntity pet = new PetEntity();
92+
pet.setName(getRandomElement(petNames));
93+
pet.setCategory(category);
94+
pet.setPhotoUrls(generatePhotoUrlsForCategory(category.getName()));
95+
pet.setTags(tags);
96+
pet.setStatus(getRandomEntityStatus());
97+
98+
return pet;
99+
}
100+
101+
private List<String> generatePhotoUrlsForCategory(String categoryName) {
102+
int count = random.nextInt(3) + 1;
103+
return IntStream.range(0, count)
104+
.mapToObj(i -> "/images/" + categoryName.toLowerCase() + "_" + (i + 1) + ".jpg")
105+
.collect(Collectors.toList());
106+
}
107+
108+
private CategoryEntity getRandomExistingCategory() {
109+
List<CategoryEntity> allCategories = categoryRepository.findAll();
110+
return getRandomElement(allCategories);
111+
}
112+
113+
private List<TagEntity> getRandomExistingTags() {
114+
List<TagEntity> allTags = tagRepository.findAll();
115+
int count = random.nextInt(4) + 1;
116+
return IntStream.range(0, count)
117+
.mapToObj(i -> getRandomElement(allTags))
118+
.distinct()
119+
.collect(Collectors.toList());
120+
}
121+
122+
private PetEntity.PetStatus getRandomEntityStatus() {
123+
PetEntity.PetStatus[] statuses = PetEntity.PetStatus.values();
124+
return statuses[random.nextInt(statuses.length)];
125+
}
126+
127+
private <T> T getRandomElement(List<T> list) {
128+
return list.get(random.nextInt(list.size()));
129+
}
130+
}
Lines changed: 15 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,58 @@
11
package com.sourcegraph.petstore.controller;
22

33
import com.sourcegraph.petstore.openapi.generated.model.Category;
4+
import com.sourcegraph.petstore.service.CategoryService;
5+
import org.springframework.beans.factory.annotation.Autowired;
46
import org.springframework.http.HttpStatus;
57
import org.springframework.web.bind.annotation.*;
68

7-
import java.util.*;
8-
import java.util.concurrent.ConcurrentHashMap;
9-
import java.util.concurrent.atomic.AtomicLong;
10-
import java.util.stream.Collectors;
11-
import java.util.stream.IntStream;
9+
import java.util.List;
1210

1311
@RestController
1412
@RequestMapping("/api/categories")
1513
public class CategoryController {
1614

17-
private final Map<Long, Category> categoryStore = new ConcurrentHashMap<>();
18-
private final AtomicLong idCounter = new AtomicLong(1);
19-
private final Random random = new Random();
15+
private final CategoryService categoryService;
2016

21-
private final List<String> petCategories = Arrays.asList(
22-
"Dog", "Cat", "Bird", "Fish", "Reptile", "Amphibian", "Small Mammal",
23-
"Exotic", "Farm Animal", "Insect", "Arachnid"
24-
);
17+
@Autowired
18+
public CategoryController(CategoryService categoryService) {
19+
this.categoryService = categoryService;
20+
}
2521

2622
@GetMapping
2723
public List<Category> getAllCategories() {
28-
return new ArrayList<>(categoryStore.values());
24+
return categoryService.getAllCategories();
2925
}
3026

3127
@GetMapping("/{id}")
3228
public Category getCategoryById(@PathVariable Long id) {
33-
return categoryStore.get(id);
29+
return categoryService.getCategoryById(id);
3430
}
3531

3632
@PostMapping
3733
@ResponseStatus(HttpStatus.CREATED)
3834
public Category createCategory(@RequestBody Category category) {
39-
Long id = idCounter.getAndIncrement();
40-
category.setId(id);
41-
categoryStore.put(id, category);
42-
return category;
35+
return categoryService.createCategory(category);
4336
}
4437

4538
@PutMapping("/{id}")
4639
public Category updateCategory(@PathVariable Long id, @RequestBody Category category) {
47-
category.setId(id);
48-
categoryStore.put(id, category);
49-
return category;
40+
return categoryService.updateCategory(id, category);
5041
}
5142

5243
@DeleteMapping("/{id}")
5344
@ResponseStatus(HttpStatus.NO_CONTENT)
5445
public void deleteCategory(@PathVariable Long id) {
55-
categoryStore.remove(id);
46+
categoryService.deleteCategory(id);
5647
}
5748

5849
@GetMapping("/random")
5950
public Category getRandomCategory() {
60-
return generateRandomCategory();
51+
return categoryService.getRandomCategory();
6152
}
6253

6354
@GetMapping("/random/{count}")
6455
public List<Category> getRandomCategories(@PathVariable int count) {
65-
return IntStream.range(0, count)
66-
.mapToObj(i -> generateRandomCategory())
67-
.collect(Collectors.toList());
68-
}
69-
70-
private Category generateRandomCategory() {
71-
return new Category()
72-
.id(random.nextLong(100))
73-
.name(petCategories.get(random.nextInt(petCategories.size())));
56+
return categoryService.getRandomCategories(count);
7457
}
7558
}
Lines changed: 15 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,30 @@
11
package com.sourcegraph.petstore.controller;
22

3-
import com.sourcegraph.petstore.openapi.generated.model.Category;
43
import com.sourcegraph.petstore.openapi.generated.model.Pet;
5-
import com.sourcegraph.petstore.openapi.generated.model.Tag;
4+
import com.sourcegraph.petstore.service.PetService;
5+
import org.springframework.beans.factory.annotation.Autowired;
66
import org.springframework.web.bind.annotation.*;
77

8-
import java.util.Arrays;
98
import java.util.List;
10-
import java.util.Random;
11-
import java.util.UUID;
12-
import java.util.stream.Collectors;
13-
import java.util.stream.IntStream;
149

1510
@RestController
1611
@RequestMapping("/api/pets")
1712
public class PetController {
1813

19-
private final Random random = new Random();
14+
private final PetService petService;
2015

21-
private final List<String> petNames = Arrays.asList(
22-
"Buddy", "Max", "Charlie", "Lucy", "Cooper", "Bella", "Luna", "Daisy",
23-
"Rocky", "Sadie", "Milo", "Bailey", "Jack", "Oliver", "Chloe", "Pepper"
24-
);
16+
@Autowired
17+
public PetController(PetService petService) {
18+
this.petService = petService;
19+
}
2520

26-
private final List<String> categories = Arrays.asList(
27-
"Dog", "Cat", "Bird", "Fish", "Reptile", "Rodent", "Exotic"
28-
);
21+
@GetMapping("/random")
22+
public Pet getRandomPet() {
23+
return petService.generateRandomPet();
24+
}
2925

30-
private final List<String> tagNames = Arrays.asList(
31-
"Friendly", "Playful", "Trained", "Young", "Adult", "Senior",
32-
"Vaccinated", "Neutered", "Spayed", "Rescue", "Purebred", "Hypoallergenic"
33-
);
34-
35-
@GetMapping("/random")
36-
public Pet getRandomPet() {
37-
return generateRandomPet();
38-
}
39-
40-
@GetMapping("/random/{count}")
41-
public List<Pet> getRandomPets(@PathVariable int count) {
42-
return IntStream.range(0, count)
43-
.mapToObj(i -> generateRandomPet())
44-
.collect(Collectors.toList());
45-
}
46-
private Pet generateRandomPet() {
47-
// First generate the category so we can use it for photo URLs
48-
Category category = generateRandomCategory();
49-
50-
// Create pet with required fields
51-
Pet pet = new Pet()
52-
.id(random.nextLong(10000))
53-
.name(getRandomElement(petNames))
54-
.photoUrls(generatePhotoUrlsForCategory(category.getName()));
55-
56-
// Add optional fields
57-
pet.setCategory(category);
58-
pet.setTags(generateRandomTags());
59-
pet.setStatus(getRandomStatus());
60-
61-
return pet;
62-
}
63-
64-
private List<String> generatePhotoUrlsForCategory(String categoryName) {
65-
int count = random.nextInt(3) + 1; // 1-3 photos
66-
return IntStream.range(0, count)
67-
.mapToObj(i -> "/images/" + categoryName + ".jpg")
68-
.collect(Collectors.toList());
69-
}
70-
71-
private Category generateRandomCategory() {
72-
return new Category()
73-
.id(random.nextLong(100))
74-
.name(getRandomElement(categories));
75-
}
76-
77-
private List<String> generateRandomPhotoUrls() {
78-
int count = random.nextInt(3) + 1; // 1-3 photos
79-
return IntStream.range(0, count)
80-
.mapToObj(i -> "https://example.com/pet-photos/" + UUID.randomUUID() + ".jpg")
81-
.collect(Collectors.toList());
82-
}
83-
84-
private List<Tag> generateRandomTags() {
85-
int count = random.nextInt(4); // 0-3 tags
86-
return IntStream.range(0, count)
87-
.mapToObj(i -> new Tag()
88-
.id(random.nextLong(100))
89-
.name(getRandomElement(tagNames)))
90-
.collect(Collectors.toList());
91-
}
92-
93-
private Pet.StatusEnum getRandomStatus() {
94-
Pet.StatusEnum[] statuses = Pet.StatusEnum.values();
95-
return statuses[random.nextInt(statuses.length)];
96-
}
97-
98-
private <T> T getRandomElement(List<T> list) {
99-
return list.get(random.nextInt(list.size()));
100-
}
26+
@GetMapping("/random/{count}")
27+
public List<Pet> getRandomPets(@PathVariable int count) {
28+
return petService.generateRandomPets(count);
29+
}
10130
}

0 commit comments

Comments
 (0)