1

I'm using Flyway to attempt to create and then seed a database when the Spring Boot web app starts up. Flyway successfully creates the database tables in the first migration, but fails to populate them in the 2nd because of a NullPointerException.

Here is the migration code, called V2_seed_database.java, located in package db.migration:

package db.migration;

import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;

import net.tekknow.medaverter.db.seeds.AppointmentSeeder;

public class V2__seed_database extends BaseJavaMigration {
    public void migrate(Context context) {
        AppointmentSeeder appointmentSeeder = new AppointmentSeeder();
        appointmentSeeder.seed();
    }
}

Here is the AppointmentSeeder code:

package net.tekknow.medaverter.db.seeds;

import org.json.JSONArray; 
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import net.tekknow.medaverter.domain.Appointment;
import net.tekknow.medaverter.service.AppointmentService;

@Service
public class AppointmentSeeder {
    @Autowired
    AppointmentService appointmentService;

    @PostConstruct
    public void seed() {
        String json = "[" +
            "{\"id\":1,\"patient_id\":1,\"dateTime\":\"10/29/2010\",\"physician_id\":1,\"lab_id\":1,\"note_id\":0}" +
        "]";
        org.json.JSONArray appointments = new JSONArray(json);

        for (int i=0; i<appointments.length(); i++) {
            JSONObject appointment = appointments.getJSONObject(i);
            Appointment dbAppointment = new Appointment();
            dbAppointment.setId(appointment.getInt("id"));
            dbAppointment.setPatientId(appointment.getInt("patient_id"));
            dbAppointment.setDateTime(appointment.getString("dateTime"));
            dbAppointment.setPhysicianId(appointment.getInt("physician_id"));
            dbAppointment.setLabId(appointment.getInt("lab_id"));
            dbAppointment.setNoteId(appointment.getInt("note_id"));
            appointmentService.save(dbAppointment);
        }
    }
}   

Here is the AppointmentRepository code:

package net.tekknow.medaverter.db;

import org.springframework.data.jpa.repository.JpaRepository;
import net.tekknow.medaverter.domain.Appointment;

public interface AppointmentRepository extends JpaRepository<Appointment,Integer> {
}

And here is the AppointmentService code:

package net.tekknow.medaverter.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import net.tekknow.medaverter.db.AppointmentRepository;
import net.tekknow.medaverter.domain.Appointment;

@Service
@Transactional
public class AppointmentService {

    @Autowired
    AppointmentRepository repo;

    public void save(Appointment appointment) {
        System.out.println("AppointmentService.save: appointment="+appointment.toString());
        repo.save(appointment);  //its failing here
    }  
}

Here is the Appointment bean:

package net.tekknow.medaverter.domain;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

@Entity
@Table(name = "appointments")
public class Appointment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @NotBlank
    @Column(unique = true)
    private int patient_id;
    @Size(max = 32)
    private String date_time;
    private int physician_id;
    private int lab_id;
    private int note_id;

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
... other getters and setters, truncated for brevity

When I run the program, the migration starts but fails on this line:

    repo.save(appointment);  //its failing here

with the following error message:

Caused by: java.lang.NullPointerException: null at net.tekknow.medaverter.service.AppointmentService.save(AppointmentService.java:32) ~[classes/:na]

Just before the line of code that fails, I output the contents of the object that it says is null, and its not null:

AppointmentService.save: appointment=id:1, patient_id:1, date_time:10/29/2010, physician_id:1, lab_id:1, note_id:0

Any suggestions?

user3217883
  • 1,216
  • 4
  • 38
  • 65

3 Answers3

1

This is not hibernate related, you are using spring dependency injection in an incorrect way: you are trying to autowire static fields, which is not allowed.

See Can you use @Autowired with static fields?.

Update

Now you have

AppointmentService appointmentService = new AppointmentService();

instead of injecting this field to the Seeder.

Seeder needs to be a Spring-managed bean (@Service probably) if you want dependency injection to work. See also @PostConstruct, you may use it to call a method after a bean has been initialized.

Update 2

You are still instantiating Spring beans yourself with new, and thus not getting your dependencies injected.

AppointmentSeeder appointmentSeeder = new AppointmentSeeder();

You have however successfully pushed the problem one step towards Flyway. Now the problem is that Flyway migrations are not Spring beans: they are created by Flyway, not Spring, and thus don’t have their dependencies autowired by Spring.

