Объекты Spring JPA теряют данные при добавлении одинаковых элементов к другим объектам - PullRequest
3 голосов
/ 09 марта 2020

Используя 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);
        }
    }


Вопрос (ы):

  1. Почему, добавляя новые автомобили в ArrayList автомобиля Владельца, он удаляет другие автомобили Владельца (которые имеют тот же car.id)?

  2. Заметил, как внутри Владельца. 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) для целей тестирования?

Ответы [ 2 ]

4 голосов
/ 11 марта 2020
  1. Почему, добавляя новые автомобили в ArrayList автомобиля Владельца, он удаляет автомобили другого Владельца (которые имеют тот же car.id)?

Потому что вы ' мы запрограммировали это так. Вот как вы определили отношения между автомобилем и владельцем:

У автомобиля есть только один владелец.

Итак, как вы хотите, чтобы у машины было несколько владельцев? Если вы хотите иметь один и тот же автомобиль несколько раз, вам придется создать новый объект с одинаковыми данными (кроме идентификатора).

2. LazyInitializationException

Отношение X-ко-многим в спящем режиме (один-ко-многим, многие-ко-многим) всегда выбирается лениво. Это означает, что при извлечении сущности, имеющей отношение ко многим, по соображениям производительности коллекция не извлекается. Если вы попытаетесь перебрать его, выдается LazyInitializationException. Аннотирование с помощью FetchType.EAGER является решением, но не очень хорошим, так как коллекция всегда будет извлекаться независимо от того, нужна ли она. Лучше было бы использовать, например, jpql в вашем хранилище:

@Query("select o from Owner o where o.id = :id left join fetch o.cars")
findOrderWithCars(@Param("id") Long ownerId)
Есть ли лучший способ заполнить базу данных фиктивными данными (возможно, в модульных или интеграционных тестах, а не при загрузке ApplicationContext) для целей тестирования?

Да. Такое решение будет, например, использовать пролет . Вам нужно только создать сценарии sql, которые заполняют базу данных данными и конфигурируют источник данных. Вам не нужно будет писать столько кода, карты json объектов и c.


Примечание: этот код является убийцей:

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 1038 *. Optional должно никогда не быть null. Вы, вероятно, хотели написать owner.isPresent().

1 голос
/ 12 марта 2020

Проблема с DataInserter заключается в том, что он использует одни и те же автомобильные объекты для переназначения его другим владельцам. Если мы хотим назначить несколько автомобилей владельцам, нам нужно иметь больше автомобильных объектов, чем владельцев. Нам нужно клонировать автомобильные объекты, чтобы создать разные автомобильные объекты с одинаковыми свойствами.

Мы можем написать конструктор копирования в классе автомобилей, который берет существующий автомобиль и возвращает копию или клон автомобиля с теми же свойствами. , Так что мы можем создать новый автомобильный объект с существующими автомобильными объектами. Например, как показано ниже

     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)));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...