wiki:AdvancedApplicationDevelopment

Напреден апликативен развој

Трансакции

Зачувување на нов корисник

@Transactional
public UserResponseDto registerAndLogIn(HttpServletResponse response, AuthRequestDto authRequestDto)
    throws IOException {
    if (userRepository.findByUsername(authRequestDto.username()).isPresent()){
       throw new UserAlreadyExistsException();
    }
    User user = createNonAdminUser(authRequestDto);
    return authenticateAndRespond(response, user);
}


@Transactional
public User createNonAdminUser(AuthRequestDto authRequestDto) throws IOException {
    User.UserBuilder userBuilder = User.builder()
            .username(authRequestDto.username())
            .fullName(authRequestDto.fullname())
            .email(authRequestDto.email())
            .password(passwordEncoder.encode(authRequestDto.password()))
            .role(Role.NONADMIN)
            .artist(authRequestDto.userType().contains(UserType.ARTIST))
            .listener(authRequestDto.userType().contains(UserType.LISTENER));


    MultipartFile profilePhoto = authRequestDto.profilePhoto();
    if (profilePhoto != null && !profilePhoto.isEmpty()) {
        String contentType = profilePhoto.getContentType();
        if (contentType == null || !contentType.startsWith("image/")) {
            throw InvalidFileException.invalidType();
        }

        if (profilePhoto.getSize() > MAX_FILE_SIZE) {
            throw InvalidFileException.tooLarge(MAX_FILE_SIZE);
        }

        String filename = UUID.randomUUID() + "-" + profilePhoto.getOriginalFilename();
        Path path = Paths.get("uploads/profile-pictures", filename);
        Files.copy(profilePhoto.getInputStream(), path);
        userBuilder.profilePhoto("profile-pictures/" + filename);
    }

    User user = userBuilder.build();

    NonAdminUser nonAdminUser = new NonAdminUser();
    nonAdminUser.setUser(user);
    user.setNonAdminUser(nonAdminUser);
    nonAdminUserRepository.save(nonAdminUser);

    if (user.isArtist()){
        Artist artist = new Artist();
        artist.setNonAdminUser(nonAdminUser);
        artistRepository.save(artist);
    }
    if (user.isListener()){
        Listener listener = new Listener();
        listener.setNonAdminUser(nonAdminUser);
        listenerRepository.save(listener);
    }

    return user;
}

Објавување на нова песна

@Transactional
public void handleSongPublish(PublishSongRequestDto requestDto) throws IOException {
    Long userId = authService.getCurrentUserID();
    Artist artist = artistService.getArtistById(userId);

    MusicalEntity musicalEntity = createMusicalEntity(
            requestDto.getTitle(),
            requestDto.getGenre(),
            requestDto.getCover(),
            artist
    );

    Song song = new Song();
    song.setAlbum(null);
    song.setLink(requestDto.getLink());
    song.setMusicalEntities(musicalEntity);
    songRepository.save(song);

    // add publishing artist
    saveContribution(artist, musicalEntity, MAIN_VOCAL_ROLE);

    // add additional contributors
    saveContributionsFromDto(musicalEntity, requestDto.getContributors());
}

@Transactional
protected MusicalEntity createMusicalEntity(String title, String genre, MultipartFile cover, Artist releasedBy)
        throws IOException {
    MusicalEntity.MusicalEntityBuilder builder = MusicalEntity.builder()
            .title(title)
            .genre(genre)
            .releaseDate(LocalDate.now())
            .releasedBy(releasedBy);

    if (cover != null && !cover.isEmpty()) {
        try {
            String filename = saveCoverPhoto(cover);
            builder.cover("profile-pictures/" + filename);
        } catch (Exception ignored) {}
    }

    return musicalEntityRepository.save(builder.build());
}

@Transactional
protected void saveContribution(Artist artist, MusicalEntity musicalEntity, String role) {
    ArtistContributionId id = new ArtistContributionId();
    id.setArtistId(artist.getId());
    id.setMusicalEntityId(musicalEntity.getId());

    ArtistContribution contribution = new ArtistContribution();
    contribution.setId(id);
    contribution.setArtist(artist);
    contribution.setMusicalEntity(musicalEntity);
    contribution.setRole(role);

    artistContributionRepository.save(contribution);
}