Flyway 4.1 solved this problem by allowing to use a pre-existing Spring beans (or other Java objects) with a bit of config

See API: Make it possible to use pre-instantiated Java-based migrations #1062.

ApplicationContext applicationContext = ...; // obtain a reference to Spring's ApplicationContext.

Flyway flyway = Flyway.configure()
    .dataSource(url, user, password)
    // Add all Spring-instantiated JavaMigration beans
    .javaMigrations(applicationContext.getBeansOfType(JavaMigration.class).values().toArray(new JavaMigration[0]))
    .load();
flyway.migrate();

see also JavaMigration and BaseJavaMigration javadoc.

Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • I didn't have it that way initially. Eclipse offered it as a "quick fix" to the error "Cannot make a static reference to the non-static field repo". So what should I do intead? – user3217883 Apr 14 '20 at 21:19
  • You probably only had the `repo` field as instance field, but the `save` method was static. It is impossible to reach to instance fields from static methods (hence thequickfix). You need to make both field and method non-static – Lesiak Apr 14 '20 at 21:35
  • but the save method is one that gets inherited from public interface AppointmentRepository extends JpaRepository { } JpaRepository is Spring Boot (or Hibernate?) code, not mine. – user3217883 Apr 14 '20 at 21:39
  • Ah, yes of course. Sorry I missed that. Thank you. I took it away and now the next error shows "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appController' defined in file [C:\Users\Greg\Projects\MedAverter\target\classes\net\tekknow\medaverter\controller\AppController.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [net.tekknow.medaverter.controller.AppController] Caused by: java.lang.NoClassDefFoundError: Lnet/tekknow/medaverter/service/AppointmentService;" – user3217883 Apr 14 '20 at 22:02
  • I guess you solved this problem so will give you the credit. Thank you! – user3217883 Apr 14 '20 at 22:09
  • Pls check if the service is discovered. Are you relying on default spring boot app layout (Application in root package) or are you customising it? – Lesiak Apr 14 '20 at 22:12
  • I'm relying on the default. Here is how the app starts up, from MedAverter.java located in src>main>java>net>tekknow>medaverter folder. @SpringBootApplication public class MedAverter { public static void main(String[] args) { SpringApplication.run(MedAverter.class, args); } } – user3217883 Apr 15 '20 at 00:56
  • Unfortunately, despite the adjustments you suggested, I'm back to the original problem, so I unchecked the answer. I updated the original post to include the change s you suggested. – user3217883 Apr 15 '20 at 01:16
  • Annotate the Seeder with `@Service`, add an autowired AppointmentService field (preferably use constructor injection), annotate `seed` with `@PostConstruct` – Lesiak Apr 15 '20 at 14:11
  • I added your suggestions to the main post code and my code, but alas, same error. – user3217883 Apr 15 '20 at 18:05
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/211746/discussion-between-lesiak-and-user3217883). – Lesiak Apr 15 '20 at 18:45
0

maybe you should change the value of ** note_id** in your JSON file to 1 !

Amir Pezeshk
  • 359
  • 1
  • 5
0

do this in the AppointmentSeeder,

@Autowire
AppointmentService service;

public void seed() {
        String json = "[" +
            "{\"id\":1,\"patient_id\":1,\"dateTime\":\"10/29/2010\",\"physician_id\":1,\"lab_id\":1,\"note_id\":0}" +
        "]";
        org.json.JSONArray appointments = new JSONArray(json);

        for (int i=0; i<appointments.length(); i++) {
            JSONObject appointment = appointments.getJSONObject(i);
            Appointment dbAppointment = new Appointment();
            dbAppointment.setId(appointment.getInt("id"));
            dbAppointment.setPatientId(appointment.getInt("patient_id"));
            dbAppointment.setDateTime(appointment.getString("dateTime"));
            dbAppointment.setPhysicianId(appointment.getInt("physician_id"));
            dbAppointment.setLabId(appointment.getInt("lab_id"));
            dbAppointment.setNoteId(appointment.getInt("note_id"));
            service.save(dbAppointment);
        }
    }
Amir Pezeshk
  • 359
  • 1
  • 5
  • Its @Autowired. But that didn't work either. Still getting the same error "Caused by: java.lang.NullPointerException: null at net.tekknow.medaverter.service.AppointmentService.save(AppointmentService.java:32) ~[classes/:na]" – user3217883 Apr 15 '20 at 17:22
  • I modified the code shown in the main post with your suggestions. – user3217883 Apr 15 '20 at 17:32