Search code examples
postgresqlejbpostgisjpa-2.1hibernate-spatial

JPA Create Parent/child relationship with a lot of children


I am trying to store entity Track with children entities TrackPoints with JPA method create. However, to store Track with its children TrackPoints last really long - about 30 seconds. I tried GenerationType.Identity and GenerationType.SEQUENCE. If I also have Hibernate Spatial (Postgis) column, it lasts even longer - about 60 seconds to store parent and all children. JPA sends insert sequentially one followed by another. How can I optimize this? Can anybody tell me what is the main problem?

Technologies:

  • Wildfly 8.1, JPA 2.1 (hibernate), Hibernate Spatial, EJB, JTA
  • PostgreSQL 9.3 + PostGis - default setup (just install from Ubuntu package)

Track.java

@Entity
@Table(name = "TRACKS")
public class Track implements Serializable {    
    @Id
    @Column(name = "track_id", nullable = false, unique = true)
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @NotNull
    @NotEmpty
    @Size(min = 1, max = 100)
    @Column(nullable = false, length = 100)
    private String name;

    @Size(max = 200)
    @Column(nullable = false, length = 200)
    private String description;

    @OneToOne(optional = false)
    @JoinColumn(name = "userId", nullable = false)
    private User userOwner;

    @NotNull
    @NotEmpty
    @Column(nullable = false, length = 55)
    private String type;

    @NotNull
    private Boolean isShared;

    @OneToMany(mappedBy = "track")
    private List<TrackPoint> trackPoints;
}

TrackPoint.java

@Entity
@Table(name = "TRACK_POINTS")
public class TrackPoint implements Serializable {

    private static final long serialVersionUID = 8089601593251025235L;

    @Id
    @Column(name = "trackpoint_id", nullable = false, unique = true)
    @GeneratedValue(generator = "track_point_sequence", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "track_point_sequence", sequenceName = "track_point_sequence", allocationSize = 1000)
    private Long id;

    @NotNull
    private int trackSegment;

    @NotNull
    private double elevation;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    private Date timeStamp;

    @NotNull
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "track_id")
    private Track track;

    /*Hibernate Spatial - Postgis field.
    @NotNull
    @Column(nullable = false)
    @Type(type = "org.hibernate.spatial.GeometryType")
    private Geometry location;*/
}

TrackService.java

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class TracksService implements ITracksService {

    @Inject
    private IDaoService dao;

    @Override
    public Long createTrack(GpxType gpx, String userId, String name, String desc) {
        // Map GPX to Track, TrackPoint object.
        track = dao.create(track);

    int batch_size = 50;

    int i = 0;

        for(TrackPoint point: track.getTrackPoints()) {
            dao.create(point);
            if(i++ % batch_size == 0) {
                dao.flush();
                dao.clear();
            }
        }

        return track.getId();

}

DaoService.java

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoService implements IDaoService {

    @PersistenceContext()
    private EntityManager em;

    @Override
    public <T extends Serializable> T create(T t) {
        em.persist(t);
        return t;
    }
}

persistence.xml

<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xsi:schemaLocation=
                     "http://xmlns.jcp.org/xml/ns/persistence
                          http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

    <persistence-unit name="postgisTourbookPU" transaction-type="JTA">

        <description>PostgresSQL database with PostGIS extension</description>
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>${tourbook.datasource.postgresql.jndi-name}</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <shared-cache-mode>NONE</shared-cache-mode>

        <properties>
            <!-- JPA properties -->
            <property name="javax.persistence.schema-generation.database.action"
                      value="drop-and-create"/>

            <!--            <property name="javax.persistence.schema-generation-target"
                                  value="database"/>-->

            <!-- Creation Schema Properties -->
            <property name="javax.persistence.schema-generation.create-source"
                      value="metadata"/>

            <!--            &lt;!&ndash; DDL Script location, when script is used &ndash;&gt;
                        <property name="javax.persistence.schema-generation.create-script-source"
                                    value="META-INF/create-script.sql"/>-->

            <!-- Drop Schema Properties -->
            <property name="javax.persistence.schema-generation.drop-source"
                      value="metadata"/>
            <!--            <property name="javax.persistence.schema-generation.drop-script-source"
                                  value="META-INF/drop-script.sql"/>-->

            <property name="javax.persistence.sql-load-script-source"
                                  value="META-INF/load-script.sql"/>

            <!-- JPA driver information -->
            <property name="javax.persistence.jdbc.driver"
                      value="org.postgresql.Driver"/>

            <!-- Hibernate properties -->
            <property name="hibernate.connection.characterEncoding"
                      value="UTF-8"/>

            <property name="hibernate.dialect"
                      value="org.hibernate.spatial.dialect.postgis.PostgisDialect"/>

            <property name="hibernate.default_schema"
                      value="public"/>

            <property name="hibernate.show_sql"
                      value="true"/>

            <property name="hibernate.jdbc.batch_size" value="50"/>

            <property name="hibernate.jdbc.fetch_size"
                      value="50"/>

            <property name="hibernate.order_inserts"
                      value="true"/>

            <property name="hibernate.order_updates"
                      value="true"/>

            <property name="hibernate.cache.use_query_cache"
                      value="false"/>

            <!-- Hibernate caching -->

        </properties>
    </persistence-unit>
</persistence>

Edited

So I have tried, batch insert in Hibernate, but I still get 30 seconds for saving 2000 points.


Solution

  • you're inserting a parent with all the children. In that case the Hibernate JPA indeed can be slow, but there are a few tips to improve the performance - check the hibernate batch guide http://docs.jboss.org/hibernate/core/4.0/devguide/en-US/html/ch04.html - I've used the hibernate.jdbc.batch_size parameter (set to e.g. 50)

    Good luck Gabriel