Changes between Initial Version and Version 1 of AdvancedApplicationDevelopment


Ignore:
Timestamp:
05/26/26 01:48:35 (9 hours ago)
Author:
181201
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedApplicationDevelopment

    v1 v1  
     1= Advanced Application Development (Transactions, Pooling) =
     2
     3== Transactional ==
     4
     5In PetSitter, there are strict rules about transactions. Write operations (INSERT, UPDATE, DELETE) are labeled with `@Transactional` to ensure data consistency and enable automatic fallbacks on failure.
     6
     7Read operations (SELECT) are optimized with `@Transactional(readOnly = true)` to avoid unnecessary operations in the background and improve performance.
     8
     9=== Authenticating a user ===
     10{{{
     11#!java
     12    @Transactional(readOnly = true)
     13    public User authenticate(String username, String password) {
     14        User user = userRepository.findByUsername(username).orElse(null);
     15        // P9 - use BCryptPasswordEncoder here
     16        if (user != null && user.getPassword().equals(password)) {
     17            return user;
     18        }
     19        return null;
     20    }
     21}}}
     22
     23=== Registering a new user ===
     24{{{
     25#!java
     26    @Transactional
     27    public User registerUser(String username, String password, String firstName, String lastName, String email, String role) {
     28        if (userRepository.findByUsername(username).isPresent()) {
     29            throw new IllegalArgumentException("Username already exists!");
     30        }
     31
     32        User newUser;
     33        if ("OWNER".equalsIgnoreCase(role)) {
     34            newUser = new PetOwner();
     35        } else if ("SITTER".equalsIgnoreCase(role)) {
     36            newUser = new PetSitter();
     37        } else {
     38            throw new IllegalArgumentException("Invalid role selected!");
     39        }
     40
     41        newUser.setUsername(username);
     42        newUser.setPassword(password);
     43        newUser.setFirstName(firstName);
     44        newUser.setLastName(lastName);
     45        newUser.setEmail(email);
     46
     47        // Saving a PetOwner or PetSitter automatically saves to the parent 'users' table with InhertianceType.JOINED
     48        if (newUser instanceof PetOwner) {
     49            return petOwnerRepository.save((PetOwner) newUser);
     50        } else {
     51            return petSitterRepository.save((PetSitter) newUser);
     52        }
     53    }
     54}}}
     55
     56=== Creating a new booking ===
     57{{{
     58#!java
     59    @Transactional
     60    public Booking createBooking(String ownerId, String sitterId, LocalDate dateFrom, LocalDate dateTo, String address, List<String> petIds, String serviceType) {
     61        PetOwner owner = petOwnerRepository.findById(ownerId).orElseThrow(() -> new IllegalArgumentException("Invalid owner"));
     62        PetSitter sitter = petSitterRepository.findById(sitterId).orElseThrow(() -> new IllegalArgumentException("Invalid sitter"));
     63        List<Pet> pets = petRepository.findAllById(petIds);
     64
     65        Booking booking = new Booking();
     66        booking.setOwner(owner);
     67        booking.setSitter(sitter);
     68        booking.setDateFrom(dateFrom);
     69        booking.setDateTo(dateTo);
     70        booking.setAddress(address);
     71        booking.setPets(pets);
     72       
     73        if (serviceType != null && !serviceType.isEmpty()) {
     74            serviceRepository.findAll().stream()
     75                .filter(s -> s.getType().equals(serviceType))
     76                .findFirst()
     77                .ifPresent(service -> booking.setServices(List.of(service)));
     78        }
     79       
     80        return bookingRepository.save(booking);
     81    }
     82}}}
     83
     84=== Fetching bookings for a user ===
     85{{{
     86#!java
     87    @Transactional(readOnly = true)
     88    public List<Booking> getBookingsForSitter(String sitterId) {
     89        return bookingRepository.findBySitter_UserIdOrderByDateFromDesc(sitterId);
     90    }
     91
     92    @Transactional(readOnly = true)
     93    public List<Booking> getBookingsForOwner(String ownerId) {
     94        return bookingRepository.findByOwner_UserIdOrderByDateFromDesc(ownerId);
     95    }
     96}}}
     97
     98=== Adding a new pet ===
     99{{{
     100#!java
     101    @Transactional
     102    public Pet addPet(String name, Integer age, String specialNeeds, String description, String photoUrl, String ownerId, String petTypeId) {
     103        PetOwner owner = petOwnerRepository.findById(ownerId).orElseThrow(() -> new IllegalArgumentException("Invalid owner ID"));
     104        PetType petType = petTypeRepository.findById(petTypeId).orElseThrow(() -> new IllegalArgumentException("Invalid pet type ID"));
     105       
     106        Pet pet = new Pet();
     107        pet.setName(name);
     108        pet.setAge(age);
     109        pet.setSpecialNeeds(specialNeeds);
     110        pet.setDescription(description);
     111        pet.setPhoto(photoUrl);
     112        pet.setOwner(owner);
     113        pet.setPetType(petType);
     114       
     115        return petRepository.save(pet);
     116    }
     117}}}
     118
     119=== Managing Reviews ===
     120{{{
     121#!java
     122    @Transactional
     123    public Review addReview(String bookingId, Integer rating, String comment) {
     124        Booking booking = bookingRepository.findById(bookingId).orElseThrow(() -> new IllegalArgumentException("Invalid booking"));
     125        Review review = new Review();
     126        review.setBooking(booking);
     127        review.setRating(rating);
     128        review.setComment(comment);
     129        return reviewRepository.save(review);
     130    }
     131
     132    @Transactional(readOnly = true)
     133    public Double getAverageRating(String sitterId) {
     134        return reviewRepository.getAverageRatingForSitter(sitterId);
     135    }
     136}}}
     137
     138== Pooling ==
     139
     140Since PetSitter uses Spring Boot, the database connections are automatically handled with '''HikariCP'''.
     141
     142HikariCP comes with the `spring-boot-starter-data-jpa` dependency, but I explicitly forced the library version to `<hikaricp.version>6.2.1</hikaricp.version>` in the `pom.xml` file for more verbose logging capabilities.
     143
     144HikariCP and logging parameters inside `application.properties`:
     145
     146{{{
     147#!text
     148spring.datasource.hikari.maximum-pool-size=20
     149spring.datasource.hikari.minimum-idle=5
     150spring.datasource.hikari.connection-timeout=20000
     151spring.datasource.hikari.idle-timeout=300000
     152spring.datasource.hikari.max-lifetime=1200000
     153spring.datasource.hikari.auto-commit=true
     154
     155logging.level.com.zaxxer.hikari.HikariConfig=DEBUG
     156logging.level.com.zaxxer.hikari=DEBUG
     157logging.level.org.springframework.transaction.interceptor=TRACE
     158logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
     159}}}
     160
     161=== Pooling configuration ===
     162 * '''`maximum-pool-size` (20)''': Number of concurrent database connections. This prevents the PostgreSQL server from having too many connections during heavy usage.
     163 * '''`minimum-idle` (5)''': Instructs HikariCP to always keep at least 5 idle in the background, so sudden incoming requests can query the database instantly without disrupting regular users connection.
     164 * '''`connection-timeout` (20s)''': If all 20 connections are in use, new requests will wait 20 seconds for a free slot until it throws a timeout exception.
     165 * '''`idle-timeout` (5m)''': Any connection after the minimum 5th connection that is not actively used for 5 minutes will be terminated
     166 * '''`max-lifetime` (20m)''': Every connection can last up to maximum of 20 minutes.
     167 * '''`auto-commit` (true)''': The database automatically commits/saves every change without explicitly being told to do so.
     168
     169=== Logging configuration ===
     170Additional verbose logs for the connection pools:
     171 * '''`com.zaxxer.hikari.HikariConfig=DEBUG`''': Outputs the pooling settings  Hikari uses when the application is started.
     172 * '''`com.zaxxer.hikari=DEBUG`''': Shows pooling status in real time (active vs. idle connections) and logs when connections are created or closed.
     173 * '''`org.springframework.transaction.interceptor=TRACE`''': Logs exactly when Spring executes a `@Transactional` method in the code
     174 * '''`org.springframework.orm.jpa.JpaTransactionManager=DEBUG`''': Logs the actual database transactions happening in the background
     175
     176
     177=== Pooling Logs ===
     178Below is the log output proving the successful initialization and population of the HikariCP connection pool upon application startup. As configured, exactly 5 background connections are established and kept idle for immediate request processing.
     179
     180{{{
     181#!text
     1822026-05-25T15:27:56.827Z  INFO 1 --- [petsitter] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
     1832026-05-25T15:27:56.842Z DEBUG 1 --- [petsitter] [           main] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Attempting to create/setup new connection (7c6a30fc-4cfe-446a-a352-818177bca0d1)
     1842026-05-25T15:27:57.233Z DEBUG 1 --- [petsitter] [           main] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Established new connection (7c6a30fc-4cfe-446a-a352-818177bca0d1)
     1852026-05-25T15:27:57.235Z  INFO 1 --- [petsitter] [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@2ab9e43e
     1862026-05-25T15:27:57.340Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Attempting to create/setup new connection (3367dbcd-5010-4605-ab92-423e4562c990)
     1872026-05-25T15:27:57.869Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Established new connection (3367dbcd-5010-4605-ab92-423e4562c990)
     1882026-05-25T15:27:57.870Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@411815df
     1892026-05-25T15:27:57.900Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - After adding stats (total=2/20, idle=2/5, active=0, waiting=0)
     1902026-05-25T15:27:57.901Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Attempting to create/setup new connection (a71e892a-e173-41ba-909b-ca4e547437fd)
     1912026-05-25T15:27:58.525Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Established new connection (a71e892a-e173-41ba-909b-ca4e547437fd)
     1922026-05-25T15:27:58.525Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@4fdfe38c
     1932026-05-25T15:27:58.555Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - After adding stats (total=3/20, idle=3/5, active=0, waiting=0)
     1942026-05-25T15:27:58.556Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Attempting to create/setup new connection (670b5f5c-bddb-4a45-83ef-aa7a30a54530)
     1952026-05-25T15:27:58.631Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Established new connection (670b5f5c-bddb-4a45-83ef-aa7a30a54530)
     1962026-05-25T15:27:58.632Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@4be07eaf
     1972026-05-25T15:27:58.662Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - After adding stats (total=4/20, idle=4/5, active=0, waiting=0)
     1982026-05-25T15:27:58.662Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Attempting to create/setup new connection (81793045-e2cb-42de-9b40-e06e476bbea7)
     1992026-05-25T15:27:58.735Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Established new connection (81793045-e2cb-42de-9b40-e06e476bbea7)
     2002026-05-25T15:27:58.735Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@ee2e9b7
     2012026-05-25T15:27:58.765Z DEBUG 1 --- [petsitter] [onnection-adder] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - After adding stats (total=5/20, idle=5/5, active=0, waiting=0)
     202}}}