Home > Software engineering >  How to inject dependencies in an Entity listener?
How to inject dependencies in an Entity listener?

Time:10-18

I'm trying to perform an action prior to saving an Entity in a Spring boot application. After researching, I've seen that I cannot configure a sequence to automatically populate an Entity field that isn't the primary key. Thus, I manually created a sequence in my database and I'm trying to add a pre-save hook somewhere in my app to fetch the nextval() on my sequence and apply its value (after converting it to a String) to my field. I want to do things that way so I don't need to do it in the service side where instances of my Entity are created.

I have identified multiple ways to do that, including AOP and Entity listeners. So far my best guess for an elegant code is to use an Entity listener with a @PrePersist method that fetches the next value and apply it to my field.

The main problem I'm facing is I can't manage to inject my repository dependency in my Entity listener (since it's the repository that hosts the nextval() function). I have no idea why this is not working, why my dependency is always null while I can read on multiple websites that this implementation is supposed to work (like in this article for example).

Here's what I have so far (edited after the first two answers):

My repository:

package my.project.package.spi.jpa.repository;

@Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {

    ...

    @Query(nativeQuery = true, value = "SELECT nextval('serial_number_sequence')")
    Long getNextSerialNumber();

}

My Entity:

package my.project.package.spi.jpa.model;

@Entity
@EntityListeners(MyEntityListener.class)
@Table(name = "my_table")
public class MyEntity {

    @Id
    @SequenceGenerator(name = "my_table_id", sequenceName = "id_sequence")
    @GeneratedValue(generator = "my_table_id", strategy = GenerationType.SEQUENCE)
    @Column(name = "id")
    private long id;

    @Column(name = "serial_number", nullable = false)
    private String serialNumber;

    ...

    public void setSerialNumber(String serialNumber) {
        this.serialNumber = serialNumber;
    }
}

My Entity listener:

package my.project.package.spi.jpa.model;

@Component
public class MyEntityListener {

    @Autowired
    private MyEntityRepository myEntityRepository;

    @PrePersist
    private void beforeSave(MyEntity myEntity) {
        String serialNumber = String.format("2d", myEntityRepository.getNextSerialNumber());
        myEntity.setSerialNumber(serialNumber);
    }

}

My Spring boot application:

package my.project.package.starter;

@SpringBootApplication
@EntityScan(basePackages = { "my.project.package.spi.jpa.model" })
@EnableJpaRepositories(basePackages = { "my.project.package.spi.jpa.repository" })
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MyApplication.class);
        springApplication.addListeners(new ApplicationPidFileWriter());
        springApplication.run(args);
    }

}

Most notable Configuration file:

package my.project.package.starter.configuration;

@Configuration
@ComponentScan(basePackages = "my.project.package", includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
        DomainService.class))
public class DomainConfiguration {

}

My project follows the Clean Code architecture and is divided into multiple modules (one per SPI/API, one for the domain and one for the starter).

Can someone tell me what I'm doing wrong? How can I effectively access my repository from my Entity listener?

CodePudding user response:

@RequiredArgsConstructor

Generates a constructor with required arguments. Required arguments are final fields and fields with constraints such as @NonNull.

So if you intend to use constructor dependency injection, you should have marked the myEntityRepository as final.

 private final MyEntityRepository myEntityRepository;

else this field won't be initialized via the constructor and it will be always null.

CodePudding user response:

Lombok needs the field to be final. See here: https://www.baeldung.com/spring-injection-lombok

  • Related