JPA:将新条目添加到需要很长时间的多对多

时间:2018-12-10 13:45:36

标签: java mysql hibernate jpa many-to-many

在以多对多关系添加新条目时存在问题,因为列表很大。例如:

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

问题在于类别索引列表很大,并且包含很多索引,因此执行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: 我要解决的问题: 正如Ariel Kohan回答的那样,我尝试执行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()); 

我能够将查询的时间从10s减少到300milis,这真是令人印象深刻。最后,对于我的项目而言,这样做已经很合适了,而不是创建一个表示多对多重新安置的新类,这对它更好。感谢所有尝试帮助\ o /

的人

2 个答案:

答案 0 :(得分:1)

在这种情况下,我将防止您的代码将项目列表加载到内存中。

为此,我可以考虑以下两种选择:

使用@Modyfing查询将项目直接插入数据库中。

[推荐用于避免更改模型的情况]

您可以尝试使用常规JPQL创建查询,但是根据您的模型,可能需要使用本机查询。使用本机查询将是这样的:

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

创建此查询后,您将需要更新代码,以删除将对象加载到内存中的部分,并添加这些部分以调用insert语句。

  

[更新]   正如您在评论中提到的那样,这样做可以将您的性能从10s提高到300milis。


修改您的实体,以将@ManyToMany的关系替换为@OneToMany的关系

此解决方案中的想法是用中间实体A替换实体BRelationAB之间的ManyToMany关系。我认为您可以通过两种方式做到这一点:

  1. 仅将RelationAB中A和B中的ID保存为组合键(当然,您可以添加其他字段,例如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;
    }

}

RelationABEntityRelationABId

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

上面的代码解释了另一种处理这类事情的方法。当然,您可以根据实际需要进行修改。

希望这会有所帮助:)

答案 1 :(得分:0)

这基本上是从我之前给出的类似答案中复制而来的,但同样也存在类似的问题。第一次编写时,下面的代码运行了,但是我更改了名称以匹配该问题,因此可能会有一些错别字。 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的ID。这些由持久层(在这种情况下为休眠)处理。

还请注意,由于已设置ItemCategory,因此可以使用其自己的存储库显式更新CascadeType.ALL条目,也可以通过从列表中添加和删除它们来进行更新,如图所示。对spring-data-jpa使用CascadeType.ALL的问题在于,即使您预取了连接表实体,spring-data-jpa仍将再次执行此操作。尝试通过CascadeType.ALL更新新实体的关系是有问题的。

没有CascadeTypeitems列表(应为Set)都不是该关系的所有者,因此,在持久性和仅用于查询结果。

在阅读categories关系时,由于没有ItemCategory,因此需要专门获取它们。 FetchType.EAGER的问题是开销,如果您不希望联接,并且同时将其放在FetchType.EAGERCategory上,那么您将创建一个递归获取来获取所有{{ 1}}和Item进行任何查询。

categories