| | 1 | = Advanced Application Development |
| | 2 | == Transactional |
| | 3 | === Enrolling a user to a course === |
| | 4 | {{{ |
| | 5 | @Transactional |
| | 6 | public void enrollUserToCourse(Integer courseId, String email) { |
| | 7 | UserEntity userEntity = userEntityRepository.findByEmail(email).get(); |
| | 8 | User user = userRepository.findById(userEntity.getId()).get(); |
| | 9 | Course course = courseRepository.findById(courseId).get(); |
| | 10 | |
| | 11 | if(userSubscriptionService.hasUserSubscription(email) |
| | 12 | && enrollmentRepository.findByCourseAndUser(course, user).isEmpty()) { |
| | 13 | |
| | 14 | Enrollment enrollment = new Enrollment(); |
| | 15 | enrollment.setUser(user); |
| | 16 | enrollment.setCourse(course); |
| | 17 | enrollment.setEnrollDate(LocalDate.now()); |
| | 18 | enrollment.setCompletionStatus("NOT COMPLETE"); |
| | 19 | enrollment.setProgressPercentage(0); |
| | 20 | |
| | 21 | enrollmentRepository.save(enrollment); |
| | 22 | } |
| | 23 | } |
| | 24 | }}} |
| | 25 | - This transaction is responsible for enrolling a user in a course. |
| | 26 | - The method validates the user subscription and checks if the user is already enrolled before creating a new enrollment entry. |
| | 27 | |
| | 28 | === Completing a course and generating a certificate === |
| | 29 | {{{ |
| | 30 | @Transactional |
| | 31 | public void completeCourse(Integer courseId, String email) { |
| | 32 | UserEntity userEntity = userEntityRepository.findByEmail(email).get(); |
| | 33 | User user = userRepository.findById(userEntity.getId()).get(); |
| | 34 | Course course = courseRepository.findById(courseId).get(); |
| | 35 | |
| | 36 | Random random = new Random(); |
| | 37 | |
| | 38 | Enrollment enrollment = enrollmentRepository.findByCourseAndUser(course, user).get(); |
| | 39 | enrollment.setCompletionStatus("COMPLETE"); |
| | 40 | enrollment.setProgressPercentage(100); |
| | 41 | enrollmentRepository.save(enrollment); |
| | 42 | |
| | 43 | Certificate certificate = new Certificate(); |
| | 44 | certificate.setEnrollment(enrollment); |
| | 45 | certificate.setIssueDate(LocalDate.now()); |
| | 46 | certificate.setStatus("COMPLETE"); |
| | 47 | certificate.setCertificateCode("CODE" + random.nextLong()); |
| | 48 | |
| | 49 | certificateRepository.save(certificate); |
| | 50 | } |
| | 51 | }}} |
| | 52 | - This transaction updates the enrollment status and generates a certificate after successful course completion. |
| | 53 | - Both operations are executed within a single transaction to preserve database consistency. |
| | 54 | |
| | 55 | === Adding a user subscription and payment === |
| | 56 | {{{ |
| | 57 | @Transactional |
| | 58 | public void addUserSubscription(String email, Integer subscriptionPlanId) { |
| | 59 | UserEntity userEntity = userEntityRepository.findByEmail(email).get(); |
| | 60 | User user = userRepository.findById(userEntity.getId()).get(); |
| | 61 | SubscriptionPlan subscriptionPlan = subscriptionPlanRepository.findById(subscriptionPlanId).get(); |
| | 62 | |
| | 63 | UserSubscription userSubscription = new UserSubscription(); |
| | 64 | userSubscription.setUser(user); |
| | 65 | userSubscription.setPlan(subscriptionPlan); |
| | 66 | userSubscription.setStartDate(LocalDate.now()); |
| | 67 | userSubscription.setEndDate( |
| | 68 | LocalDate.now().plusMonths(subscriptionPlan.getDurationMonths()) |
| | 69 | ); |
| | 70 | userSubscription.setStatus("ACTIVE"); |
| | 71 | |
| | 72 | userSubscriptionRepository.save(userSubscription); |
| | 73 | |
| | 74 | Payment payment = new Payment(); |
| | 75 | payment.setUser(user); |
| | 76 | payment.setSubscription(userSubscription); |
| | 77 | payment.setAmount(subscriptionPlan.getPrice()); |
| | 78 | |
| | 79 | paymentRepository.save(payment); |
| | 80 | } |
| | 81 | }}} |
| | 82 | - This transaction creates a new subscription for a user and also creates a payment record connected to the subscription. |
| | 83 | |
| | 84 | === Checking expired subscriptions === |
| | 85 | {{{ |
| | 86 | @Scheduled(cron = "0 0 0 * * *") |
| | 87 | @Transactional |
| | 88 | public void checkExpiredSubscriptions() { |
| | 89 | |
| | 90 | LocalDate today = LocalDate.now(); |
| | 91 | |
| | 92 | List<UserSubscription> expiredSubscriptions = |
| | 93 | userSubscriptionRepository |
| | 94 | .findByEndDateBeforeAndStatusNot(today, "EXPIRED"); |
| | 95 | |
| | 96 | for (UserSubscription subscription : expiredSubscriptions) { |
| | 97 | subscription.setStatus("EXPIRED"); |
| | 98 | userSubscriptionRepository.save(subscription); |
| | 99 | } |
| | 100 | } |
| | 101 | }}} |
| | 102 | - This scheduled transaction automatically checks and updates expired subscriptions every day. |
| | 103 | |
| | 104 | === Creating a support ticket === |
| | 105 | {{{ |
| | 106 | @Transactional |
| | 107 | public void addSupportTicket( |
| | 108 | String email, |
| | 109 | String subject, |
| | 110 | String description |
| | 111 | ) { |
| | 112 | |
| | 113 | UserEntity userEntity = userEntityRepository.findByEmail(email).get(); |
| | 114 | User user = userRepository.findById(userEntity.getId()).get(); |
| | 115 | |
| | 116 | List<Administrator> admins = administratorRepository.findAll(); |
| | 117 | |
| | 118 | Random random = new Random(); |
| | 119 | Administrator assignedAdmin = |
| | 120 | admins.get(random.nextInt(admins.size())); |
| | 121 | |
| | 122 | SupportTicket supportTicket = new SupportTicket(); |
| | 123 | |
| | 124 | supportTicket.setUser(user); |
| | 125 | supportTicket.setAdmin(assignedAdmin); |
| | 126 | supportTicket.setSubject(subject); |
| | 127 | supportTicket.setDescription(description); |
| | 128 | supportTicket.setStatus("UNRESOLVED"); |
| | 129 | supportTicket.setCreatedAt(LocalDateTime.now()); |
| | 130 | |
| | 131 | supportTicketRepository.save(supportTicket); |
| | 132 | } |
| | 133 | }}} |
| | 134 | - This transaction creates a support ticket and automatically assigns an administrator responsible for resolving the issue. |
| | 135 | |
| | 136 | === Resolving a support ticket === |
| | 137 | {{{ |
| | 138 | @Transactional |
| | 139 | public void resolveSupportTicket(Integer ticketId) { |
| | 140 | |
| | 141 | SupportTicket supportTicket = |
| | 142 | supportTicketRepository.findById(ticketId).get(); |
| | 143 | |
| | 144 | supportTicket.setStatus("RESOLVED"); |
| | 145 | |
| | 146 | supportTicketRepository.save(supportTicket); |
| | 147 | } |
| | 148 | }}} |
| | 149 | - This transaction updates the support ticket status from unresolved to resolved. |
| | 150 | |
| | 151 | == Database Connection Pooling == |
| | 152 | - The application uses Spring Boot with Spring Data JPA to communicate with the PostgreSQL database. |
| | 153 | - Database connections are not created manually. Instead, they are automatically managed by HikariCP, which is the default connection pool implementation in Spring Boot. |
| | 154 | - HikariCP is included automatically through: |
| | 155 | {{{ |
| | 156 | spring-boot-starter-data-jpa |
| | 157 | }}} |
| | 158 | |
| | 159 | - The following configuration is used in the application: |
| | 160 | {{{ |
| | 161 | spring.application.name=db |
| | 162 | |
| | 163 | spring.datasource.url=jdbc:postgresql://localhost:3307/db_202526z_va_prj_olpms |
| | 164 | spring.datasource.username=db_202526z_va_prj_olpms_owner |
| | 165 | spring.datasource.password=3ad0b6760a91 |
| | 166 | spring.datasource.driver-class-name=org.postgresql.Driver |
| | 167 | |
| | 168 | spring.jpa.hibernate.ddl-auto=validate |
| | 169 | |
| | 170 | spring.jpa.show-sql=true |
| | 171 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect |
| | 172 | |
| | 173 | spring.datasource.hikari.maximum-pool-size=10 |
| | 174 | spring.datasource.hikari.minimum-idle=5 |
| | 175 | spring.datasource.hikari.idle-timeout=30000 |
| | 176 | spring.datasource.hikari.connection-timeout=20000 |
| | 177 | }}} |
| | 178 | |
| | 179 | - These values allow the application to: |
| | 180 | * reuse database connections efficiently |
| | 181 | * improve performance |
| | 182 | * reduce connection creation overhead |
| | 183 | * support multiple simultaneous database requests |
| | 184 | |
| | 185 | - Database connections from the pool are automatically used in repository operations such as: |
| | 186 | * courseRepository.save() |
| | 187 | * enrollmentRepository.save() |
| | 188 | * userSubscriptionRepository.save() |
| | 189 | * supportTicketRepository.save() |