У меня проблема при добавлении новой записи в отношении «многие ко многим», потому что список огромен. Бывший:
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);
Проблема в том, что список Category Itens огромен, с большим количеством элементов, поэтому выполнение cat.getItemList () занимает очень много времени. Везде, где я ищу правильный способ добавить запись "многие ко многим", говорится, что это необходимо. Может кто поможет?
Редактировать: Небольшой контекст: я организовываю свои itens с помощью тегов, так что 1 элемент может иметь несколько тегов, а 1 тег может иметь несколько itens, время прошло, и теперь у меня есть теги с большим количеством itens (> 5.000), и теперь, когда я сохраняю новый элемент с одним из этих тегов занимает много времени, я отладил свой код и обнаружил, что большая часть задержки находится в строке cat.getItensList (), что имеет смысл, поскольку у нее есть обширный список o itens. Я много искал, как это сделать, и все говорят, что правильный способ сохранить запись в случае «многие ко многим» - это добавить в список с обеих сторон отношения, но если одна сторона огромна, это займет много времени, поскольку вызов 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 /
Но если я использую составной ключевой класс для отношений «многие ко многим», не нужно ли мне получать список этого класса с обеих сторон и добавлять этот класс, как я должен это сделать сейчас?




В этом случае я бы не допустил, чтобы ваш код загружал список элементов в памяти.
Для этого я могу подумать о двух вариантах:
@Modyfing для вставки элементов непосредственно в БД.[Рекомендуется для случаев, когда вы не хотите менять свою модель]
Вы можете попробовать создать запрос, используя обычный JPQL, но, в зависимости от вашей модели, вам может потребоваться использовать собственный запрос. Использование собственного запроса будет примерно таким:
@Query(value = "insert into ...", nativeQuery = true)
void addItemToCategory(@Param("param1") Long param1, ...);
После создания этого запроса вам нужно будет обновить свой код, удалив части, в которых вы загружаете объекты в память, и добавив части для вызова операторов вставки.
[Update] As you mentioned in a comment, doing this improved your performance from 10s to 300milis.
@ManyToMany отношениями @OneToManysИдея этого решения состоит в том, чтобы заменить связь ManyToMany между объектами 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.
*/
}
}
Приведенный выше код объясняет другой способ справиться с подобными проблемами. Конечно, вы можете вносить изменения в соответствии с тем, что вам нужно.
Надеюсь это поможет :)
Я отредактировал вопрос, добавив больше контекста. Написание запроса, вероятно, решит мою проблему, но это сложный объект, и я бы предпочел использовать EntityManager и Pojos JPA. В последнем случае напишу запрос, спасибо за ответ
Хорошо, теперь я понимаю. Эти отношения двунаправленные? Пожалуйста, попробуйте добавить только категорию к элементу и не добавляйте элемент в cat.getItemList(), и посмотрите, что произошло, когда вы его сохраните. Если это не сработает, добавьте часть своей модели (особенно строки, в которых вы определяете отношение @ManyToMany)
Они двунаправленные, поэтому добавить только одну сторону не получится. Я снова редактировал с классами). Я написал запрос и сократил время с 10 до 300 миль, но мне кажется неправильным, что что-то такое простое и использованное может вызвать такую большую проблему (извините за мой английский)
Рад слышать это, невероятная оптимизация. Однако я понимаю, и вы правы, мы можем сделать что-нибудь более чистое. Я собираюсь отредактировать ответ, предоставив вам другой вариант, аналогичный тому, что предложил @ K.Nicholas.
Это в основном скопировано из аналогичного ответа, который я дал ранее, но также на аналогичный вопрос. Приведенный ниже код запускался, когда я впервые его писал, но я изменил имена, чтобы они соответствовали этому вопросу, поэтому могут быть некоторые опечатки. 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 либо явно, используя собственное репо, либо добавляя и удаляя их из списка, поскольку 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);
5000 - это не очень много, и я думаю, что его следует загружать только один раз, но если вы хотите вставить отношение напрямую, вы можете специально создать объект соединения (требуется создание составного ключа, множество примеров) и вставить новое отношение в эта сущность. Это должно быть в состоянии сделать это без необходимости сначала читать что-либо.