Hibernate - How to persist a new item in a Collection without loading the entire Collection

前端 未结 4 1393
悲哀的现实
悲哀的现实 2020-12-05 14:20

I have a collection in my model that contains a set of \'previous versions\' of my root domain object. The previous versions are therefore \'immutable\' and we will never wa

相关标签:
4条回答
  • 2020-12-05 14:48

    When you have a List or Set-based collection and you add a new object into your collection, Hibernate will always hit the database because it compare one by one object by using equals implementation before saving or updating - when using a Set - or by comparing a index column when using a List. This behavior is needed because of the Set and List semantic. Because of that, the performance of your application can decrease significantly whether you have a bunch of records.

    Some workaround to overcome this issue

    Conversion pattern by using a encapsuled Bag collection plus your desired Set or List exposed as a property

    @Entity
    public class One {
    
        private Collection<Many> manyCollection = new ArrayList<Many>();
    
        @Transient
        public Set<Many> getManyCollectionAsSet() { return new HashSet<Many>(manyCollection); }
        public void setManyCollectionAsSet(Set<Many> manySet) { manyCollection = new ArrayList<Many>(manySet); }
    
        /**
          * Keep in mind that, unlike Hibernate, JPA specification does not allow private visibility. You should use public or protected instead
          */
        @OneToMany(cascade=ALL)
        private Collection<Many> getManyCollection() { return manyCollection; }
        private void setManyCollection(Collection<Many> manyCollection) { this.manyCollection = manyCollection; }
    
    }
    

    Use ManyToOne instead of OneToMany

    @Entity
    public class One {
    
        /**
          * Neither cascade nor reference
          */
    
    }
    
    @Entity
    public class Many {
    
        private One one;
    
        @ManyToOne(cascade=ALL)
        public One getOne() { return one; }
        public void setOne(One one) { this.one = one }
    
    }
    

    Caching - when applied because of, depending on your requirements, your configuration can increase or decrease the performance of your application. See here

    SQL constraint - If you want a collection that behaves like a Set, you can use a SQL constraint, which can be applied to a column or set of columns. See here

    0 讨论(0)
  • 2020-12-05 14:55

    The way I typically do this is to define the collection as "inverse".

    That roughly means: the primary definition of the 1-N association is done at the "N" end. Tf you want to add something to the collection you alter the associated object of the detail data.

    A small XML example:

    <class name="common.hibernate.Person" table="person">
        <id name="id" type="long" column="PERSON_ID">
            <generator class="assigned"/>
        </id>
        <property name="name"/>
        <bag name="addresses" inverse="true">
            <key column="PERSON_ID"/>
            <one-to-many class="common.hibernate.Address"/>
        </bag>
     </class>
    
     <class name="common.hibernate.Address" table="ADDRESS">
        <id name="id" column="ADDRESS_ID"/>
        <property name="street"/>
        <many-to-one name="person" column="PERSON_ID"/>
       </class>
    

    then the update is done exclusively in Address:

    Address a = ...;
    a.setPerson(me);
    a.setStreet("abc");
    Session s = ...;
    s.save(a);
    

    Done. You did not even touch the collection. Consider it read-only, which may be very practical for querying with HQL, and iterating and displaying it.

    0 讨论(0)
  • 2020-12-05 15:00

    If I understand well your need:

    • you have an in-memory add-only collection of immutable objects
    • how to add an item to that collection without initializing the entire collection (in the current Session)?

    Did you consider maintaining your collection disconnected?

    • Load it once when the application starts
    • Adding an element would mean two operations, done by a single service in only one place:
      • save the element (fast database operation, no cascading is done to the parent)
      • add the element to the existing disconnected collection (no database operation)
    0 讨论(0)
  • 2020-12-05 15:04

    I used to create a native query for this cases, like below:

        @Modifying
        @Transactional
        @Query(nativeQuery = true, value = "INSERT INTO categorytopic_topic(category_id, topic_id) VALUES (?1, ?2)")
        public void addTopic(long categoryId, long topicId);
    

    It's very fast because does not need load the collection.

    0 讨论(0)
提交回复
热议问题