JPA: добавление новой записи для многих многим занимает много времени - PullRequest
0 голосов
/ 10 декабря 2018

У меня возникла проблема при добавлении новой записи в отношении многие ко многим, потому что список огромен.Пример:

Item item = new Item(1);
Category cat = dao.find(1, Category.class);
List<Category> list = new ArrayList<>();
list.add(cat);
item.setCategoryList(list);
cat.getItemList().add(item);

Проблема в том, что список категорий Itens огромен, с большим количеством itens, поэтому выполнение cat.getItemList () занимает очень много времени.Везде, где я ищу правильный способ добавления записи «многие ко многим», говорится, что это необходимо сделать.Может кто-нибудь помочь?

Редактировать: Небольшой контекст: я организовываю свои itens с тегами, так что 1 элемент может иметь несколько тегов, и 1 тег может иметь несколько itens, время прошло, и теперь у меня есть теги с большим количеством itens (>5.000), и теперь, когда я сохраняю новый элемент с одним из этих тегов, это занимает много времени, я отлаживал свой код и обнаружил, что большая часть задержки находится в строке cat.getItensList (), что имеет смысл, так как имеетобширный список итальянцев.Я много искал, как это сделать, и все говорят, что правильный способ сохранить запись в случае «многие ко многим» - это добавить в список по обе стороны отношения, но если одна сторона огромна,это займет много времени, так как вызов getItensList () загружает их в контексте.Я ищу способ сохранить свой предмет, ссылаясь на тег без загрузки всех итэнов этого тега.

Редактировать 2: Мои классы: Предмет:

@Entity
@Table(name = "transacao")
@XmlRootElement
 public class Transacao implements Serializable {
    @ManyToMany(mappedBy = "transacaoList")
    private List<Tagtransacao> tagtransacaoList;
    ...(other stuff)
}

Тег:

@Entity
@Table(name = "tagtransacao")
@XmlRootElement
public class Tagtransacao implements Serializable {
  @JoinTable(name = "transacao_has_tagtransacao", joinColumns = {
        @JoinColumn(name = "tagtransacao_idTagTransacao", referencedColumnName = "idTagTransacao")}, inverseJoinColumns = {
        @JoinColumn(name = "transacao_idTransacao", referencedColumnName = "idTransacao")})
    @ManyToMany
    private List<Transacao> transacaoList;
...(other stuff)
}

Редактировать 3: Что я решил решить: В ответ на вопрос Ариэля Кохана я попытался сделать NativeQuery для вставки отношения:

  Query query = queryDAO.criarNativeQuery("INSERT INTO " + config.getNomeBanco() + ".`transacao_has_tagtransacao` "
            + "(`transacao_idTransacao`, `tagtransacao_idTagTransacao`) VALUES (:idTransacao, :idTag);");
    query.setParameter("idTransacao", transacao.getIdTransacao());
    query.setParameter("idTag", tag.getIdTagTransacao()); 

Я смогсократить время ожидания очереди с 10 секунд до 300 миллионов, что впечатляет.В конце концов, для моего проекта лучше, если он уже готов сделать это, вместо того, чтобы создавать новый класс, который представляет выпуск «многие ко многим».Спасибо всем, кто пытался помочь \ o /

Ответы [ 2 ]

0 голосов
/ 11 декабря 2018

Это в основном скопировано с аналогичного ответа, который я дал ранее, но схожего вопроса.Приведенный ниже код запускался при первом написании, но я изменил имена, чтобы соответствовать этому вопросу, поэтому могут быть некоторые опечатки.Spring-data-jpa - это слой поверх JPA.У каждой сущности есть свой собственный репозиторий, с которым вам приходится иметь дело.Для работы со связями «многие ко многим», в частности в spring-data-jpa, вы можете создать отдельный репозиторий для таблицы ссылок, если вы считаете, что это хорошая идея.

@Entity
public class Item {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ItemCategory> categories;

@Entity
public class Category {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<ItemCategory> items;

@Entity
public class ItemCategory {
    @EmbeddedId
    private ItemcategoryId id = new ItemcategoryId();

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("itemId")
    private Item Item;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("categoryId")
    private Category category;

