【问题标题】:Java Spring Null Pointer Exception when saving ManyToOne保存ManyToOne时Java Spring Null指针异常
【发布时间】:2022-01-23 10:58:25
【问题描述】:

试图保存与“Role”、“City”、“SalarySite”具有多对一关系的“Salary”类型的对象。它似乎使用所有预期数据正确打印,但保存失败。其他类似的问题似乎都缺少“@Autowired”,但它存在于我的。

“Dataloader”类在我们看到错误的地方运行“GoogleSearchScraper”类。

城市

package fyi.incomeoutcome.salarytaxspend.city;
import fyi.incomeoutcome.salarytaxspend.salary.Salary;

import javax.persistence.*;
import java.util.Set;

@Entity
public class City {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String name;
    private String country;
    private String currency;

    @OneToMany(mappedBy="city", cascade = CascadeType.ALL)
    private Set<Salary> salaries;

    protected City(){}

    public City(String name, String country, String currency){
        this.name = name;
        this.country = country;
        this.currency = currency;
    }

    public String getName(){ return this.name; }

    public String getCountry(){ return this.country; }

    public String toString(){
        return String.format("City[id=%d, name='%s', country = '%s', currency= '%s']",
                id, name, country, currency);
    }

}

城市资料库

package fyi.incomeoutcome.salarytaxspend.city;

import org.springframework.stereotype.Repository;
import org.springframework.data.repository.CrudRepository;

@Repository
public interface CityRepository extends CrudRepository<City, Long> {

}

角色

package fyi.incomeoutcome.salarytaxspend.role;

import fyi.incomeoutcome.salarytaxspend.salary.Salary;

import javax.persistence.*;
import java.util.Set;

@Entity
public class Role {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String seniority;
    private String roleName;
    @OneToMany(mappedBy="role", cascade = CascadeType.ALL)
    private Set<Salary> salaries;



    protected Role(){}

    public Role(String seniority,String roleName){
        this.seniority = seniority;
        this.roleName = roleName;
    }

    public String getSeniorityAndRole(){
        if (this.seniority != ""){
            return String.format("%s %s", this.seniority, this.roleName);
        } else{
            return roleName;
        }
    }

    public String toString(){
        return String.format("id=%d, %s %s", id, seniority, roleName);
    }


}

角色存储库

package fyi.incomeoutcome.salarytaxspend.role;

import org.springframework.stereotype.Repository;
import org.springframework.data.repository.CrudRepository;

@Repository
public interface RoleRepository extends CrudRepository<Role, Long> {

}

薪资网站

package fyi.incomeoutcome.salarytaxspend.salary.salarysite;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class SalarySite {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private long id;
    private String name;
    private String searchUrl;
    private String salaryElementClass;

    protected SalarySite(){}

    public SalarySite(String name, String searchUrl, String salaryElementClass){
        this.name = name;
        this.searchUrl = searchUrl;
        this.salaryElementClass = salaryElementClass;
    }

    public String getName() {
        return name;
    }

    public String toString(){
        return String.format("%s %s %s", name, searchUrl, salaryElementClass);
    }

    public String getSearchUrl(){
        return searchUrl;
    }

    public String getSalaryElementClass(){
        return salaryElementClass;
    }
}

SalarySiteRepo

package fyi.incomeoutcome.salarytaxspend.salary.salarysite;

import org.springframework.stereotype.Repository;
import org.springframework.data.repository.CrudRepository;

@Repository
public interface SalarySiteRepository extends CrudRepository<SalarySite, Long> {

}

薪水

package fyi.incomeoutcome.salarytaxspend.salary;

import fyi.incomeoutcome.salarytaxspend.city.City;
import fyi.incomeoutcome.salarytaxspend.role.Role;
import fyi.incomeoutcome.salarytaxspend.salary.salarysite.SalarySite;

import javax.persistence.*;

@Entity
public class Salary {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id;
    private int compensation;
    @ManyToOne(optional = false)
    @JoinColumn(name = "role_id")
    private Role role;
    @ManyToOne(optional = false)
    @JoinColumn(name = "city_id")
    private City city;
    @ManyToOne(optional = false)
    @JoinColumn(name = "site_id")
    private SalarySite site;

    protected Salary(){}

    public Salary(int compensation, Role role, City city,  SalarySite site){
        this.compensation = compensation;
        this.role = role;
        this.city = city;
        this.site = site;
    }

    public int getCompensation(){
        return this.compensation;
    }

    public String getCity(){
        return city.getName();
    }

    public String getSeniorityAndRoleName(){
        return role.getSeniorityAndRole();
    }

    public String toString(){
        return String.format("Salary[id=%d, compensation=%d, city='%s', role='%s'",
                        id, compensation, city, role);
    }

}

工资库


