Search code examples
javaspringspring-bootauditaudit-logging

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


Solution

  • 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();
        }
    }