@Transactional
protected void saveContributionsFromDto(MusicalEntity musicalEntity, List<ArtistContributionSummaryDto> contributors) {
    if (contributors == null) return;

    for (ArtistContributionSummaryDto contributorDto : contributors) {
        if (contributorDto.getId() == null) continue;

        Artist contributor = artistService.getArtistById(contributorDto.getId());
        saveContribution(contributor, musicalEntity, contributorDto.getRole());
    }
}

Објавување на нов албум

@Transactional
public void handleAlbumPublish(PublishAlbumRequestDto requestDto) throws IOException {
    Long userId = authService.getCurrentUserID();
    Artist artist = artistService.getArtistById(userId);

    MusicalEntity albumMusicalEntity = createMusicalEntity(
            requestDto.getTitle(),
            requestDto.getGenre(),
            requestDto.getCover(),
            artist
    );

    Album album = new Album();
    album.setMusicalEntities(albumMusicalEntity);
    album = albumRepository.save(album);

    // add publishing artist
    saveContribution(artist, albumMusicalEntity, MAIN_ROLE);

    // track artists already added to avoid duplicates
    Set<Long> albumContributorIds = new HashSet<>();
    albumContributorIds.add(artist.getId());

    List<AlbumSongsDto> albumSongs = requestDto.getAlbumSongs();
    for (AlbumSongsDto songDto : albumSongs) {
        createSongForAlbum(albumMusicalEntity, album, songDto, artist, albumContributorIds);
    }
}


@Transactional
protected void createSongForAlbum(MusicalEntity albumMe, Album album, AlbumSongsDto songDto, 
                                    Artist publishingArtist, Set<Long> albumContributorIds) {
    MusicalEntity songMusicalEntity = MusicalEntity.builder()
            .title(songDto.getTitle())
            .releaseDate(albumMe.getReleaseDate())
            .releasedBy(albumMe.getReleasedBy())
            .cover(albumMe.getCover())
            .genre(albumMe.getGenre())
            .build();
    songMusicalEntity = musicalEntityRepository.save(songMusicalEntity);

    Song song = new Song();
    song.setLink(songDto.getLink());
    song.setAlbum(album);
    song.setMusicalEntities(songMusicalEntity);
    songRepository.save(song);

    saveContribution(publishingArtist, songMusicalEntity, MAIN_VOCAL_ROLE);

    // add song-specific contributors
    List<ArtistContributionSummaryDto> songContributors = songDto.getContributors();
    if (songContributors != null) {
        for (ArtistContributionSummaryDto contributorDto : songContributors) {
            if (contributorDto.getId() == null) continue;
            
            Artist contributor = artistService.getArtistById(contributorDto.getId());
            
            // first add to song
            saveContribution(contributor, songMusicalEntity, contributorDto.getRole());
            
            // then add to album if not already added
            if (!albumContributorIds.contains(contributor.getId())) {
                saveContribution(contributor, album.getMusicalEntities(), contributorDto.getRole());
                albumContributorIds.add(contributor.getId());
            }
        }
    }
}

Pooling

Бидејќи користиме Spring Boot за развој на backend делот од нашата апликација, не треба рачно да ги правиме конекциите кон базата. Pooling во Spring Boot се менаџира од страна на HikariCP.

HikariCP е зависност на spring-boot-starter-jdbc, што е зависност на spring-boot-starter-data-jpa, така што доколку во pom.xml додадеме:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

HikariCP ќе ни биде додадено како транзитивна зависност.

Во нашиот проект не ја менувавме конфигурацијата на Hikari, така што се користат default вредности, кои се следниве:

spring.datasource.hikari.maximum-pool-size  = 10
spring.datasource.hikari.minimum-idle       = 10
spring.datasource.hikari.connection-timeout = 30000   #(30 seconds)
spring.datasource.hikari.idle-timeout       = 600000  #(10 minutes)
spring.datasource.hikari.max-lifetime       = 1800000 #(30 minutes)
spring.datasource.hikari.auto-commit        = true

По потреба истите можат да се менуваат.

Дополнително, воспоставување на конекциите можеме да забележиме во логовите на тунел скриптата кога ќе ја започнеме Spring Boot апликацијата

debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 2: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 3: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 4: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 5: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 6: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 7: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 8: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 9: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 10: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
debug1: Connection to port 9999 forwarding to localhost port 5432 requested.
debug1: channel 11: new direct-tcpip [direct-tcpip] (inactive timeout: 0)
Last modified 2 weeks ago Last modified on 02/12/26 10:12:41
Note: See TracWiki for help on using the wiki.