From 7ca7b1d46f0916a55d8a7792fdf462127cbd194c Mon Sep 17 00:00:00 2001 From: "junie-eap[bot]" Date: Thu, 22 May 2025 11:56:47 +0000 Subject: [PATCH] chore(junie): Junie: remove all current databases, make it all work over redis as main db changes from the task: #28 --- docker-compose.yml | 26 +-- k8s/db.yml | 50 ++--- k8s/petclinic.yml | 2 +- pom.xml | 21 +- .../samples/petclinic/config/RedisConfig.java | 26 +++ .../config/RedisDataInitializer.java | 210 ++++++++++++++++++ .../samples/petclinic/model/BaseEntity.java | 8 - .../samples/petclinic/model/NamedEntity.java | 4 - .../samples/petclinic/model/Person.java | 5 - .../samples/petclinic/owner/Owner.java | 16 -- .../samples/petclinic/owner/Pet.java | 18 -- .../samples/petclinic/owner/PetType.java | 5 - .../petclinic/owner/RedisOwnerRepository.java | 164 ++++++++++++++ .../samples/petclinic/owner/Visit.java | 6 - .../petclinic/vet/RedisVetRepository.java | 93 ++++++++ .../samples/petclinic/vet/Specialty.java | 5 - .../samples/petclinic/vet/Vet.java | 11 - src/main/resources/application.properties | 11 +- 18 files changed, 529 insertions(+), 152 deletions(-) create mode 100644 src/main/java/org/springframework/samples/petclinic/config/RedisConfig.java create mode 100644 src/main/java/org/springframework/samples/petclinic/config/RedisDataInitializer.java create mode 100644 src/main/java/org/springframework/samples/petclinic/owner/RedisOwnerRepository.java create mode 100644 src/main/java/org/springframework/samples/petclinic/vet/RedisVetRepository.java diff --git a/docker-compose.yml b/docker-compose.yml index 47579bbaf59..9708195177b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,21 +1,11 @@ services: - mysql: - image: mysql:9.1 + redis: + image: redis:7.2 ports: - - "3306:3306" - environment: - - MYSQL_ROOT_PASSWORD= - - MYSQL_ALLOW_EMPTY_PASSWORD=true - - MYSQL_USER=petclinic - - MYSQL_PASSWORD=petclinic - - MYSQL_DATABASE=petclinic + - "6379:6379" volumes: - - "./conf.d:/etc/mysql/conf.d:ro" - postgres: - image: postgres:17.0 - ports: - - "5432:5432" - environment: - - POSTGRES_PASSWORD=petclinic - - POSTGRES_USER=petclinic - - POSTGRES_DB=petclinic + - redis-data:/data + command: redis-server --appendonly yes + +volumes: + redis-data: diff --git a/k8s/db.yml b/k8s/db.yml index c230ddba2b1..3b71bb656fc 100644 --- a/k8s/db.yml +++ b/k8s/db.yml @@ -3,15 +3,12 @@ apiVersion: v1 kind: Secret metadata: name: demo-db -type: servicebinding.io/postgresql +type: servicebinding.io/redis stringData: - type: "postgresql" - provider: "postgresql" + type: "redis" + provider: "redis" host: "demo-db" - port: "5432" - database: "petclinic" - username: "user" - password: "pass" + port: "6379" --- apiVersion: v1 @@ -20,7 +17,7 @@ metadata: name: demo-db spec: ports: - - port: 5432 + - port: 6379 selector: app: demo-db @@ -41,33 +38,24 @@ spec: app: demo-db spec: containers: - - image: postgres:17 - name: postgresql - env: - - name: POSTGRES_USER - valueFrom: - secretKeyRef: - name: demo-db - key: username - - name: POSTGRES_PASSWORD - valueFrom: - secretKeyRef: - name: demo-db - key: password - - name: POSTGRES_DB - valueFrom: - secretKeyRef: - name: demo-db - key: database + - image: redis:7.2 + name: redis + args: ["--appendonly", "yes"] ports: - - containerPort: 5432 - name: postgresql + - containerPort: 6379 + name: redis livenessProbe: tcpSocket: - port: postgresql + port: redis readinessProbe: tcpSocket: - port: postgresql + port: redis startupProbe: tcpSocket: - port: postgresql + port: redis + volumeMounts: + - name: redis-data + mountPath: /data + volumes: + - name: redis-data + emptyDir: {} diff --git a/k8s/petclinic.yml b/k8s/petclinic.yml index a5677cd06a6..235682ffcc8 100644 --- a/k8s/petclinic.yml +++ b/k8s/petclinic.yml @@ -33,7 +33,7 @@ spec: image: dsyer/petclinic env: - name: SPRING_PROFILES_ACTIVE - value: postgres + value: default - name: SERVICE_BINDING_ROOT value: /bindings - name: SPRING_APPLICATION_JSON diff --git a/pom.xml b/pom.xml index 27fb9a6bd6a..1e20669f2ed 100644 --- a/pom.xml +++ b/pom.xml @@ -50,10 +50,6 @@ org.springframework.boot spring-boot-starter-cache - - org.springframework.boot - spring-boot-starter-data-jpa - org.springframework.boot spring-boot-starter-web @@ -77,21 +73,14 @@ reactor-core - + - com.h2database - h2 - runtime - - - com.mysql - mysql-connector-j - runtime + org.springframework.boot + spring-boot-starter-data-redis - org.postgresql - postgresql - runtime + redis.clients + jedis diff --git a/src/main/java/org/springframework/samples/petclinic/config/RedisConfig.java b/src/main/java/org/springframework/samples/petclinic/config/RedisConfig.java new file mode 100644 index 00000000000..f2871c11e6f --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/config/RedisConfig.java @@ -0,0 +1,26 @@ +package org.springframework.samples.petclinic.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * Redis configuration for the application. + */ +@Configuration +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); + return template; + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/config/RedisDataInitializer.java b/src/main/java/org/springframework/samples/petclinic/config/RedisDataInitializer.java new file mode 100644 index 00000000000..9bb173b2e59 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/config/RedisDataInitializer.java @@ -0,0 +1,210 @@ +package org.springframework.samples.petclinic.config; + +import java.time.LocalDate; +import java.util.Arrays; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.samples.petclinic.owner.Owner; +import org.springframework.samples.petclinic.owner.Pet; +import org.springframework.samples.petclinic.owner.PetType; +import org.springframework.samples.petclinic.owner.RedisOwnerRepository; +import org.springframework.samples.petclinic.owner.Visit; +import org.springframework.samples.petclinic.vet.RedisVetRepository; +import org.springframework.samples.petclinic.vet.Specialty; +import org.springframework.samples.petclinic.vet.Vet; +import org.springframework.data.redis.core.RedisTemplate; + +/** + * Initialize Redis with sample data. + */ +@Configuration +public class RedisDataInitializer { + + @Bean + public CommandLineRunner initData(RedisOwnerRepository ownerRepository, RedisVetRepository vetRepository, + RedisTemplate redisTemplate) { + return args -> { + // Clear existing data + redisTemplate.delete("owners"); + redisTemplate.delete("vets"); + redisTemplate.delete("pet_types"); + redisTemplate.delete("owner_id_sequence"); + redisTemplate.delete("vet_id_sequence"); + + // Create pet types + PetType dog = new PetType(); + dog.setName("dog"); + + PetType cat = new PetType(); + cat.setName("cat"); + + PetType bird = new PetType(); + bird.setName("bird"); + + PetType lizard = new PetType(); + lizard.setName("lizard"); + + PetType snake = new PetType(); + snake.setName("snake"); + + PetType hamster = new PetType(); + hamster.setName("hamster"); + + // Save pet types to Redis + redisTemplate.opsForSet().add("pet_types", dog, cat, bird, lizard, snake, hamster); + + // Create specialties + Specialty radiology = new Specialty(); + radiology.setName("radiology"); + + Specialty surgery = new Specialty(); + surgery.setName("surgery"); + + Specialty dentistry = new Specialty(); + dentistry.setName("dentistry"); + + // Create vets + Vet vet1 = new Vet(); + vet1.setFirstName("James"); + vet1.setLastName("Carter"); + vetRepository.save(vet1); + + Vet vet2 = new Vet(); + vet2.setFirstName("Helen"); + vet2.setLastName("Leary"); + vet2.addSpecialty(radiology); + vetRepository.save(vet2); + + Vet vet3 = new Vet(); + vet3.setFirstName("Linda"); + vet3.setLastName("Douglas"); + vet3.addSpecialty(surgery); + vet3.addSpecialty(dentistry); + vetRepository.save(vet3); + + Vet vet4 = new Vet(); + vet4.setFirstName("Rafael"); + vet4.setLastName("Ortega"); + vet4.addSpecialty(surgery); + vetRepository.save(vet4); + + Vet vet5 = new Vet(); + vet5.setFirstName("Henry"); + vet5.setLastName("Stevens"); + vet5.addSpecialty(radiology); + vetRepository.save(vet5); + + Vet vet6 = new Vet(); + vet6.setFirstName("Sharon"); + vet6.setLastName("Jenkins"); + vetRepository.save(vet6); + + // Create owners, pets, and visits + Owner owner1 = new Owner(); + owner1.setFirstName("George"); + owner1.setLastName("Franklin"); + owner1.setAddress("110 W. Liberty St."); + owner1.setCity("Madison"); + owner1.setTelephone("6085551023"); + + Pet pet1 = new Pet(); + pet1.setName("Leo"); + pet1.setBirthDate(LocalDate.now().minusYears(2)); + pet1.setType(cat); + owner1.addPet(pet1); + + ownerRepository.save(owner1); + + Owner owner2 = new Owner(); + owner2.setFirstName("Betty"); + owner2.setLastName("Davis"); + owner2.setAddress("638 Cardinal Ave."); + owner2.setCity("Sun Prairie"); + owner2.setTelephone("6085551749"); + + Pet pet2 = new Pet(); + pet2.setName("Basil"); + pet2.setBirthDate(LocalDate.now().minusYears(1)); + pet2.setType(hamster); + owner2.addPet(pet2); + + ownerRepository.save(owner2); + + Owner owner3 = new Owner(); + owner3.setFirstName("Eduardo"); + owner3.setLastName("Rodriquez"); + owner3.setAddress("2693 Commerce St."); + owner3.setCity("McFarland"); + owner3.setTelephone("6085558763"); + + Pet pet3 = new Pet(); + pet3.setName("Rosy"); + pet3.setBirthDate(LocalDate.now().minusYears(3)); + pet3.setType(dog); + owner3.addPet(pet3); + + ownerRepository.save(owner3); + + Owner owner4 = new Owner(); + owner4.setFirstName("Harold"); + owner4.setLastName("Davis"); + owner4.setAddress("563 Friendly St."); + owner4.setCity("Windsor"); + owner4.setTelephone("6085553198"); + + Pet pet4 = new Pet(); + pet4.setName("Jewel"); + pet4.setBirthDate(LocalDate.now().minusYears(1)); + pet4.setType(dog); + owner4.addPet(pet4); + + ownerRepository.save(owner4); + + Owner owner5 = new Owner(); + owner5.setFirstName("Peter"); + owner5.setLastName("McTavish"); + owner5.setAddress("2387 S. Fair Way"); + owner5.setCity("Madison"); + owner5.setTelephone("6085552765"); + + Pet pet5 = new Pet(); + pet5.setName("George"); + pet5.setBirthDate(LocalDate.now().minusYears(4)); + pet5.setType(snake); + owner5.addPet(pet5); + + ownerRepository.save(owner5); + + Owner owner6 = new Owner(); + owner6.setFirstName("Jean"); + owner6.setLastName("Coleman"); + owner6.setAddress("105 N. Lake St."); + owner6.setCity("Monona"); + owner6.setTelephone("6085552654"); + + Pet pet6 = new Pet(); + pet6.setName("Max"); + pet6.setBirthDate(LocalDate.now().minusYears(2)); + pet6.setType(cat); + owner6.addPet(pet6); + + Pet pet7 = new Pet(); + pet7.setName("Samantha"); + pet7.setBirthDate(LocalDate.now().minusYears(1)); + pet7.setType(cat); + owner6.addPet(pet7); + + ownerRepository.save(owner6); + + // Add visits + Visit visit1 = new Visit(); + visit1.setDate(LocalDate.now().minusDays(5)); + visit1.setDescription("Sneezing"); + pet7.addVisit(visit1); + + ownerRepository.save(owner6); + }; + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java b/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java index 3038bce3a90..caf89929235 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java +++ b/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java @@ -17,11 +17,6 @@ import java.io.Serializable; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.MappedSuperclass; - /** * Simple JavaBean domain object with an id property. Used as a base class for objects * needing this property. @@ -29,11 +24,8 @@ * @author Ken Krebs * @author Juergen Hoeller */ -@MappedSuperclass public class BaseEntity implements Serializable { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; public Integer getId() { diff --git a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java b/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java index 012e8c4be74..aa569467e3c 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java +++ b/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java @@ -15,8 +15,6 @@ */ package org.springframework.samples.petclinic.model; -import jakarta.persistence.Column; -import jakarta.persistence.MappedSuperclass; import jakarta.validation.constraints.NotBlank; /** @@ -27,10 +25,8 @@ * @author Juergen Hoeller * @author Wick Dynex */ -@MappedSuperclass public class NamedEntity extends BaseEntity { - @Column(name = "name") @NotBlank private String name; diff --git a/src/main/java/org/springframework/samples/petclinic/model/Person.java b/src/main/java/org/springframework/samples/petclinic/model/Person.java index 7c3d81a84d9..9ed416e599c 100644 --- a/src/main/java/org/springframework/samples/petclinic/model/Person.java +++ b/src/main/java/org/springframework/samples/petclinic/model/Person.java @@ -15,8 +15,6 @@ */ package org.springframework.samples.petclinic.model; -import jakarta.persistence.Column; -import jakarta.persistence.MappedSuperclass; import jakarta.validation.constraints.NotBlank; /** @@ -24,14 +22,11 @@ * * @author Ken Krebs */ -@MappedSuperclass public class Person extends BaseEntity { - @Column(name = "first_name") @NotBlank private String firstName; - @Column(name = "last_name") @NotBlank private String lastName; diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java index c5ae067dce5..fa5c1c1bc20 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java @@ -22,14 +22,6 @@ import org.springframework.samples.petclinic.model.Person; import org.springframework.util.Assert; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.Table; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.NotBlank; @@ -43,26 +35,18 @@ * @author Oliver Drotbohm * @author Wick Dynex */ -@Entity -@Table(name = "owners") public class Owner extends Person { - @Column(name = "address") @NotBlank private String address; - @Column(name = "city") @NotBlank private String city; - @Column(name = "telephone") @NotBlank @Pattern(regexp = "\\d{10}", message = "{telephone.invalid}") private String telephone; - @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinColumn(name = "owner_id") - @OrderBy("name") private final List pets = new ArrayList<>(); public String getAddress() { diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java index 1fdc77cec2d..ca1e6fb2679 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java @@ -23,16 +23,6 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.samples.petclinic.model.NamedEntity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.Table; - /** * Simple business object representing a pet. * @@ -41,21 +31,13 @@ * @author Sam Brannen * @author Wick Dynex */ -@Entity -@Table(name = "pets") public class Pet extends NamedEntity { - @Column(name = "birth_date") @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate birthDate; - @ManyToOne - @JoinColumn(name = "type_id") private PetType type; - @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) - @JoinColumn(name = "pet_id") - @OrderBy("date ASC") private final Set visits = new LinkedHashSet<>(); public void setBirthDate(LocalDate birthDate) { diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetType.java b/src/main/java/org/springframework/samples/petclinic/owner/PetType.java index eeea6a758df..8466c87799b 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetType.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetType.java @@ -17,14 +17,9 @@ import org.springframework.samples.petclinic.model.NamedEntity; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; - /** * @author Juergen Hoeller Can be Cat, Dog, Hamster... */ -@Entity -@Table(name = "types") public class PetType extends NamedEntity { } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/RedisOwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/RedisOwnerRepository.java new file mode 100644 index 00000000000..5db159fe9dd --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/owner/RedisOwnerRepository.java @@ -0,0 +1,164 @@ +package org.springframework.samples.petclinic.owner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +/** + * Redis implementation of the {@link OwnerRepository} interface. + */ +@Repository +public class RedisOwnerRepository implements OwnerRepository { + + private final RedisTemplate redisTemplate; + private static final String OWNER_KEY = "owners"; + private static final String PET_TYPE_KEY = "pet_types"; + + public RedisOwnerRepository(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Override + public List findPetTypes() { + Set petTypes = redisTemplate.opsForSet().members(PET_TYPE_KEY); + if (petTypes == null) { + return Collections.emptyList(); + } + return petTypes.stream() + .filter(obj -> obj instanceof PetType) + .map(obj -> (PetType) obj) + .collect(Collectors.toList()); + } + + @Override + public Page findByLastNameStartingWith(String lastName, Pageable pageable) { + List owners = findAll(); + List filtered = owners.stream() + .filter(owner -> owner.getLastName().toLowerCase().startsWith(lastName.toLowerCase())) + .collect(Collectors.toList()); + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), filtered.size()); + + return new PageImpl<>( + start >= filtered.size() ? Collections.emptyList() : filtered.subList(start, end), + pageable, + filtered.size() + ); + } + + @Override + public Optional findById(Integer id) { + if (id == null) { + throw new IllegalArgumentException("Id must not be null"); + } + + List owners = findAll(); + return owners.stream() + .filter(owner -> id.equals(owner.getId())) + .findFirst(); + } + + @Override + public Page findAll(Pageable pageable) { + List owners = findAll(); + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), owners.size()); + + return new PageImpl<>( + start >= owners.size() ? Collections.emptyList() : owners.subList(start, end), + pageable, + owners.size() + ); + } + + @Override + public S save(S owner) { + if (owner.isNew()) { + owner.setId(getNextId()); + } + + redisTemplate.opsForHash().put(OWNER_KEY, owner.getId().toString(), owner); + return owner; + } + + @Override + public List saveAll(Iterable entities) { + List result = new ArrayList<>(); + for (S entity : entities) { + result.add(save(entity)); + } + return result; + } + + @Override + public void deleteById(Integer id) { + redisTemplate.opsForHash().delete(OWNER_KEY, id.toString()); + } + + @Override + public void delete(Owner entity) { + deleteById(entity.getId()); + } + + @Override + public void deleteAllById(Iterable ids) { + for (Integer id : ids) { + deleteById(id); + } + } + + @Override + public void deleteAll(Iterable entities) { + for (Owner entity : entities) { + delete(entity); + } + } + + @Override + public void deleteAll() { + redisTemplate.delete(OWNER_KEY); + } + + @Override + public long count() { + return redisTemplate.opsForHash().size(OWNER_KEY); + } + + @Override + public boolean existsById(Integer id) { + return redisTemplate.opsForHash().hasKey(OWNER_KEY, id.toString()); + } + + @Override + public List findAll() { + return redisTemplate.opsForHash().values(OWNER_KEY).stream() + .filter(obj -> obj instanceof Owner) + .map(obj -> (Owner) obj) + .collect(Collectors.toList()); + } + + @Override + public List findAllById(Iterable ids) { + List result = new ArrayList<>(); + for (Integer id : ids) { + findById(id).ifPresent(result::add); + } + return result; + } + + private Integer getNextId() { + Long nextId = redisTemplate.opsForValue().increment("owner_id_sequence"); + return nextId != null ? nextId.intValue() : 1; + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Visit.java b/src/main/java/org/springframework/samples/petclinic/owner/Visit.java index 35569bdaaf5..156c212d6f9 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/Visit.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/Visit.java @@ -20,9 +20,6 @@ import org.springframework.format.annotation.DateTimeFormat; import org.springframework.samples.petclinic.model.BaseEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; import jakarta.validation.constraints.NotBlank; /** @@ -31,11 +28,8 @@ * @author Ken Krebs * @author Dave Syer */ -@Entity -@Table(name = "visits") public class Visit extends BaseEntity { - @Column(name = "visit_date") @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate date; diff --git a/src/main/java/org/springframework/samples/petclinic/vet/RedisVetRepository.java b/src/main/java/org/springframework/samples/petclinic/vet/RedisVetRepository.java new file mode 100644 index 00000000000..4ff8da30b98 --- /dev/null +++ b/src/main/java/org/springframework/samples/petclinic/vet/RedisVetRepository.java @@ -0,0 +1,93 @@ +package org.springframework.samples.petclinic.vet; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.dao.DataAccessException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.cache.annotation.Cacheable; + +/** + * Redis implementation of the {@link VetRepository} interface. + */ +@Repository +public class RedisVetRepository implements VetRepository { + + private final RedisTemplate redisTemplate; + private static final String VET_KEY = "vets"; + + public RedisVetRepository(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Override + @Transactional(readOnly = true) + @Cacheable("vets") + public Collection findAll() throws DataAccessException { + return redisTemplate.opsForHash().values(VET_KEY).stream() + .filter(obj -> obj instanceof Vet) + .map(obj -> (Vet) obj) + .collect(Collectors.toList()); + } + + @Override + @Transactional(readOnly = true) + @Cacheable("vets") + public Page findAll(Pageable pageable) throws DataAccessException { + List vets = new ArrayList<>(findAll()); + + int start = (int) pageable.getOffset(); + int end = Math.min((start + pageable.getPageSize()), vets.size()); + + return new PageImpl<>( + start >= vets.size() ? Collections.emptyList() : vets.subList(start, end), + pageable, + vets.size() + ); + } + + /** + * Save a vet to Redis + * @param vet the vet to save + * @return the saved vet + */ + public Vet save(Vet vet) { + if (vet.isNew()) { + vet.setId(getNextId()); + } + + redisTemplate.opsForHash().put(VET_KEY, vet.getId().toString(), vet); + return vet; + } + + /** + * Find a vet by ID + * @param id the ID to search for + * @return the vet if found, null otherwise + */ + public Vet findById(Integer id) { + Object vet = redisTemplate.opsForHash().get(VET_KEY, id.toString()); + return (vet instanceof Vet) ? (Vet) vet : null; + } + + /** + * Delete a vet by ID + * @param id the ID of the vet to delete + */ + public void deleteById(Integer id) { + redisTemplate.opsForHash().delete(VET_KEY, id.toString()); + } + + private Integer getNextId() { + Long nextId = redisTemplate.opsForValue().increment("vet_id_sequence"); + return nextId != null ? nextId.intValue() : 1; + } +} diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Specialty.java b/src/main/java/org/springframework/samples/petclinic/vet/Specialty.java index b0b6315fced..8e596db9c17 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/Specialty.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/Specialty.java @@ -17,16 +17,11 @@ import org.springframework.samples.petclinic.model.NamedEntity; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; - /** * Models a {@link Vet Vet's} specialty (for example, dentistry). * * @author Juergen Hoeller */ -@Entity -@Table(name = "specialties") public class Specialty extends NamedEntity { } diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Vet.java b/src/main/java/org/springframework/samples/petclinic/vet/Vet.java index 00c7ec1c8e3..c035dddab61 100644 --- a/src/main/java/org/springframework/samples/petclinic/vet/Vet.java +++ b/src/main/java/org/springframework/samples/petclinic/vet/Vet.java @@ -24,12 +24,6 @@ import org.springframework.samples.petclinic.model.NamedEntity; import org.springframework.samples.petclinic.model.Person; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.Table; import jakarta.xml.bind.annotation.XmlElement; /** @@ -40,13 +34,8 @@ * @author Sam Brannen * @author Arjen Poutsma */ -@Entity -@Table(name = "vets") public class Vet extends Person { - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), - inverseJoinColumns = @JoinColumn(name = "specialty_id")) private Set specialties; protected Set getSpecialtiesInternal() { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6ed9856541a..977a6fc1888 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,15 +1,10 @@ -# database init, supports mysql too -database=h2 -spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql -spring.sql.init.data-locations=classpath*:db/${database}/data.sql +# Redis configuration +spring.data.redis.host=localhost +spring.data.redis.port=6379 # Web spring.thymeleaf.mode=HTML -# JPA -spring.jpa.hibernate.ddl-auto=none -spring.jpa.open-in-view=false - # Internationalization spring.messages.basename=messages/messages