    public ItemCategory() {}
    public ItemCategory(Item Item, Category category) {
        this.item = item;
        this.category = category;
    }

@SuppressWarnings("serial")
@Embeddable
public class ItemCategoryId implements Serializable {
    private Long itemId;
    private Long categoryId;
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        ItemCategoryId that = (ItemCategoryId) o;
        return Objects.equals(itemId, that.itemId) && Objects.equals(categoryId, that.categoryId);
    }
    @Override
    public int hashCode() {
        return Objects.hash(itemId, categoryId);
    }

И использовать ее.Шаг 3 показывает, как вы это делаете в настоящее время, и создает чтение существующих объединений перед выполнением обновления.Шаг 4 просто вставляет отношение непосредственно в таблицу соединений и не вызывает предварительного чтения существующих объединений.

@Transactional
private void update() {
    System.out.println("Step 1");
    Category category1 = new Category();
    Item item1 = new Item();
    ItemCategory i1c1 = new ItemCategory(Item1, Category1);
    categoryRepo.save(Category1);
    ItemRepo.save(Item1);
    ItemCategoryRepo.save(p1t1);

    System.out.println("Step 2");
    Category category2 = new Category();
    Item item2 = new Item();
    ItemCategory p2t2 = new ItemCategory(item2, category2);
    ItemRepo.save(item2);
    categoryRepo.save(category2);
    ItemCategoryRepo.save(p2t2);

    System.out.println("Step 3");
    category2 = CategoryRepo.getOneWithitems(2L);
    category2.getitems().add(new ItemCategory(item1, category2));
    categoryRepo.save(Category2);

    System.out.println("Step 4 -- better");
    ItemCategory i2c1 = new ItemCategory(item2, category1);
    itemCategoryRepo.save(i2c1);
}

Я не устанавливаю явно ItemCategoryId идентификаторы.Они обрабатываются постоянным слоем (в данном случае спящим).

Также обратите внимание, что вы можете обновлять записи ItemCategory либо explicity с его собственным репо, либо добавляя и удаляя их из списка, так как CascadeType.ALL установлен, как показано.Проблема с использованием CascadeType.ALL для spring-data-jpa заключается в том, что даже если вы предварительно выберете сущности таблицы соединений, spring-data-jpa все равно сделает это снова.Попытка обновить отношение через CascadeType.ALL для новых сущностей проблематично.

Без CascadeType ни списки items, ни categories (которые должны быть наборами) не являются владельцами отношения, поэтомудобавление к ним ничего не даст в плане сохранения и будет только для результатов запроса.

При чтении отношений ItemCategory вам необходимо специально их извлечь, поскольку у вас нет FetchType.EAGER.Проблема с FetchType.EAGER заключается в накладных расходах, если вы не хотите объединений, а также если вы поместите их в Category и Item, тогда вы создадите рекурсивную выборку, которая получает все categories и items длялюбой запрос.

@Query("select c from Category c left outer join fetch c.items is left outer join fetch is.Item where t.id = :id")
Category getOneWithItems(@Param("id") Long id);
0 голосов
/ 10 декабря 2018

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

Для этого я могу подумать о двух вариантах:

Использование запроса @Modyfing длявставьте элементы непосредственно в базу данных.

[Рекомендуется для случаев, когда вы хотите избежать изменения вашей модели]

Вы можете попытаться создать запрос, используя обычный JPQL, но, в зависимости от вашей модели,вам может понадобиться использовать собственный запрос.Использование собственного запроса будет выглядеть примерно так:

@Query(value = "insert into ...", nativeQuery = true)
void addItemToCategory(@Param("param1") Long param1, ...);

После создания этого запроса вам потребуется обновить код, удалив части, в которые вы загружаете объекты в память, и добавив части для вызова операторов вставки.

[Обновление] Как вы упомянули в комментарии, это повысило вашу производительность с 10 секунд до 300 миллионов.


Изменение ваших сущностейчтобы заменить @ManyToMany на @OneToMany s отношение

Идея в этом решении состоит в том, чтобы заменить отношение ManyToMany между объектами A и B промежуточным объектом RelationAB.Я думаю, что вы можете сделать это двумя способами:

  1. Сохранить только идентификаторы из A и B в RelationAB в качестве составного ключа (конечно, вы можете добавить другие поля, такие как Date или что угодно).
  2. Добавьте автоматически сгенерированный Id в RelationAB и добавьте A и B в качестве других полей в сущности RelationAB.

Я сделал пример с использованием первой опции (вы увидите, чтоклассы не являются общедоступными, это просто потому, что я решил сделать это в одном файле для простоты. Конечно, вы можете сделать это в нескольких файлах и с открытыми классами, если хотите):

Entities A and B:

@Entity
class EntityA  {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    public EntityA() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

}

@Entity
class EntityB {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;


    public EntityB() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

}

RelationABEntity и RelationABId:

@Embeddable
class RelationABId implements Serializable {
    private Long entityAId;
    private Long entityBId;

    public RelationABId() {
    }

    public RelationABId(Long entityAId, Long entityBId) {
        this.entityAId = entityAId;
        this.entityBId = entityBId;
    }

    public Long getEntityAId() {
        return entityAId;
    }

    public void setEntityAId(Long entityAId) {
        this.entityAId = entityAId;
    }

    public Long getEntityBId() {
        return entityBId;
    }

    public void setEntityBId(Long entityBId) {
        this.entityBId = entityBId;
    }
}

@Entity
class RelationABEntity {

    @EmbeddedId
    private RelationABId id;

    public RelationABEntity() {
    }

    public RelationABEntity(Long entityAId, Long entityBId) {
        this.id = new RelationABId(entityAId, entityBId);
    }

    public RelationABId getId() {
        return id;
    }

    public void setId(RelationABId id) {
        this.id = id;
    }
}

Мои репозитории:

@Repository
interface RelationABEntityRepository extends JpaRepository<RelationABEntity, RelationABId> {

}

@Repository
interface ARepository extends JpaRepository<EntityA, Long> {


}

@Repository
interface BRepository extends JpaRepository<EntityB, Long> {

}

Тест:

@RunWith(SpringRunner.class)
@DataJpaTest
public class DemoApplicationTest {

    @Autowired RelationABEntityRepository relationABEntityRepository;
    @Autowired ARepository aRepository;
    @Autowired BRepository bRepository;


    @Test
    public void test(){
        EntityA a = new EntityA();
        a = aRepository.save(a);
        EntityB b = new EntityB();
        b = bRepository.save(b);

        //Entities A and B in the DB at this point

        RelationABId relationABID = new RelationABId(a.getId(), b.getId());

        final boolean relationshipExist = relationABEntityRepository.existsById(relationABID);
        assertFalse(relationshipExist);

        if(! relationshipExist){
            RelationABEntity relation = new RelationABEntity(a.getId(), b.getId());
            relationABEntityRepository.save(relation);
        }

        final boolean relationshipExitNow = relationABEntityRepository.existsById(relationABID);
        assertTrue(relationshipExitNow);
        /**
         * As you can see, modifying your model you can create relationships without loading big list and without complex queries. 
         */
    }    
}

Приведенный выше код объясняет другой способ обработки подобных вещей.Конечно, вы можете вносить изменения в соответствии с вашими потребностями.

Надеюсь, это поможет:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...