Nuñito Calzada
Nuñito Calzada

Reputation: 1948

How to Enable and Configure Auditing in a Spring Boot Application

I have this main class:

@SpringBootApplication
@EnableScheduling
@ConfigurationPropertiesScan
@EnableJpaAuditing(auditorAwareRef = "auditorAwareImpl")
public class PlanetsApplication {


    public static void main(String[] args) {

        SpringApplication.run(PlanetsApplication.class, args);

    }
}

and

@Component("auditorAwareImpl")
public class AuditorAwareImpl implements AuditorAware<String> {

    @NotNull
    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of("system");
    }

}

and

@Getter
@Setter
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseEntity {


    @CreatedDate
    @Column(updatable = false)
    protected LocalDateTime createdAt;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedDate
    @Column(updatable = false)
    protected LocalDateTime updatedAt;

    @LastModifiedBy
    @Column(updatable = false)
    protected String updatedBy;


}

and

@Entity
@Table(name = "t_spotify_playlist", uniqueConstraints =
@UniqueConstraint(columnNames = {"playlistId", "sun", "moon"}))
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SpotifyPlayList extends BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String playlistId;
    @Column(length = 50)
    private String sun;
    @Column(length = 50)
    private String moon;


    // Many-to-Many relationship with SpotifyTrack
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "t_playlist_track", // Join table name
            joinColumns = @JoinColumn(name = "playlist_id"), // Foreign key for SpotifyPlayListDesc
            inverseJoinColumns = @JoinColumn(name = "track_id") // Foreign key for SpotifyTrack
    )
    private Set<SpotifyTrack> tracks; // Tracks associated with the playlist
    

}

I save the entity:

SpotifyPlayList spotifyPlayList2 = spotifyPlayListService.findAll().stream().findAny().get();
        spotifyPlayList2.setSun(spotifyPlayList2.getSun().toUpperCase());
        spotifyPlayListService.save(spotifyPlayList2);

        log.info("saved {} ", spotifyPlayList2);

but nothing is auditted in the DB

on the logs:

Hibernate: 
    update
        t_spotify_playlist 
    set
        moon=?,
        playlist_id=?,
        sun=? 
    where
        id=?

also tried

@Getter
@Setter
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseEntity {

    @CreatedDate
    @Column
    protected LocalDateTime createdAt;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedDate
    protected LocalDateTime updatedAt;

    @LastModifiedBy
    protected String updatedBy;

}

with the same result

Upvotes: 1

Views: 163

Answers (2)

Roar S.
Roar S.

Reputation: 11319

These two fields in BaseEntity should not have updatable = false, and can be written as

    @LastModifiedDate
    protected LocalDateTime updatedAt;

    @LastModifiedBy
    protected String updatedBy;

We can verify the behavior in tests

import no.mycompany.auditing.repository.AuditorAwareImpl;
import no.mycompany.auditing.repository.SpotifyPlayList;
import no.mycompany.auditing.repository.SpotifyPlayListRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.utility.DockerImageName;

import static org.junit.jupiter.api.Assertions.*;

@ActiveProfiles("test") // use settings from application-test.yml
@DataJpaTest
@Import(AuditorAwareImpl.class)
class SpotifyPlayListRepositoryTests {

    @Autowired
    TestEntityManager entityManager;

    @Autowired
    SpotifyPlayListRepository sut;

    @TestConfiguration
    static class MySQLTestContainerConfiguration {

        @Bean
        @ServiceConnection
        MySQLContainer<?> mysqlContainer() {
            return new MySQLContainer<>(DockerImageName.parse("mysql:latest"));
        }
    }

    @Test
    void saveNew_givenValidPlayList_expectAllAuditAwareColumnsToBePopulated() {
        var savedPlayList = sut.save(createValidPlayListInTest());

        // assert that all auditor aware fields are populated
        assertNotNull(savedPlayList.getCreatedAt());
        assertEquals("system", savedPlayList.getCreatedBy());
        assertNotNull(savedPlayList.getUpdatedAt());
        assertEquals("system", savedPlayList.getUpdatedBy());
    }

    @Test
    void updateExisting_givenExistingPlayListInDb_expectOnlyLastModifiedColsToBeUpdated() {
        // emulate existing record in db
        var existingPlayListInDb = entityManager.persist(createValidPlayListInTest());
        var originalUpdateAt = existingPlayListInDb.getUpdatedAt();

        // get and update the existing record
        var playListToUpdate = sut.findById(existingPlayListInDb.getId()).orElseThrow();
        playListToUpdate.setPlaylistId("~newPlaylistId~");
        sut.save(playListToUpdate);

        entityManager.flush(); // see the update statement in console

        // assert that updatedAt is updated
        var updatedPlayList = entityManager.find(SpotifyPlayList.class, existingPlayListInDb.getId());
        assertTrue(updatedPlayList.getUpdatedAt().isAfter(originalUpdateAt));
    }

    private static SpotifyPlayList createValidPlayListInTest() {
        return SpotifyPlayList.builder()
                .playlistId("~playlistId~")
                .sun("̃~sun~")
                .moon("~moon~")
                .build();
    }
}

Upvotes: 1

Anant Doshi
Anant Doshi

Reputation: 205

May be AuditorAware Bean was missing

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAwareImpl")
public class AuditConfiguration {    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }
}

x

Upvotes: -1

Related Questions