diff --git a/docker-compose.yml b/docker-compose.yml index 4ab5416..05cff88 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,27 +1,28 @@ services: - # Oracle Database service - oracle-db: - image: container-registry.oracle.com/database/express:21.3.0-xe - container_name: photoalbum-oracle + # PostgreSQL Database service + # Migrated from Oracle to PostgreSQL - replaced Oracle container with PostgreSQL container + postgres-db: + image: postgres:15-alpine + container_name: photoalbum-postgres environment: - - ORACLE_PWD=photoalbum - - ORACLE_CHARACTERSET=AL32UTF8 + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=photoalbum + - POSTGRES_DB=postgres ports: - - "1521:1521" - - "5500:5500" + - "5432:5432" volumes: - - oracle_data:/opt/oracle/oradata - - ./oracle-init:/opt/oracle/scripts/startup + - postgres_data:/var/lib/postgresql/data + - ./postgres-init:/docker-entrypoint-initdb.d networks: - photoalbum-network healthcheck: - test: ["CMD-SHELL", "echo 'SELECT 1 FROM DUAL;' | sqlplus -s photoalbum/photoalbum@//localhost:1521/XE || exit 1"] - interval: 30s - timeout: 10s - retries: 15 - start_period: 180s + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s - # Photo Album Java Application + # Photo Album Java Application photoalbum-java-app: build: context: . @@ -29,20 +30,20 @@ services: container_name: photoalbum-java-app environment: - SPRING_PROFILES_ACTIVE=docker - - SPRING_DATASOURCE_URL=jdbc:oracle:thin:@oracle-db:1521:XE - - SPRING_DATASOURCE_USERNAME=photoalbum - - SPRING_DATASOURCE_PASSWORD=photoalbum + - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres-db:5432/photoalbum + - POSTGRES_USERNAME=photoalbum + - POSTGRES_PASSWORD=photoalbum ports: - "8080:8080" depends_on: - oracle-db: + postgres-db: condition: service_healthy networks: - photoalbum-network restart: on-failure volumes: - oracle_data: + postgres_data: networks: photoalbum-network: diff --git a/oracle-init/01-create-user.sql b/oracle-init/01-create-user.sql deleted file mode 100644 index 32ad600..0000000 --- a/oracle-init/01-create-user.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This script runs automatically when Oracle XE container starts --- It creates the photoalbum user and grants necessary privileges - -ALTER SESSION SET "_ORACLE_SCRIPT"=true; - --- Create photoalbum user -CREATE USER photoalbum IDENTIFIED BY photoalbum; - --- Grant system privileges -GRANT CONNECT TO photoalbum; -GRANT RESOURCE TO photoalbum; -GRANT DBA TO photoalbum; -GRANT CREATE SESSION TO photoalbum; -GRANT CREATE TABLE TO photoalbum; -GRANT CREATE SEQUENCE TO photoalbum; -GRANT CREATE VIEW TO photoalbum; -GRANT CREATE PROCEDURE TO photoalbum; -GRANT CREATE TRIGGER TO photoalbum; -GRANT CREATE TYPE TO photoalbum; -GRANT CREATE SYNONYM TO photoalbum; -GRANT UNLIMITED TABLESPACE TO photoalbum; - --- Grant object privileges needed by Hibernate -GRANT SELECT ANY DICTIONARY TO photoalbum; -GRANT CREATE ANY INDEX TO photoalbum; -GRANT ALTER ANY INDEX TO photoalbum; -GRANT DROP ANY INDEX TO photoalbum; - --- Set default and temporary tablespace -ALTER USER photoalbum DEFAULT TABLESPACE USERS; -ALTER USER photoalbum TEMPORARY TABLESPACE TEMP; - --- Commit the changes -COMMIT; - -EXIT; \ No newline at end of file diff --git a/oracle-init/02-verify-user.sql b/oracle-init/02-verify-user.sql deleted file mode 100644 index 91c82a1..0000000 --- a/oracle-init/02-verify-user.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Verification script to check if photoalbum user exists -ALTER SESSION SET "_ORACLE_SCRIPT"=true; - --- Check if user exists -SELECT username, account_status, default_tablespace -FROM dba_users -WHERE username = 'PHOTOALBUM'; - --- Show granted privileges -SELECT grantee, privilege -FROM dba_sys_privs -WHERE grantee = 'PHOTOALBUM'; - -EXIT; \ No newline at end of file diff --git a/oracle-init/create-user.sh b/oracle-init/create-user.sh deleted file mode 100644 index c15ee5f..0000000 --- a/oracle-init/create-user.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# This script ensures the photoalbum user is created in Oracle XE - -# Wait for Oracle to be fully ready -echo "Waiting for Oracle to be ready..." -sleep 30 - -# Connect to Oracle as SYSTEM and create the photoalbum user -sqlplus -s system/photoalbum@//localhost:1521/XE < 4.0.0 @@ -18,7 +18,7 @@ jar Photo Album - A simple photo storage and gallery application built with Spring Boot and Oracle DB + A simple photo storage and gallery application built with Spring Boot and PostgreSQL 1.8 @@ -46,10 +46,14 @@ spring-boot-starter-data-jpa - + + + + - com.oracle.database.jdbc - ojdbc8 + org.postgresql + postgresql + 42.7.2 runtime @@ -60,10 +64,11 @@ + commons-io commons-io - 2.11.0 + 2.14.0 @@ -80,9 +85,13 @@ + + + com.h2database h2 + 2.2.224 test diff --git a/postgres-init/01-create-user.sql b/postgres-init/01-create-user.sql new file mode 100644 index 0000000..ec3d7dc --- /dev/null +++ b/postgres-init/01-create-user.sql @@ -0,0 +1,24 @@ +-- Migrated from Oracle to PostgreSQL according to SQL check item 1: Use lowercase for identifiers (like table and column names) and data type (like varchar), use uppercase for SQL keywords (like SELECT, FROM, WHERE). +-- This script runs automatically when PostgreSQL container starts +-- It creates the photoalbum user and database + +-- Create photoalbum user +CREATE USER photoalbum WITH PASSWORD 'photoalbum'; + +-- Create photoalbum database +CREATE DATABASE photoalbum WITH OWNER photoalbum; + +-- Grant privileges to photoalbum user on the database +GRANT ALL PRIVILEGES ON DATABASE photoalbum TO photoalbum; + +-- Connect to photoalbum database +\c photoalbum + +-- Grant schema privileges +GRANT ALL ON SCHEMA public TO photoalbum; +GRANT CREATE ON SCHEMA public TO photoalbum; + +-- Grant default privileges for future objects +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO photoalbum; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO photoalbum; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO photoalbum; diff --git a/postgres-init/02-verify-user.sql b/postgres-init/02-verify-user.sql new file mode 100644 index 0000000..0c9e068 --- /dev/null +++ b/postgres-init/02-verify-user.sql @@ -0,0 +1,13 @@ +-- Migrated from Oracle to PostgreSQL according to SQL check item 1: Use lowercase for identifiers (like table and column names) and data type (like varchar), use uppercase for SQL keywords (like SELECT, FROM, WHERE). +-- Verify photoalbum user exists and can access the database + +-- Connect to photoalbum database +\c photoalbum photoalbum + +-- Verify user can query +SELECT current_user, current_database(); + +-- List available tables (will be empty initially) +SELECT table_name +FROM information_schema.tables +WHERE table_schema = 'public'; diff --git a/postgres-init/healthcheck.sql b/postgres-init/healthcheck.sql new file mode 100644 index 0000000..8baf7a2 --- /dev/null +++ b/postgres-init/healthcheck.sql @@ -0,0 +1,3 @@ +-- Migrated from Oracle to PostgreSQL according to SQL check item 3: Remove FROM DUAL in SELECT statements that only return values. +-- Health check script for PostgreSQL DB +SELECT 1; diff --git a/src/main/java/com/photoalbum/controller/PhotoFileController.java b/src/main/java/com/photoalbum/controller/PhotoFileController.java index 2f31408..a2111b1 100644 --- a/src/main/java/com/photoalbum/controller/PhotoFileController.java +++ b/src/main/java/com/photoalbum/controller/PhotoFileController.java @@ -17,7 +17,7 @@ import java.util.Optional; /** - * Controller for serving photo files from Oracle database BLOB storage + * Controller for serving photo files from PostgreSQL database BLOB storage */ @Controller @RequestMapping("/photo") @@ -32,7 +32,7 @@ public PhotoFileController(PhotoService photoService) { } /** - * Serves a photo file by ID from Oracle database BLOB storage + * Serves a photo file by ID from PostgreSQL database BLOB storage */ @GetMapping("/{id}") public ResponseEntity servePhoto(@PathVariable String id) { @@ -51,24 +51,24 @@ public ResponseEntity servePhoto(@PathVariable String id) { } Photo photo = photoOpt.get(); - logger.info("Found photo: originalFileName={}, mimeType={}", + logger.info("Found photo: originalFileName={}, mimeType={}", photo.getOriginalFileName(), photo.getMimeType()); - // Get photo data from Oracle database BLOB + // Get photo data from PostgreSQL database BLOB byte[] photoData = photo.getPhotoData(); if (photoData == null || photoData.length == 0) { logger.error("No photo data found for photo ID {}", id); return ResponseEntity.notFound().build(); } - logger.info("Photo data retrieved: {} bytes, first 10 bytes: {}", - photoData.length, + logger.info("Photo data retrieved: {} bytes, first 10 bytes: {}", + photoData.length, photoData.length >= 10 ? java.util.Arrays.toString(java.util.Arrays.copyOf(photoData, 10)) : "less than 10 bytes"); // Create resource from byte array Resource resource = new ByteArrayResource(photoData); - logger.info("Serving photo ID {} ({}, {} bytes) from Oracle database", + logger.info("Serving photo ID {} ({}, {} bytes) from PostgreSQL database", id, photo.getOriginalFileName(), photoData.length); // Return the photo data with appropriate content type and aggressive no-cache headers @@ -82,7 +82,7 @@ public ResponseEntity servePhoto(@PathVariable String id) { .header("X-Photo-Size", String.valueOf(photoData.length)) .body(resource); } catch (Exception ex) { - logger.error("Error serving photo with ID {} from Oracle database", id, ex); + logger.error("Error serving photo with ID {} from PostgreSQL database", id, ex); return ResponseEntity.status(500).build(); } } diff --git a/src/main/java/com/photoalbum/model/Photo.java b/src/main/java/com/photoalbum/model/Photo.java index d1cea3e..89fd350 100644 --- a/src/main/java/com/photoalbum/model/Photo.java +++ b/src/main/java/com/photoalbum/model/Photo.java @@ -34,7 +34,8 @@ public class Photo { private String originalFileName; /** - * Binary photo data stored directly in Oracle database + * Binary photo data stored directly in PostgreSQL database + * Migrated from Oracle to PostgreSQL according to java check item 1: Convert all table and column names from uppercase to lowercase in JPA annotations. */ @Lob @Column(name = "photo_data", nullable = true) @@ -57,10 +58,11 @@ public class Photo { /** * File size in bytes + * Migrated from Oracle to PostgreSQL according to java check item 1: Convert all table and column names from uppercase to lowercase in JPA annotations. */ @NotNull @Positive - @Column(name = "file_size", nullable = false, columnDefinition = "NUMBER(19,0)") + @Column(name = "file_size", nullable = false) private Long fileSize; /** @@ -73,9 +75,10 @@ public class Photo { /** * Timestamp of upload + * Migrated from Oracle to PostgreSQL according to java check item 1: Convert all table and column names from uppercase to lowercase in JPA annotations. */ @NotNull - @Column(name = "uploaded_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT SYSTIMESTAMP") + @Column(name = "uploaded_at", nullable = false) private LocalDateTime uploadedAt; /** diff --git a/src/main/java/com/photoalbum/repository/PhotoRepository.java b/src/main/java/com/photoalbum/repository/PhotoRepository.java index 135799a..b369ff6 100644 --- a/src/main/java/com/photoalbum/repository/PhotoRepository.java +++ b/src/main/java/com/photoalbum/repository/PhotoRepository.java @@ -22,7 +22,7 @@ public interface PhotoRepository extends JpaRepository { @Query(value = "SELECT ID, ORIGINAL_FILE_NAME, PHOTO_DATA, STORED_FILE_NAME, FILE_PATH, FILE_SIZE, " + "MIME_TYPE, UPLOADED_AT, WIDTH, HEIGHT " + "FROM PHOTOS " + - "ORDER BY UPLOADED_AT DESC", + "ORDER BY UPLOADED_AT DESC", nativeQuery = true) List findAllOrderByUploadedAtDesc(); @@ -37,7 +37,7 @@ public interface PhotoRepository extends JpaRepository { "FROM PHOTOS " + "WHERE UPLOADED_AT < :uploadedAt " + "ORDER BY UPLOADED_AT DESC" + - ") WHERE ROWNUM <= 10", + ") WHERE ROWNUM <= 10", nativeQuery = true) List findPhotosUploadedBefore(@Param("uploadedAt") LocalDateTime uploadedAt); @@ -51,12 +51,12 @@ public interface PhotoRepository extends JpaRepository { "MIME_TYPE, UPLOADED_AT, WIDTH, HEIGHT " + "FROM PHOTOS " + "WHERE UPLOADED_AT > :uploadedAt " + - "ORDER BY UPLOADED_AT ASC", + "ORDER BY UPLOADED_AT ASC", nativeQuery = true) List findPhotosUploadedAfter(@Param("uploadedAt") LocalDateTime uploadedAt); /** - * Find photos by upload month using Oracle TO_CHAR function - Oracle specific + * Find photos by upload month using TO_CHAR function * @param year The year to search for * @param month The month to search for * @return List of photos uploaded in the specified month @@ -66,12 +66,12 @@ public interface PhotoRepository extends JpaRepository { "FROM PHOTOS " + "WHERE TO_CHAR(UPLOADED_AT, 'YYYY') = :year " + "AND TO_CHAR(UPLOADED_AT, 'MM') = :month " + - "ORDER BY UPLOADED_AT DESC", + "ORDER BY UPLOADED_AT DESC", nativeQuery = true) List findPhotosByUploadMonth(@Param("year") String year, @Param("month") String month); /** - * Get paginated photos using Oracle ROWNUM - Oracle specific pagination + * Get paginated photos using ROWNUM pagination * @param startRow Starting row number (1-based) * @param endRow Ending row number * @return List of photos within the specified row range @@ -82,12 +82,12 @@ public interface PhotoRepository extends JpaRepository { "MIME_TYPE, UPLOADED_AT, WIDTH, HEIGHT " + "FROM PHOTOS ORDER BY UPLOADED_AT DESC" + ") P WHERE ROWNUM <= :endRow" + - ") WHERE RN >= :startRow", + ") WHERE RN >= :startRow", nativeQuery = true) List findPhotosWithPagination(@Param("startRow") int startRow, @Param("endRow") int endRow); /** - * Find photos with file size statistics using Oracle analytical functions - Oracle specific + * Find photos with file size statistics using analytical functions * @return List of photos with running totals and rankings */ @Query(value = "SELECT ID, ORIGINAL_FILE_NAME, PHOTO_DATA, STORED_FILE_NAME, FILE_PATH, FILE_SIZE, " + @@ -95,7 +95,7 @@ public interface PhotoRepository extends JpaRepository { "RANK() OVER (ORDER BY FILE_SIZE DESC) as SIZE_RANK, " + "SUM(FILE_SIZE) OVER (ORDER BY UPLOADED_AT ROWS UNBOUNDED PRECEDING) as RUNNING_TOTAL " + "FROM PHOTOS " + - "ORDER BY UPLOADED_AT DESC", + "ORDER BY UPLOADED_AT DESC", nativeQuery = true) List findPhotosWithStatistics(); } \ No newline at end of file diff --git a/src/main/java/com/photoalbum/service/impl/PhotoServiceImpl.java b/src/main/java/com/photoalbum/service/impl/PhotoServiceImpl.java index fa379a5..89ff478 100644 --- a/src/main/java/com/photoalbum/service/impl/PhotoServiceImpl.java +++ b/src/main/java/com/photoalbum/service/impl/PhotoServiceImpl.java @@ -83,7 +83,7 @@ public UploadResult uploadPhoto(MultipartFile file) { if (!allowedMimeTypes.contains(file.getContentType().toLowerCase())) { result.setSuccess(false); result.setErrorMessage("File type not supported. Please upload JPEG, PNG, GIF, or WebP images."); - logger.warn("Upload rejected: Invalid file type {} for {}", + logger.warn("Upload rejected: Invalid file type {} for {}", file.getContentType(), file.getOriginalFilename()); return result; } @@ -92,7 +92,7 @@ public UploadResult uploadPhoto(MultipartFile file) { if (file.getSize() > maxFileSizeBytes) { result.setSuccess(false); result.setErrorMessage(String.format("File size exceeds %dMB limit.", maxFileSizeBytes / 1024 / 1024)); - logger.warn("Upload rejected: File size {} exceeds limit for {}", + logger.warn("Upload rejected: File size {} exceeds limit for {}", file.getSize(), file.getOriginalFilename()); return result; } @@ -113,11 +113,11 @@ public UploadResult uploadPhoto(MultipartFile file) { Integer width = null; Integer height = null; byte[] photoData = null; - + try { // Read file content for database storage photoData = file.getBytes(); - + // Extract image dimensions from byte array try (ByteArrayInputStream bis = new ByteArrayInputStream(photoData)) { BufferedImage image = ImageIO.read(bis); @@ -139,7 +139,7 @@ public UploadResult uploadPhoto(MultipartFile file) { // Create photo entity with database BLOB storage Photo photo = new Photo( file.getOriginalFilename(), - photoData, // Store actual photo data in Oracle database + photoData, // Store actual photo data in PostgreSQL database storedFileName, relativePath, // Keep for compatibility, not used for serving file.getSize(), @@ -155,10 +155,10 @@ public UploadResult uploadPhoto(MultipartFile file) { result.setSuccess(true); result.setPhotoId(photo.getId()); - logger.info("Successfully uploaded photo {} with ID {} to Oracle database", + logger.info("Successfully uploaded photo {} with ID {} to PostgreSQL database", file.getOriginalFilename(), photo.getId()); } catch (Exception ex) { - logger.error("Error saving photo to Oracle database for {}", file.getOriginalFilename(), ex); + logger.error("Error saving photo to PostgreSQL database for {}", file.getOriginalFilename(), ex); result.setSuccess(false); result.setErrorMessage("Error saving photo to database. Please try again."); } @@ -185,13 +185,13 @@ public boolean deletePhoto(String id) { Photo photo = photoOpt.get(); - // Delete from Oracle database (photos stored as BLOB) + // Delete from PostgreSQL database (photos stored as BLOB) photoRepository.delete(photo); - logger.info("Successfully deleted photo ID {} from Oracle database", id); + logger.info("Successfully deleted photo ID {} from PostgreSQL database", id); return true; } catch (Exception ex) { - logger.error("Error deleting photo with ID {} from Oracle database", id, ex); + logger.error("Error deleting photo with ID {} from PostgreSQL database", id, ex); throw new RuntimeException("Error deleting photo", ex); } } diff --git a/src/main/resources/application-docker.properties b/src/main/resources/application-docker.properties index 8dff306..ceb5b55 100644 --- a/src/main/resources/application-docker.properties +++ b/src/main/resources/application-docker.properties @@ -1,8 +1,11 @@ -# Docker-specific configuration for Oracle DB -spring.datasource.url=jdbc:oracle:thin:@oracle-db:1521:XE -spring.datasource.username=photoalbum -spring.datasource.password=photoalbum -spring.datasource.driver-class-name=oracle.jdbc.OracleDriver +# Docker-specific configuration for PostgreSQL DB +# Migrated from Oracle to PostgreSQL according to property check item 1: Change JDBC URL from Oracle to PostgreSQL. +spring.datasource.url=jdbc:postgresql://postgres-db:5432/photoalbum +# Migrated from Oracle to PostgreSQL according to property check item 5: Change username and password to environment variable placeholder. +spring.datasource.username=${POSTGRES_USERNAME:photoalbum} +spring.datasource.password=${POSTGRES_PASSWORD:photoalbum} +# Migrated from Oracle to PostgreSQL according to property check item 2: Replace Oracle JDBC driver class with PostgreSQL driver class. +spring.datasource.driver-class-name=org.postgresql.Driver # Character encoding server.servlet.encoding.charset=UTF-8 @@ -10,12 +13,13 @@ server.servlet.encoding.enabled=true server.servlet.encoding.force=true # JPA Configuration for Docker -spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +# Migrated from Oracle to PostgreSQL according to property check item 3: Change Hibernate dialect from Oracle to PostgreSQL. +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true -# File Upload Configuration - Validation only (photos stored in Oracle database) +# File Upload Configuration - Validation only (photos stored in PostgreSQL database) app.file-upload.max-file-size-bytes=10485760 app.file-upload.allowed-mime-types=image/jpeg,image/png,image/gif,image/webp app.file-upload.max-files-per-upload=10 diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cc00cc2..ddeaebe 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,14 +6,18 @@ server.servlet.encoding.charset=UTF-8 server.servlet.encoding.enabled=true server.servlet.encoding.force=true -# Oracle Database Configuration -spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE -spring.datasource.username=photoalbum -spring.datasource.password=photoalbum -spring.datasource.driver-class-name=oracle.jdbc.OracleDriver +# PostgreSQL Database Configuration +# Migrated from Oracle to PostgreSQL according to property check item 1: Change JDBC URL from Oracle to PostgreSQL. +spring.datasource.url=jdbc:postgresql://localhost:5432/photoalbum +# Migrated from Oracle to PostgreSQL according to property check item 5: Change username and password to environment variable placeholder. +spring.datasource.username=${POSTGRES_USERNAME:photoalbum} +spring.datasource.password=${POSTGRES_PASSWORD:photoalbum} +# Migrated from Oracle to PostgreSQL according to property check item 2: Replace Oracle JDBC driver class with PostgreSQL driver class. +spring.datasource.driver-class-name=org.postgresql.Driver # JPA Configuration -spring.jpa.database-platform=org.hibernate.dialect.OracleDialect +# Migrated from Oracle to PostgreSQL according to property check item 3: Change Hibernate dialect from Oracle to PostgreSQL. +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true