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:
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