import fyi.incomeoutcome.salarytaxspend.city.City;
import fyi.incomeoutcome.salarytaxspend.role.Role;
import fyi.incomeoutcome.salarytaxspend.salary.salarysite.SalarySite;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SalaryRepository extends CrudRepository<Salary, Long> {
    //List<Salary> findByLocation(String location);

    Salary findById(long id);

    boolean existsBySiteAndCityAndRole(SalarySite salarySite, City city, Role role);
}

数据加载器

package fyi.incomeoutcome.salarytaxspend.salary.service;

import fyi.incomeoutcome.salarytaxspend.city.City;
import fyi.incomeoutcome.salarytaxspend.city.CityRepository;
import fyi.incomeoutcome.salarytaxspend.role.RoleRepository;
import fyi.incomeoutcome.salarytaxspend.salary.Salary;
import fyi.incomeoutcome.salarytaxspend.salary.SalaryRepository;
import fyi.incomeoutcome.salarytaxspend.salary.salarysite.SalarySite;
import fyi.incomeoutcome.salarytaxspend.role.Role;
import fyi.incomeoutcome.salarytaxspend.salary.salarysite.SalarySiteRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;




@Component
@Slf4j
public class DataLoader {

    @Autowired
    private SalaryRepository salaryRepository;
    @Autowired
    private SalarySiteRepository siteRepository;
    @Autowired
    private CityRepository cityRepository;
    @Autowired
    private RoleRepository roleRepository;



    @PostConstruct
    public void process() {
        List<SalarySite> salarySiteList = new ArrayList<>();
        siteRepository.findAll().forEach(salarySiteList::add);

        List<Role> roleList = new ArrayList<>();
        roleRepository.findAll().forEach(roleList::add);

        List<City> cityList = new ArrayList();
        cityRepository.findAll().forEach(cityList::add);

        List<Salary> salaryList = new ArrayList<>();
        salaryRepository.findAll().forEach(salaryList::add);



        log.info("Entering Loop");
        log.info("--------------------");
        int numberOfSalaries = salaryList.size();
        int numberOfRoles = roleList.size();
        int numberOfSites = salarySiteList.size();
        int numberOfCities = cityList.size();

        log.info(String.format("salary list %d", numberOfSalaries));
        log.info(String.format("role list %d", numberOfRoles));
        log.info(String.format("site list %d", numberOfSites));
        log.info(String.format("city list %d", numberOfCities));

        for (SalarySite salarySite : salarySiteList){
           for (City city: cityList){
                for (Role role: roleList){
                    boolean foundSalary = salaryRepository.existsBySiteAndCityAndRole(salarySite, city, role);
                    if (foundSalary == true) {
                        log.debug("Found");
                    } else {
                        log.debug("Not Found, Scraping");
                        String roleTitle = role.getSeniorityAndRole();
                        String cityName = city.getName();
                        String searchUrl = salarySite.getSearchUrl();
                        String salaryElementClass = salarySite.getSalaryElementClass();
                        ScraperService siteScraper = new GoogleSearchScraper(role, city, salarySite);
                        siteScraper.executeScrape();
                    }
                }
           }
        }
    }
}

Google 搜索抓取工具

package fyi.incomeoutcome.salarytaxspend.salary.service;

import fyi.incomeoutcome.salarytaxspend.city.City;
import fyi.incomeoutcome.salarytaxspend.role.Role;
import fyi.incomeoutcome.salarytaxspend.salary.Salary;
import fyi.incomeoutcome.salarytaxspend.salary.SalaryRepository;
import fyi.incomeoutcome.salarytaxspend.salary.salarysite.SalarySite;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

@Component
@Slf4j
public class GoogleSearchScraper implements ScraperService {
    private Role role;
    private City city;
    private SalarySite site;

    private String webPageUrl;
    private int compensation;

    @Autowired
    SalaryRepository salaryRepo;

    public GoogleSearchScraper(){}

    public GoogleSearchScraper(Role role, City city, SalarySite site){
        this.role = role;
        this.city = city;
        this.site = site;
    }

    @Override
    public void executeScrape(){
        int statusCodeFetchUrl = fetchWebpageUrl();
        log.info(String.format("fetchWebPageUrl for %s : %d", toString(), statusCodeFetchUrl));
        int parsePageStatusCode = parsePageForSalary();
        log.info(String.format("parseUrl for salary %s : %d", toString(), parsePageStatusCode));
        saveSalary();
    }

