简体   繁体   中英

Spring Boot AuditAware - Database not updating

I am new to Spring Boot and want to add auditing support. Most of my inspiration is from https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#auditing , and a good amount of searching and trying.

The audit columns (created, created_by, last_modified, last_modified_by) are not being updated in the database.

Persistence Configuration

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories("com.ncc.vrts")
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {
    @Bean
    AuditorAware<User> auditorAware() {
        return new AuditorAwareImpl();
    }
}

AuditAwareImpl

I checked this with the debugger, the User is properly returned

@Component
public class AuditorAwareImpl implements AuditorAware<User> {
        @Override
        public Optional<User> getCurrentAuditor() {
            return Optional.ofNullable(SecurityContextHolder.getContext())
                    .map(SecurityContext::getAuthentication)
                    .filter(Authentication::isAuthenticated)
                    .map(Authentication::getPrincipal)
                    .map(User.class::cast);
        }
    }

AuditMetadata class

@Embeddable
public class AuditMetadata  {
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
    @JoinColumn(name = "created_by")
    @CreatedBy
    private User createdBy;
    @Column(name = "created")
    @CreatedDate
    private Timestamp createdDate;
    @LastModifiedBy
    @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST})
    @JoinColumn(name = "last_modified_by")
    private User lastModifiedBy;
    @Column(name = "last_modified")
    @LastModifiedDate
    private Timestamp lastModifiedDate;

    // no setters or getters (although I tried with them and it didn't work either)

Entity

@Entity
@Table(name = "trip_request")
@SQLDelete(sql = "UPDATE trip_request SET deleted = true WHERE id=?")
@FilterDef(name = "deletedRequestFilter", parameters = @ParamDef(name = "isDeleted", type = Boolean.class))
@Filter(name = "deletedRequestFilter", condition = "deleted = :isDeleted")
@EntityListeners(AuditingEntityListener.class)
public class Request {
    ...
    @Embedded
    private AuditMetadata auditingMetadata;
    ...
    // no setter or getter
    ...

Controller call

// .save is not overridden
requestRepository.save(request);

Resulting SQL on.save()

Hibernate: select r1_0.id,r1_0.created_by,r1_0.created,r1_0.last_modified_by,r1_0.last_modified,r1_0.deleted,r1_0.expected_return_date_time,r1_0.last_updated,r1_0.note,r1_0.purpose,r1_0.request_date_time,r1_0.request_submitted,r1_0.status,r1_0.status_reviewed,r1_0.status_user_id,r1_0.user_id,r1_0.vehicle_id from trip_request r1_0 where r1_0.id=?
Hibernate: update trip_request set created_by=?, created=?, last_modified_by=?, last_modified=?, deleted=?, expected_return_date_time=?, last_updated=?, note=?, purpose=?, request_date_time=?, request_submitted=?, status=?, status_reviewed=?, status_user_id=?, user_id=?, vehicle_id=? where id=?

Database view

select * from trip_request where id=8\G;
*************************** 1. row ***************************
                        id: 8
... all fields updated as expected except audit
                   created: NULL
             last_modified: NULL
                created_by: NULL
          last_modified_by: NULL

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.0.2'
    id 'io.spring.dependency-management' version '1.1.0'
}

group = 'com.ncc'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.okta.spring:okta-spring-boot-starter:3.0.3'
    implementation 'org.springframework.boot:spring-boot-starter-mail'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.2.0'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
    implementation 'javax.servlet:javax.servlet-api:4.0.1'
    implementation 'io.hypersistence:hypersistence-utils-parent:3.2.0'
    implementation 'io.hypersistence:hypersistence-utils-hibernate-60:3.2.0'
    implementation 'io.openliberty.features:com.ibm.websphere.appserver.securityContext-1.0:23.0.0.2'
    runtimeOnly 'com.mysql:mysql-connector-j'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

Seen in the Run Window

2023-05-30T07:46:53.311-04:00  INFO 15224 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-05-30T07:46:53.316-04:00  INFO 15224 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'entityManagerFactory' of type [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

The root cause of the failure was in the SecurityConfiguration , specifically the SecurityExpression handler. This code is what worked:

SecurityConfiguration

I changed from using @EnableMethodSecurity to @EnableWebSecurity

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration {

    @Bean
    public RoleHierarchyImpl roleHierarchy() {
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("""
            ROLE_ADMIN > ROLE_CAMPUS_SAFETY
            ROLE_CAMPUS_SAFETY > ROLE_USER
        """);
        return roleHierarchy;
    }

    @Bean
    public DefaultWebSecurityExpressionHandler expressionHandler() {
        DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
        expressionHandler.setRoleHierarchy(roleHierarchy());
        return expressionHandler;
    }

I updated the entity to extend a base class with the audit and version data:

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseEntity  {
    @Version
    @Column(name = "version")
    private Long version;
    @ManyToOne
    @JoinColumn(name = "created_by")
    @CreatedBy
    private User createdBy;
    @Column(name = "created",updatable = false)
    @CreatedDate
    private Timestamp createdDate;
    @LastModifiedBy
    @ManyToOne
    @JoinColumn(name = "last_modified_by")
    private User lastModifiedBy;
    @Column(name = "last_modified")
    @LastModifiedDate
    private Timestamp lastModifiedDate;

    public void setVersion(Long version) {
        this.version = version;
    }

    public Long getVersion() {
        return version;
    }
}

Entity

Specifically Request , note the 'extends BaseEntity'

@Entity
@Table(name = "trip_request")
@SQLDelete(sql = "UPDATE trip_request SET deleted = true WHERE id=?")
@FilterDef(name = "deletedRequestFilter", parameters = @ParamDef(name = "isDeleted", type = Boolean.class))
@Filter(name = "deletedRequestFilter", condition = "deleted = :isDeleted")
public class Request extends BaseEntity { ... }

PersistenceConfiguration

@Configuration
@EnableJpaRepositories("com.ncc.vms")
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class PersistenceConfiguration {

    @Bean
    AuditorAware<User> auditorAware() {
        return new AuditorAwareImpl();
    }
}

I apologize for not explaining WHY the problem occurred - but it was related to these messages:

2023-05-30T07:46:53.316-04:00 INFO 15224 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker: Bean 'entityManagerFactory' of type [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

I believe that the SecurityExpression was interfering with the workflow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM