Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

select nextval for every row inserted with hibernate

It's my first time using Hibernate and when I insert multiple rows in a loop hibernate prints out the following for each row being inserted

Hibernate: select nextval ('hibernate_sequence')

after it does that for all the data in the loop it starts to insert the data

Hibernate: insert into user_data (age, location, sent_count, user_id, username, id) values (?, ?, ?, ?, ?, ?)

Is this how hibernate always operates? would it not be possible for the database to take care of the sequence? I feel like this really slows down the process of inserting rows. I am using PostgreSQL for my database.

Here is my relevant code user_data

@Entity
@Table(name = "user_data")
public class UserData
{
  @Id @GeneratedValue
  @Column(name = "id")
  private int id;

  @Column(name = "user_id")
  private String userid;

  @Column(name = "username")
  private String username;

  @Column(name = "age")
  private int age;

  @Column(name = "location")
  private int location;

  @Column(name = "sent_count")
  private int sentCount;

    public int getId()
    {
        return id;
    }

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

    public String getUserid()
    {
        return userid;
    }

    public void setUserid(String userid)
    {
        this.userid = userid;
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }

    public int getLocation()
    {
        return location;
    }

    public void setLocation(int location)
    {
        this.location = location;
    }

    public int getSentCount()
    {
        return sentCount;
    }

    public void setSentCount(int sentCount)
    {
        this.sentCount = sentCount;
    }
}

My Hibernate helper class

public class HibernateUtil
{
    static SessionFactory       sessionFactory;
    static ServiceRegistry  serviceRegistry;

    static
    {
        try
        {
            Configuration configuration = new Configuration();
            configuration.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
            configuration.configure();
            serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
            sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        }
        catch (Throwable ex)
        {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            closeSessionFactory();
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory()
    {
        return sessionFactory;
    }

    public static void closeSessionFactory()
    {
        sessionFactory.close();
    }

}

and the relevant section which inserts the rows

    Session session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();

    for (String memberId : memberIds)
    {
        System.out.println(memberId);
        UserData user = new UserData();
        user.setUserid(memberId);
        session.save(user);
    }

    session.getTransaction().commit();
    session.close();
    HibernateUtil.closeSessionFactory();
like image 243
Arya Avatar asked Nov 17 '14 22:11

Arya


1 Answers

When you call session.save(), you aren't inserting anything to the database yet. You are just enrolling your object to the session cache, where Hibernate keeps track of all the objects that will eventually need to be inserted/updated in the database. But what Hibernate needs right away is the new object's identifier, so it can wire other objects which refer to it by foreign key.

By default Hibernate will use a simple-minded ID generator backed by a native DB sequence, obtaining an ID from that sequence for each saved object. This will work alright as long as you don't do batch inserts (insert a lot of stuff in the same transaction). Your for-loop does make it look like you're doing precisely that, though.

If you have a need for good performance while doing batch inserts, you'll need to take care of a number of issues (search around for it), but here I'll present the solution to your immediate question: optimized sequence generators. This is what I use:

@GenericGenerator(name = "optimized-sequence", strategy = "enhanced-sequence", parameters = {
    @Parameter(name = "prefer_sequence_per_entity", value = "true"),
    @Parameter(name = "optimizer", value = "hilo"),
    @Parameter(name = "increment_size", value = "50") })
package org.example.myproject;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

The above must be saved in a file named package-info.java. What it achieves is define a package-wide custom ID generator under the name optimized-sequence. You can use it like this:

@Id @GeneratedValue(generator = "optimized-sequence") public long id;

and what this will buy you is a native sequence-backed ID generator, but one which needs to bump the DB sequence only once for every 50 (configurable) saved objects. It changes the semantics of the underlying sequence such that, for example, currval = 1 means that the current Hibernate session has reserved the ID range 1-50 for itself. Yes, this will create "holes" in your ID space, but with 264 to go around, you shouldn't start worrying about that very soon.

like image 122
Marko Topolnik Avatar answered Nov 15 '22 18:11

Marko Topolnik