Skip to content

AuditingEntityListener with Embeddable classes #2365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dgallego58 opened this issue Nov 25, 2021 · 4 comments
Closed

AuditingEntityListener with Embeddable classes #2365

dgallego58 opened this issue Nov 25, 2021 · 4 comments
Assignees
Labels
status: feedback-provided Feedback has been provided type: task A general task

Comments

@dgallego58
Copy link

dgallego58 commented Nov 25, 2021

i see that an Entity with @CreatedDate and @LastModifiedDate and other auditable annotations works nice when those attributes are in the same class annotated with @entity, but it doesn't seem to work when those are on a @Embeddable annotated class

@Embeddable
public class Auditable {

    @CreatedDate
    private Instant cratedDate;

    @LastModifiedDate
    private Instant lastModifiedDate;
     ...
}
@Entity
@EntityListeners(AuditableListener.class) //this works with embedded, it is a custom Auditable
//@EntityListeners(AuditingEntityListener.class) //doesn't work for embedded
public class EntityB implements AuditableEntity{

    @Id
    @GeneratedValue
    private UUID uuid;

    private String randomAtt;

    @Embedded
    private AuditablePart auditablePart;

    ...

it would be cool if it can be used a similar approach by using the same AuditingEntityListener.class in addition to a custom one

i made a demo project showing this

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 25, 2021
@dgallego58 dgallego58 changed the title AuditinEntityLister with Embeddable classes AuditingEntityLister with Embeddable classes Nov 25, 2021
@dgallego58 dgallego58 changed the title AuditingEntityLister with Embeddable classes AuditingEntityListener with Embeddable classes Nov 25, 2021
@schauder schauder added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Nov 29, 2021
@gregturn
Copy link
Contributor

I crafted a test case and verified that indeed, @EntityListeners(AuditingEntityListener.class) works for @Embedded entities.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class AuditableEntity {

	@Id
	@GeneratedValue //
	private Long id;

	private String data;

	@Embedded //
	private AuditableEmbeddable auditDetails;
	...
}
@Embeddable
public class AuditableEmbeddable {

	@CreatedDate //
	private Instant dateCreated;

	@LastModifiedDate //
	private Instant dateUpdated;
	...
}
public interface AuditableEntityRepository extends JpaRepository<AuditableEntity, Long> {}
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:auditing/auditing-entity-with-embeddable-listener.xml")
public class AuditingEntityWithEmbeddableListenerTests {

	@Autowired AuditableEntityRepository repository;

	private AuditableEntity entity;
	private AuditableEmbeddable auditDetails;

	@BeforeEach
	void setUp() {

		entity = new AuditableEntity();
		entity.setId(1L);
		entity.setData("original value");

		auditDetails = new AuditableEmbeddable();
		entity.setAuditDetails(auditDetails);
	}

	@Test
	void auditsEmbeddedCorrectly() {

		// when
		repository.saveAndFlush(entity);
		Optional<AuditableEntity> optionalEntity = repository.findById(1L);

		// then
		assertThat(optionalEntity).isNotEmpty();

		AuditableEntity auditableEntity = optionalEntity.get();
		assertThat(auditableEntity.getData()).isEqualTo("original value");

		assertThat(auditableEntity.getAuditDetails().getDateCreated()).isNotNull();
		assertThat(auditableEntity.getAuditDetails().getDateUpdated()).isNotNull();

		Instant originalCreationDate = auditableEntity.getAuditDetails().getDateCreated();
		Instant originalDateUpdated = auditableEntity.getAuditDetails().getDateUpdated();

		auditableEntity.setData("updated value");

		repository.saveAndFlush(auditableEntity);

		Optional<AuditableEntity> optionalRevisedEntity = repository.findById(1L);

		assertThat(optionalRevisedEntity).isNotEmpty();

		AuditableEntity revisedEntity = optionalRevisedEntity.get();
		assertThat(revisedEntity.getData()).isEqualTo("updated value");

		assertThat(revisedEntity.getAuditDetails().getDateCreated()).isEqualTo(originalCreationDate);
		assertThat(revisedEntity.getAuditDetails().getDateUpdated()).isAfter(originalDateUpdated);
	}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/data/jpa https://www.springframework.org/schema/data/jpa/spring-jpa.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<import resource="../infrastructure.xml"/>

