Используя Java 1.8, Spring Boot, JPA, я создаю микросервис Spring Boot, в котором модель данных (отношение сущностей) соответствует этой конкретной взаимосвязи:
Owner can have many Cars.
Cars only have one Owner.
Этот микросервис Spring Boot имеет следующие функции:
Конечные точки GET HTTP :
- Получение данных о конкретном Владельце (имя, адрес и т. д. c.) из базы данных.
- Извлечение информации о конкретном автомобиле владельца (производитель, модель и т. Д. c.) Из базы данных.
Конечные точки HTTP POST :
- Сохранение данных о владельце в базе данных.
- Сохранение данных об автомобиле владельца в базе данных .
Все это работает, когда я запускаю Spring Boot Microservice и вручную создаю владельцев и их автомобили, а также извлекаю их, используя мои конечные точки метода GET.
Что я пытаюсь сделать теперь нужно заполнить их при загрузке микросервиса Spring Boot (таким образом, я могу начать писать модульные и интеграционные тесты до завершения сборки Maven).
Итак, для этого я создал следующий файл:
@Component
public class DataInserter implements ApplicationListener<ContextRefreshedEvent> {
@Value("classpath:data/owners.json")
Resource ownersResource;
@Value("classpath:data/cars.json")
Resource carsResource;
@Autowired
private OwnerService ownerService;
@Autowired
private CarsService carService;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
List<Owner> populatedOwners = new ArrayList<>();
try {
Owner aOwner;
File ownersFile = ownersResource.getFile();
File carsFile = carsResource.getFile();
String ownersString = new String(Files.readAllBytes(ownersFile.toPath()));
String carsString = new String(Files.readAllBytes(carsFile.toPath()));
ObjectMapper mapper = new ObjectMapper();
List<Owner> owners = Arrays.asList(mapper.readValue(ownersString, Owner[].class));
List<ElectricCars> cars = Arrays.asList(mapper.readValue(carsString, ElectricCars[].class));
// Populate owners one by one
for (Owner owner : owners) {
aOwner = new Owner(owner.getName(), owner.getAddress(), owner.getCity(), owner.getState(), owner.getZipCode());
ownerService.createOwner(aOwner);
populatedOwners.add(aOwner);
}
// Populate owner cars one by one
for (int i = 0; i < populatedOwners.size(); i++) {
carService.createCars(populatedOwners.get(i).getId(), cars.get(i));
}
// Provide some owners with multiple cars
// carService.createCars(populatedOwners.get(0).getId(), cars.get(3));
// carService.createCars(populatedOwners.get(0).getId(), cars.get(4));
// carService.createCars(populatedOwners.get(1).getId(), cars.get(3));
}
catch(IOException ioe) {
ioe.printStackTrace();;
}
}
}
src / main / resources / data / cars. json:
[
{
"make": "Honda",
"model": "Accord",
"year": "2020"
},
{
"make": "Nissan",
"model": "Maxima",
"year": "2019"
},
{
"make": "Toyota",
"model": "Prius",
"year": "2015"
},
{
"make": "Porsche",
"model": "911",
"year": "2017"
},
{
"make": "Hyundai",
"model": "Elantra",
"year": "2018"
},
{
"make": "Volkswagen",
"model": "Beatle",
"year": "1973"
},
{
"make": "Ford",
"model": "F-150",
"year": "2010"
},
{
"make": "Chevrolet",
"model": "Silverado",
"year": "2020"
},
{
"make": "Toyota",
"model": "Camary",
"year": "2018"
},
{
"make": "Alfa",
"model": "Romeo",
"year": "2017"
}
]
src / main / resources / data / owners. json:
[
{
"name": "Tom Brady"
},
{
"name": "Kobe Bryant"
},
{
"name": "Mike Tyson"
},
{
"name": "Scottie Pippen"
},
{
"name": "John Madden"
},
{
"name": "Arnold Palmer"
},
{
"name": "Tiger Woods"
},
{
"name": "Magic Johnson"
},
{
"name": "George Foreman"
},
{
"name": "Charles Barkley"
}
]
Итак, когда я запускаю это со следующими закомментированными строками:
// Populate owner cars one by one
for (int i = 0; i < populatedOwners.size(); i++) {
carService.createCars(populatedOwners.get(i).getId(), cars.get(i));
}
// Provide some owners with multiple cars
// carService.createCars(populatedOwners.get(0).getId(), cars.get(3));
// carService.createCars(populatedOwners.get(0).getId(), cars.get(4));
// carService.createCars(populatedOwners.get(1).getId(), cars.get(3));
И затем я вызываю мой Get All Owners REST Конечная точка (см. Ниже):
GET http://localhost:8080/car-api/owners
JSON Полезная нагрузка получается правильно (каждый отдельный владелец есть один автомобиль):
[
{
"id": 1,
"name": "Tom Brady",
"cars": [
{
"id": 1,
"make": "Honda",
"model": "Accord",
"year": "2020"
}
]
},
{
"id": 2,
"name": "Kobe Bryant",
"cars": [
{
"id": 2,
"make": "Nissan",
"model": "Maxima",
"year": "2019"
}
]
},
{
"id": 3,
"name": "Mike Tyson",
"cars": [
{
"id": 3,
"make": "Toyota",
"model": "Prius",
"year": "2015"
}
]
},
{
"id": 4,
"name": "Scottie Pippen",
"cars": [
{
"id": 4,
"make": "Porsche",
"model": "911",
"year": "2017"
}
]
},
{
"id": 5,
"name": "John Madden",
"cars": [
{
"id": 5,
"make": "Hyundai",
"model": "Elantra",
"year": "2018"
}
]
},
{
"id": 6,
"name": "Arnold Palmer",
"cars": [
{
"id": 6,
"make": "Volkswagen",
"model": "Beatle",
"year": "1973"
}
]
},
{
"id": 7,
"name": "Tiger Woods",
"cars": [
{
"id": 7,
"make": "Ford",
"model": "F-150",
"year": "2010"
}
]
},
{
"id": 8,
"name": "Magic Johnson",
"cars": [
{
"id": 8,
"make": "Chevrolet",
"model": "Silverado",
"year": "2020"
}
]
},
{
"id": 9,
"name": "George Foreman",
"cars": [
{
"id": 9,
"make": "Toyota",
"model": "Camary",
"year": "2018"
}
]
},
{
"id": 10,
"name": "Charles Barkley",
"cars": [
{
"id": 10,
"make": "Alfa",
"model": "Romeo",
"year": "2017"
}
]
}
]
Однако, когда я пытаюсь назначить больше автомобилей отдельным владельцам (кажется, это приводит к тому, что массив других владельцев JSON становится пустым):
// Populate owner cars one by one
for (int i = 0; i < populatedOwners.size(); i++) {
carService.createCars(populatedOwners.get(i).getId(), cars.get(i));
}
// Provide some owners with multiple cars
carService.createCars(populatedOwners.get(0).getId(), cars.get(3));
carService.createCars(populatedOwners.get(0).getId(), cars.get(4));
carService.createCars(populatedOwners.get(1).getId(), cars.get(3));
JSON Полезная нагрузка дает следующее:
[
{
"id": 1,
"name": "Tom Brady",
"cars": [
{
"id": 1,
"make": "Honda",
"model": "Accord",
"year": "2020"
},
{
"id": 5,
"make": "Hyundai",
"model": "Elantra",
"year": "2018"
}
]
},
{
"id": 2,
"name": "Kobe Bryant",
"cars": [
{
"id": 2,
"make": "Nissan",
"model": "Maxima",
"year": "2019"
},
{
{
"id": 4,
"make": "Porsche",
"model": "911",
"year": "2017"
}
]
},
{
"id": 3,
"name": "Mike Tyson",
"cars": [
{
"id": 3,
"make": "Toyota",
"model": "Prius",
"year": "2015"
}
]
},
{
"id": 4,
"name": "Scottie Pippen",
"cars": []
},
{
"id": 5,
"name": "John Madden",
"cars": []
},
{
"id": 6,
"name": "Arnold Palmer",
"cars": [
{
"id": 6,
"make": "Volkswagen",
"model": "Beatle",
"year": "1973"
}
]
},
{
"id": 7,
"name": "Tiger Woods",
"cars": [
{
"id": 7,
"make": "Ford",
"model": "F-150",
"year": "2010"
}
]
},
{
"id": 8,
"name": "Magic Johnson",
"cars": [
{
"id": 8,
"make": "Chevrolet",
"model": "Silverado",
"year": "2020"
}
]
},
{
"id": 9,
"name": "George Foreman",
"cars": [
{
"id": 9,
"make": "Toyota",
"model": "Camary",
"year": "2018"
}
]
},
{
"id": 10,
"name": "Charles Barkley",
"cars": [
{
"id": 10,
"make": "Alfa",
"model": "Romeo",
"year": "2017"
}
]
}
]
Как вы можете видеть, эти машины были добавлены в массив JSON Тома Брэйди и Коби Брайанта автомобили, но удаленные от людей, у которых они были (у Скотта ie Пиппена и Джона Мэддена теперь есть пустые JSON массивы автомобилей) ...
Почему это происходит, это возможная ошибка с моим CarServiceImpl.createCar()
method?
pom. xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.myapi</groupId>
<artifactId>car-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>car-api</name>
<description>Car REST API</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
src / main / resources / application.properties:
server.servlet.context-path=/car-api
server.port=8080
server.error.whitelabel.enabled=false
# Database specific
spring.jpa.hibernate.ddl-auto=create
spring.datasource.url=jdbc:mysql://localhost:3306/car_db?useSSL=false
spring.datasource.ownername=root
spring.datasource.password=
Владелец:
@Entity
@Table(name = "owner")
public class Owner {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
private String name;
@OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "owner")
private List<Car> cars = new ArrayList<>();
public Owner() {
}
// Getter & Setters omitted for brevity.
}
Автомобиль:
@Entity
@Table(name="car")
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
String make;
String model;
String year;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "owner_id", nullable = false)
private Owner owner;
// Getter & Setters omitted for brevity.
}
OwnerRepository:
@Repository
public interface OwnerRepository extends JpaRepository<Owner, Long> {
}
CarRepository:
@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
}
OwnerService:
public interface OwnerService {
boolean createOwner(Owner owner);
Owner getOwnerByOwnerId(Long ownerId);
List<Owner> getAllOwners();
}
OwnerServiceImpl:
@Service
public class OwnerServiceImpl implements OwnerService {
@Autowired
OwnerRepository ownerRepository;
@Autowired
CarRepository carRepository;
@Override
public List<Owner> getAllOwners() {
return ownerRepository.findAll();
}
@Override
public boolean createOwner(Owner owner) {
boolean created = false;
if (owner != null) {
ownerRepository.save(owner);
created = true;
}
return created;
}
@Override
public Owner getOwnerByOwnerId(Long ownerId) {
Optional<Owner> owner = null;
if (ownerRepository.existsById(ownerId)) {
owner = ownerRepository.findById(ownerId);
}
return owner.get();
}
}
CarService:
public interface CarService {
boolean createCar(Long ownerId, Car car);
}
CarServiceImpl:
@Service
public class CarServiceImpl implements CarService {
@Autowired
OwnerRepository ownerRepository;
@Autowired
CarRepository carRepository;
@Override
public boolean createCar(Long ownerId, Car car) {
boolean created = false;
if (ownerRepository.existsById(ownerId)) {
Optional<Owner> owner = ownerRepository.findById(ownerId);
if (owner != null) {
List<Car> cars = owner.get().getCars();
cars.add(car);
owner.get().setCars(cars);
car.setOwner(owner.get());
carRepository.save(car);
created = true;
}
}
return created;
}
}
OwnerController:
@RestController
public class OwnerController {
private HttpHeaders headers = null;
@Autowired
OwnerService ownerService;
public OwnerController() {
headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
}
@RequestMapping(value = { "/owners" }, method = RequestMethod.POST, produces = "APPLICATION/JSON")
public ResponseEntity<Object> createOwner(@Valid @RequestBody Owner owner) {
boolean isCreated = ownerService.createOwner(owner);
if (isCreated) {
return new ResponseEntity<Object>(headers, HttpStatus.OK);
}
else {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
}
@RequestMapping(value = { "/owners" }, method = RequestMethod.GET, produces = "APPLICATION/JSON")
public ResponseEntity<Object> getAllOwners() {
List<Owner> owners = ownerService.getAllOwners();
if (owners.isEmpty()) {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Object>(owners, headers, HttpStatus.OK);
}
@RequestMapping(value = { "/owners/{ownerId}" }, method = RequestMethod.GET, produces = "APPLICATION/JSON")
public ResponseEntity<Object> getOwnerByOwnerId(@PathVariable Long ownerId) {
if (null == ownerId || "".equals(ownerId)) {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
Owner owner = ownerService.getOwnerByOwnerId(ownerId);
return new ResponseEntity<Object>(owner, headers, HttpStatus.OK);
}
}
CarController:
@RestController
public class CarController {
private HttpHeaders headers = null;
@Autowired
CarService carService;
public CarController() {
headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
}
@RequestMapping(value = { "/cars/{ownerId}" }, method = RequestMethod.POST, produces = "APPLICATION/JSON")
public ResponseEntity<Object> createCarBasedOnOwnerId(@Valid @RequestBody Car car, Long ownerId) {
boolean isCreated = carService.createCar(ownerId, car);
if (isCreated) {
return new ResponseEntity<Object>(headers, HttpStatus.OK);
}
else {
return new ResponseEntity<Object>(HttpStatus.NOT_FOUND);
}
}
Вопрос (ы):
Почему, добавляя новые автомобили в ArrayList автомобиля Владельца, он удаляет другие автомобили Владельца (которые имеют тот же car.id)?
Заметил, как внутри Владельца. java, у меня было сделать FetchType.EAGER
:
@OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "owner")
private List<Car> cars = new ArrayList<>();
Когда я получил его как fetch = FetchType.LAZY
, он выдал следующее исключение:
2020-03-08 15:18:13,175 ERROR org.springframework.boot.SpringApplication [main] Application run failed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myapi.model.User.cars, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.write(AbstractPersistentCollection.java:409)
at org.hibernate.collection.internal.PersistentBag.add(PersistentBag.java:407)
at org.hibernate.collection.internal.PersistentBag.add(PersistentBag.java:407)
at com.myapi.service.CarServiceImpl.createCar(CarServiceImpl.java:36)
at com.myapi.bootstrap.DataInserter.onApplicationEvent(DataInserter.java:71)
at com.myapi.bootstrap.DataInserter.onApplicationEvent(DataInserter.java:24)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:403)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:360)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:897)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:162)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:553)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
at com.myapi.CarApplication.main(CarApplication.java:12)
Это связано или это отдельная проблема? Я немного новичок в JPA, поэтому мне интересно, нужно ли мне изменить значения cascade = CascadeType.ALL
в обеих сущностях на что-то еще.
Есть ли лучший способ заполнить базу данных фиктивными данными (возможно, в модульных или интеграционных тестах, а не при загрузке ApplicationContext) для целей тестирования?