Java 向其他实体添加相同的项时,Spring JPA实体正在丢失数据

Java 向其他实体添加相同的项时,Spring JPA实体正在丢失数据,java,spring,database,spring-data,jpql,Java,Spring,Database,Spring Data,Jpql,使用Java 1.8、Spring Boot、JPA,我创建了一个Spring Boot微服务, 其中数据模型(实体关系)遵循此特定的一对多关系: Owner can have many Cars. Cars only have one Owner. 此Spring Boot Microservice具有以下功能: HTTP获取端点: 从数据库中获取特定所有者的数据(姓名、地址等) 从数据库中检索特定车主的汽车信息(品牌、型号等) HTTP POST端点: 将有关所有者的数据持久化到数据

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

Owner can have many Cars.
Cars only have one Owner.
此Spring Boot Microservice具有以下功能:

HTTP获取端点

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

  • 将有关所有者的数据持久化到数据库中
  • 将有关车主汽车的数据持久保存到数据库中
当我运行springbootmicroservice并手动创建车主和他们的汽车,以及使用GET方法端点检索车主和汽车时,这些都可以工作

我现在要做的是在SpringBootMicroService加载时填充这些测试(这样,我可以在Maven构建完成之前开始编写单元测试和集成测试)

为此,我创建了以下文件:



src/main/resources/data/owner.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));

但是,当我尝试将更多汽车分配给个人车主时(这似乎会导致其他车主的汽车JSON数组变为空):

如你所见,这些汽车似乎被添加到了汤姆·布雷迪和科贝·布莱恩特的JSON汽车数组中,但从拥有它们的人那里删除了(斯科蒂·皮蓬和约翰·马登现在拥有空的JSON汽车数组)

为什么会发生这种情况,这是我的
carserviceinpl.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>

业主单位:

@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.
}

业主地址:

@Repository
public interface OwnerRepository extends JpaRepository<Owner, Long> {
}
@Repository
public interface CarRepository extends JpaRepository<Car, Long> {
}
@Query("select o from Owner o where o.id = :id left join fetch o.cars")
findOrderWithCars(@Param("id") Long ownerId)

CarServiceImpl:

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

}


当我把它设为
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加载上)填充数据库以进行测试
  • 为什么在车主汽车列表中添加新车会删除其他车主的汽车(具有相同的car.id)
  • 因为你已经把它编好了。这就是你如何定义汽车和车主之间的关系:

    汽车只有一个主人

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

    2。LazyInitializationException

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

    @Repository
    public interface OwnerRepository extends JpaRepository<Owner, Long> {
    }
    
    @Repository
    public interface CarRepository extends JpaRepository<Car, Long> {
    }
    
    @Query("select o from Owner o where o.id = :id left join fetch o.cars")
    findOrderWithCars(@Param("id") Long ownerId)
    
  • 是否有更好的方法用模拟数据(可能在单元或集成测试中,而不是在ApplicationContext加载上)填充数据库以进行测试
  • 对。例如,这种解决方案可以使用。您只需要创建sql脚本,用数据填充数据库并配置数据源。您不必编写那么多代码、映射json对象等


    旁注:此代码是一个杀手:

    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;
        }
    }
    
    if(ownerRepository.existsById(ownerId)){
    可选owner=ownerRepository.findById(ownerId);
    如果(所有者!=null){
    List cars=owner.get().getCars();
    cars.add(car);
    owner.get().setCars(cars);
    car.setOwner(owner.get());
    carRepository.save(car);
    创建=真;
    }
    }
    

    首先,检查数据库中是否存在该实体,如果存在,则向数据库发射另一枪以获取该实体。它可以在一次往返中完成。另一个问题是,您正在检查
    可选
    是否为
    <代码>可选应永远不为
    。您可能想编写
    owner.isPresent()

    数据插入器的问题在于,它使用相同的汽车对象将其重新分配给不同的车主。如果我们想将多辆车分配给车主,我们需要比车主拥有更多的车对象。我们需要克隆汽车对象以创建具有相同属性的不同汽车对象

    我们可以在Car类中编写copy构造函数,它接受现有的Car并返回具有相同属性的Car的副本或克隆。因此,我们可以使用现有的汽车对象创建新的汽车对象。例如,如下所示

         Car newCarObject  = new Car(existingCarObject);
    
    并修改以下代码以使用现有汽车对象创建新汽车对象

      // Provide some owners with multiple cars
      // carService.createCars(populatedOwners.get(0).getId(), new Car(cars.get(3)));
      // carService.createCars(populatedOwners.get(0).getId(), new Car(cars.get(4)));
      // carService.createCars(populatedOwners.get(1).getId(), new Car(cars.get(3)));
    

    对于问题3,集成测试和模拟数据,您应该看看project,特别是MySql模块,您是否可以发布您的数据库模型?如何定义汽车和车主之间的关系?
    @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;
        }
    
    }
    
    
    @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);
        }
    
    }
    
    @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);
            }
        }
    
    
    @OneToMany(cascade = CascadeType.ALL,
               fetch = FetchType.EAGER,
               mappedBy = "owner")
    private List<Car> cars = new ArrayList<>();
    
    
    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)
    
    
    @Query("select o from Owner o where o.id = :id left join fetch o.cars")
    findOrderWithCars(@Param("id") Long ownerId)
    
    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;
        }
    }
    
         Car newCarObject  = new Car(existingCarObject);
    
      // Provide some owners with multiple cars
      // carService.createCars(populatedOwners.get(0).getId(), new Car(cars.get(3)));
      // carService.createCars(populatedOwners.get(0).getId(), new Car(cars.get(4)));
      // carService.createCars(populatedOwners.get(1).getId(), new Car(cars.get(3)));