gpt4 book ai didi

java - 将相同项目添加到其他实体时,Spring JPA 实体会丢失数据

转载 作者:行者123 更新时间:2023-12-03 02:15:59 25 4
gpt4 key购买 nike

使用 Java 1.8、Spring Boot、JPA,我创建了一个 Spring Boot 微服务,其中数据模型(实体关系)遵循这种特定的一对多关系:

Owner can have many Cars.
Cars only have one Owner.

此 Spring Boot 微服务具有以下功能:

HTTP GET 端点:

  • 从数据库获取有关特定所有者的数据(姓名、地址等)。
  • 从数据库中检索有关特定车主汽车的信息(品牌、型号等)。

HTTP POST 端点:

  • 将所有者的数据保存到数据库中。
  • 将车主汽车的数据保存到数据库中。

当我运行 Spring Boot 微服务并手动创建所有者及其汽车并使用我的 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 端点(见下文):

获取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 数组中,但从拥有它们的人中删除(斯科蒂·皮蓬和约翰·马登现在拥有空的汽车 JSON 数组)...

为什么会发生这种情况,这是否是我的 CarServiceImpl.createCar() 方法可能存在的错误?

<小时/>

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/applications.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.
}
<小时/>

所有者存储库:

@Repository
public interface OwnerRepository extends JpaRepository<Owner, Long> {
}
<小时/>

汽车存储库:

@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
}
<小时/>

所有者服务:

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();
}
}
<小时/>

汽车服务:

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;
}

}

<小时/>

所有者 Controller :

@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);
}

}
<小时/>

汽车 Controller :

@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);
}
}

<小时/>

问题:

  1. 为什么通过将新车添加到车主的汽车 ArrayList 中,会删除其他车主的汽车(具有相同的 car.id)?

  2. 注意到在 Owner.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 加载时)以进行测试?
  • 最佳答案

    1. Why by adding new cars to an Owner's car ArrayList, it removes other Owner's cars (which have the same car.id)?

    因为你已经这样编程了。这就是您定义汽车和车主之间关系的方式:

    Cars only have one Owner.

    那么如何让汽车拥有多个车主呢?如果您想多次拥有同一辆车,则必须使用相同的数据(id 除外)创建一个新实体。

    <强>2。 LazyInitializationException

    hibernate 中的 X-to-many 关系(一对多、多对多)始终是延迟获取的。这意味着,当您获取具有一对多关系的实体时,出于性能原因,不会获取集合。如果您尝试迭代它,则会抛出LazyInitializationException。使用 FetchType.EAGER 进行注释是一种解决方案,但不是一个好的解决方案,因为无论是否需要,始终都会获取集合。更好的方法是在存储库中使用 jpql:

    @Query("select o from Owner o where o.id = :id left join fetch o.cars")
    findOrderWithCars(@Param("id") Long ownerId)
    1. Is there a better way to populate the database with mock data (perhaps in the unit or integration tests rather than on ApplicationContext load up) for testing purposes?

    是的。例如,这样的解决方案可以使用 flyway 。您只需要创建 sql 脚本,用数据填充数据库并配置数据源。您不必编写那么多代码、映射 json 对象等。

    <小时/>

    旁注:这段代码是一个 killer :

    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;
    }
    }

    首先,您要检查该实体是否存在于数据库中,如果存在,您将向数据库发起另一次攻击以获取它。它可以在单次往返中完成。另一个是您要检查 Optional 是否为 null可选应该永远不会null。您可能想编写owner.isPresent()

    关于java - 将相同项目添加到其他实体时,Spring JPA 实体会丢失数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60593232/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com