	<jpa:auditing/>

	<jpa:repositories base-package="org.springframework.data.jpa.repository.sample"/>

</beans>

When I run this test case, it passes just fine, proving that the embeddable fields are populated and updated by the AuditingEntityListener.

Something I'm missing?

@gregturn gregturn added the status: waiting-for-feedback We need additional information before we can continue label Mar 18, 2022
@gregturn
Copy link
Contributor

If there is no further comment, I will add these additional test cases to our test suite.

@gregturn gregturn added type: task A general task and removed type: bug A general bug labels Mar 18, 2022
@dgallego58
Copy link
Author

dgallego58 commented Mar 20, 2022

Hello, sorry for the late answer, i upgraded the demo i made to 2.6.4 from 2.5.7, and still the same, to replicate the problem i did this:

The entity:

import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.UUID;

@Entity
@EntityListeners(AuditingEntityListener.class) //doesn't work for embedded
public class EntityB implements AuditableEntity {

    @Id
    @GeneratedValue
    private UUID uuid;

    private String randomAtt;

    @Embedded
    private Auditable auditable;

    //getter and setters
    //toString

}

Note: im using composition over inheritance for the embedded auditable.

the auditable class:

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;

import javax.persistence.Embeddable;
import java.time.Instant;

@Embeddable
public class Auditable {

    @CreatedDate //-> doesnt work with @EntityListeners(AuditingEntityListener.class)
    private Instant dateCreated;

    @LastModifiedDate
    private Instant dateUpdated;
   
   //getter and setter

}

The interface that acts as composition AuditableEntity that the Entity implements

public interface AuditableEntity {

    Auditable getAuditable();

    void setAuditable(Auditable auditable);

}

Repo interface:

import com.example.demoauditable.entities.EntityB;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.UUID;

public interface EntityBRepository extends JpaRepository<EntityB, UUID> {
}

And the demo...

@SpringBootApplication
@EnableJpaAuditing
public class DemoAuditableApplication {

    private static final Logger log = LoggerFactory.getLogger(DemoAuditableApplication.class);

    @Autowired
    EntityBRepository entityBRepository;


    public static void main(String[] args) {
        SpringApplication.run(DemoAuditableApplication.class, args);
    }

    @Bean
    ApplicationRunner afterInit() {
        return args -> {

            EntityB entityB = new EntityB().setRandomAtt("test2");
            entityBRepository.save(entityB);

            List<EntityB> bResult = entityBRepository.findAll();
            log.info("EntityB result: {}", bResult);
        };
    }
}

output

EntityB result: [EntityB(uuid = dd8befb9-2f29-4c0f-9360-803b6b740a8a, randomAtt = test2, auditablePart = null)]

EDIT: I did another test initializing the @Embedded attribute with new Auditable(); and it did work as expected.
output
EntityB result: [EntityB(uuid = dd8befb9-2f29-4c0f-9360-803b6b740a8a, randomAtt = test2, auditablePart = Auditable(dateCreated = 2022-03-20T16:13:13.326813Z, dateUpdated = 2022-03-20T16:13:13.326813Z))]

it seems that is an issue with Hibernate doesnt initialize by default the composite components so i added this property to avoid using new keyword but it remains the same (return null)

spring:
  jpa:
    properties:
      hibernate:
        create_empty_composites:
          enabled: true
    open-in-view: false

so i think the solution to this is to initialize your @Embedded attribute in your entity class

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 20, 2022
@gregturn
Copy link
Contributor

gregturn commented Mar 22, 2022

It looks like https://hibernate.atlassian.net/browse/HHH-11936 is an effort currently open to track Hibernate's experimental flag hibernate.create_empty_composites.enabled. I wouldn't rely upon this. Not totally sure what it's supposed to do. (More: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#_misc_options)

If we are in agreement that the issue is properly initializing the embeddable entry, I think we're done here. I will gladly merge these additional test cases I wrote into the main branch and forward merge to 3.0.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided type: task A general task
Projects
None yet
Development

No branches or pull requests

4 participants