I have a personal program that uses Springboot and Javafx. I've been trying to gain more skills in creating springboot and javafx apps. Program launches an fxml page where a person can update a person's records, clicks save, and is then saved to an mssql database.
I'm trying to save a new person record to a mssql database.
PersonEntity.java
@Entity
@Getter
@NoArgsConstructor(force = true)
@Data
@Table(name = "persons")
public class PersonEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "personID", nullable = false)
private final Integer personID;
@Column(name = "first_name")
@Setter
private String firstName;
@Column(name = "last_name")
@Setter
private String lastName;
@Column(name = "dob")
@Setter
private String dob;
PersonRepo.java
@Repository
public interface PersonRepo extends JpaRepository<PersonEntity, String> {
List<PersonEntity> findAll();
}
PersonService.java
public interface PersonService{
Optional<PersonEntity> save(PersonEntity personEntity);
PersonEntity update(PersonEntity personEntity);
PersonEntity findAll();
}
PersonServiceImpl.java
@Service
@RequiredArgsConstructor
public class PersonServiceImplimplements PersonService {
@Autowired
private PersonRepo repo;
@Override
public Optional<PersonEntity> save(PersonEntity personEntity) {
return Optional.of(repo.save(personEntity));
}
@Override
public PersonEntity update(PersonEntity personEntity){ return repo.save(personEntity);}
@Override
public PersonEntity findAll() {
return (PersonEntity ) repo.findAll();
}
}
PersonController.java
@Component
public class PersonController {
@Autowired
PersonService personService;
public TextField lName;
public TextField fName;
public TextField dob;
public Button onSaveNewPerson;
@FXML
public void onSaveNewPerson(ActionEvent event) throws SQLException {
if(fName != null){
PersonEntity newPerson = new PersonEntity ();
fName.setText(newPerson.getFirstName());
personService.save(newPerson);
}
}
PersonsRecord.fxml
<AnchorPane fx:id="scenePane" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.controllers.PersonController">
<Pane layoutX="-2.0" layoutY="91.0" prefHeight="800.0" prefWidth="1004.0">
<Label layoutX="14.0" layoutY="14.0" text="New Person Form">
<font>
<Font size="20.0"/>
</font>
</Label>
<Label layoutX="14.0" layoutY="69.0" text="First Name"/>
<Label layoutX="16.0" layoutY="130.0" text="Last name"/>
<Label layoutX="18.0" layoutY="130.0" text="Date of Birth"/>
<Label layoutX="20.0" layoutY="130.0" text="Address"/>
<TextField fx:id="fName" layoutX="98.0" layoutY="66.0"/>
<TextField fx:id="lName" layoutX="98.0" layoutY="126.0"/>
<TextField fx:id="dob" layoutX="98.0" layoutY="146.0"/>
<TextField fx:id="personAddress" layoutX="98.0" layoutY="166.0"/>
<Button fx:id="saveNewPerson" layoutX="803.0" layoutY="333.0" mnemonicParsing="false"
onAction="#onSaveNewPerson" prefHeight="58.0" prefWidth="187.0" text="Save New Person"/>
<Label layoutX="597.0" layoutY="70.0" text="DOB"/>
<TextField fx:id="dob" layoutX="691.0" layoutY="66.0"/>
</Pane>
</AnchorPane>
Currently after clicking the onSaveNewPersonbutton I receive the following error -
Cannot invoke "com.example.services.PersonService.save(com.example.entity.persons.PersonEntity)" because "this.personService" is null
I do want all the textfields to save but for right now I've just been testing with the fname field.
I've ensured the @Autowired is set for the personService in the controller. I also made sure the @Repository is set for the PersonRepo.
I also added the implements CommandLineRunner to the FxApplication file and had a test person added to the database to ensure proper connectivity. That ran just fine and a person was entered successfully. Code for investigation -
@SpringBootApplication
@EnableJpaRepositories("com.example.repo")
public class FxApplication extends Application implements CommandLineRunner {
private ConfigurableApplicationContext springContext;
private Parent root;
@Autowired
PersonRepo personRepo;
public static void main(String[] args){
launch(FxApplication.class, args);
}
@Override
public void start(Stage stage) throws Exception {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setControllerFactory(springContext::getBean);
fxmlLoader.setLocation(getClass().getResource("/PersonsRecord.fxml"));
Parent root = fxmlLoader.load();
stage.setTitle("Add NewPerson");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
@Override
public void init() throws Exception{
springContext = SpringApplication.run(FxApplication.class);
}
@Override
public void stop() throws Exception{
springContext.close();
Platform.exit();
}
@Override
public void run(String... args) throws Exception {
PersonEntity person = new PersonEntity ();
person.setFirstName("TestSpring3");
person.setLastName("LastNameTest4");
person.setDOB("1/1/2000")
personRepo.save(person);
}
}
I edited my code and used the template that was provided via @David Weber.
This is now my FxApplicationLauncher.java class that I have edited.
package com.example;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.*;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.stereotype.Component;
import java.io.IOException;
@SpringBootApplication
@EnableJpaRepositories("com.example.repo")
public class FxApplicationLauncher {
public static void main(String[] args) {
Application.launch(FxApplication.class, args);
}
public static class FxApplication extends Application {
private ConfigurableApplicationContext context;
@Override
public void init(){
ApplicationContextInitializer<GenericApplicationContext> initializer = applicationContext -> {
applicationContext.registerBean(Application.class, () -> FxApplication.this);
applicationContext.registerBean(Parameters.class, this::getParameters);
applicationContext.registerBean(HostServices.class, this::getHostServices);
};
this.context = new SpringApplicationBuilder()
.sources(FxApplicationLauncher.class)
.initializers(initializer)
.run(getParameters().getRaw().toArray(new String[0]));
}
@Override
public void start(Stage stage) { this.context.publishEvent(new StageIsReadyEvent(stage)); }
@Override
public void stop() throws Exception {
context.close();
Platform.exit();
System.exit(0);
}
@Component
public static class FxApplicationStageIsReadyListener implements ApplicationListener<StageIsReadyEvent>{
@Value("${spring.application.name}")
private String applicationTitle;
private ApplicationContext applicationContext;
public void JfxSpringBootStageIsReadyListener(
@Value("${spring.application.name}") String applicationTitle,
ApplicationContext applicationContext) {
this.applicationTitle = applicationTitle;
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(StageIsReadyEvent event) {
try {
FXMLLoader fxmlLoader = new FXMLLoader();
fxmlLoader.setLocation(getClass().getResource("/PersonsRecord.fxml"));
fxmlLoader.setControllerFactory(applicationContext::getBean);
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 600, 600);
Stage stage = event.getStage();
stage.setScene(scene);
stage.setTitle(this.applicationTitle);
stage.show();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static class StageIsReadyEvent extends ApplicationEvent {
public Stage getStage() {
return (Stage) getSource();
}
public StageIsReadyEvent(Stage source) {
super(source);
}
}
}
}
For some reason the applicationContext is null.
java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:208)
at com.example.gaitlabapp.FxApplicationLauncher$FxApplication$FxApplicationStageIsReadyListener.onApplicationEvent(FxApplicationLauncher.java:84)
at com.example.gaitlabapp.FxApplicationLauncher$FxApplication$FxApplicationStageIsReadyListener.onApplicationEvent(FxApplicationLauncher.java:65)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:451)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:384)
at com.example.gaitlabapp.FxApplicationLauncher$FxApplication.start(FxApplicationLauncher.java:51)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
at java.base/java.lang.Thread.run(Thread.java:842)
Explanation:
I am quite sure, that your service is null because your controller is not a Spring Component
. You marked it with @Component
, which is a nice idea, but because you use FXML
I bet, that your controller is not created by Spring Boot
, but by the FxmlLoader
. This leads to "ignoring" the annotation, because the FxmlLoader creates a new instance of your controller by using the keyword new
.
Example repos of mine which demonstrate the use of Spring Boot and JavaFx with Maven and Gradle.
JFX with Spring Boot and Gradle (better and more up to date)
JFX with Spring Boot and Maven
What to do next:
You have several options from here.
Spring Boot Application context
to a static variable and get your service in a non Spring Component by using Service service = springContext.getBean(Service.class)
.SpringFxmlLoader
class).Additional information:
Be aware when using Spring components. They can be stateless or stateful. In easier words: They can be a Singleton (scope singleton) or an own instance for every injection (scope prototype).