【发布时间】: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
【问题讨论】:
-
请修剪您的代码,以便更容易找到您的问题。请按照以下指南创建minimal reproducible example。
标签: java spring spring-boot jpa