    @Override
    public int fetchWebpageUrl(){
        String title = role.getSeniorityAndRole();
        title = title.replace(" ", "+");
        String searchUrl = site.getSearchUrl();
        String cityName = city.getName();;
        String fullUrl = String.format("%s%s+%s", searchUrl, title, cityName);
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(fullUrl))
                .GET()
                .build();
        int statusCode = 0;
        try{
            HttpResponse<String> response = client.send(request,
                    HttpResponse.BodyHandlers.ofString());
            statusCode = response.statusCode();
            String jsonResultLink = new JSONObject(response.body())
                    .getJSONArray("items")
                    .getJSONObject(0)
                    .getString("link");
            log.info(city + " " + role + " " + jsonResultLink);
            this.webPageUrl = jsonResultLink;

            return statusCode;
        } catch(Exception e) {
            log.error(e.toString());
            return statusCode;
        }
    }

    @Override
    public int parsePageForSalary() {
        HttpClient client = HttpClient.newBuilder()
                .followRedirects(HttpClient.Redirect.ALWAYS)
                .build();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(this.webPageUrl))
                .GET()
                .build();
        int statusCode = 0;
        try{
            HttpResponse<String> response = client.send(request,
                    HttpResponse.BodyHandlers.ofString());
            statusCode = response.statusCode();
            Document doc = Jsoup.parse(response.body());
            Element salaryElement = doc.getElementsByClass(site.getSalaryElementClass()).first();
            String salaryText = salaryElement.text();
            char[] salaryChars = salaryText.toCharArray();
            String nonDigitCharacters = "";
            for (char c: salaryChars){
                if (!Character.isDigit(c)){
                    salaryText = salaryText.replace(Character.toString(c),"");
                    nonDigitCharacters += c;
                }
            }
            log.info(this.toString() + " removed " + nonDigitCharacters + " from salary " + salaryText);
            this.compensation = Integer.parseInt(salaryText);
            return statusCode;
        } catch (Exception e){
            log.error(e.toString());
            return statusCode;
        }
    }

    public void saveSalary(){
        Salary scrapedSalary = new Salary(this.compensation, this.role, this.city, this.site);
        log.info("saving " + scrapedSalary.toString());
        salaryRepo.save(scrapedSalary);
    }

    @Override
    public String getWebPageUrl() {
        return webPageUrl;
    }

    @Override
    public int getSalary() {
        if (this.compensation == 0){
            parsePageForSalary();
            return this.compensation;
        }
        return this.compensation;
    }

    public String toString(){
        return String.format("Scraper %s, %s, %s", role.getSeniorityAndRole(),
                city.getName(), site.getName());
    }
}

错误

2022-01-23 10:53:54.194  INFO 34291 --- [           main] f.i.s.s.service.GoogleSearchScraper      : saving Salary[id=0, compensation=36149, city='City[id=1, name='Dublin', country = 'Ireland', currency= 'EUR']', role='id=1, Junior Software Developer'
2022-01-23 10:53:54.194  WARN 34291 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataLoader': Invocation of init method failed; nested exception is java.lang.NullPointerException
2022-01-23 10:53:54.195  INFO 34291 --- [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-01-23 10:53:54.196  INFO 34291 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-01-23 10:53:54.210  INFO 34291 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2022-01-23 10:53:54.211  INFO 34291 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2022-01-23 10:53:54.222  INFO 34291 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-01-23 10:53:54.237 ERROR 34291 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataLoader': Invocation of init method failed; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:440) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:953) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.14.jar:5.3.14]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.14.jar:5.3.14]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:730) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:412) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:302) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) ~[spring-boot-2.6.2.jar:2.6.2]
    at fyi.incomeoutcome.salarytaxspend.SalaryTaxSpendApplication.main(SalaryTaxSpendApplication.java:27) ~[classes/:na]
Caused by: java.lang.NullPointerException: null
    at fyi.incomeoutcome.salarytaxspend.salary.service.GoogleSearchScraper.saveSalary(GoogleSearchScraper.java:119) ~[classes/:na]
    at fyi.incomeoutcome.salarytaxspend.salary.service.GoogleSearchScraper.executeScrape(GoogleSearchScraper.java:48) ~[classes/:na]
    at fyi.incomeoutcome.salarytaxspend.salary.service.DataLoader.process(DataLoader.java:78) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.3.14.jar:5.3.14]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.3.14.jar:5.3.14]
    ... 18 common frames omitted

【问题讨论】:

标签: java spring spring-boot jpa


【解决方案1】:

由于您自己创建了 GoogleSearchScraper 的实例,因此 Spring 不会自动装配字段。因此,salaryRepository 为空。 您可以在创建 GoogleSearchScraper 时自己注入 SalaryRepository 实例,如下所示:

ScraperService siteScraper = new GoogleSearchScraper(role, city, salarySite, salaryRepo);
siteScraper.executeScrape();

当然你需要将 SalaryRepository 添加到构造函数参数列表并分配它。 如果您想自己创建实例,请考虑从 GoogleSearchScraper 中删除 @Component。只有当实例由 Spring 管理时才需要它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-01
    • 2021-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-11
    相关资源
    最近更新 更多