orochi
orochi

Reputation: 1258

Saving multiple entities by multiple threads with hibernate

I'd like to save new entities by multiple threads with hibernate.

OBS: I can't use batch process on client.

So I made this structure:

+-----------------+
|+------------------+
||+------------------+     +-----------------------------------------------+
|||                  |     | Each thread do these steps:                   |
||| Multiple Threads |     |  - get EntityManagerFactory                   |
|||                  |---> |  - create Entity Manager                      |
||| Creating new     |     |  - begin new transaction                      |
+||    Entities on   |     |  - create new entity (with autoincrement id)  |
 +|        Hibernate |     |  - persist                                    |
  +------------------+     |  - commit the transaction                     |
                           |  - close the entity manager                   |
                           +-----------------------------------------------+

The entity:

@Entity
@Table(name="EN_TEST")
public class EnTest {
    private long id;

    private String name;

    public EnTest() {
    }

    @Id
    @GeneratedValue(generator="increment")
    @GenericGenerator(name="increment", strategy = "increment")
    @Column(name = "ID")
    public long getId() {
        return id;
    }

    @SuppressWarnings("unused")
    private void setId(long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(length = 20)
    public String getName() {
        return name;
    }
}

The java stuff (DBHandler):

// just to get the session factory
public class DBHandler {
    private static DBHandler dbHandler;

    private static EntityManager entityManager;

    public static DBHandler get() {
        if (dbHandler == null)  {
            dbHandler = new DBHandler();
        }

        return dbHandler;
    }

    private DBHandler() {
        try {
            this.sessionFactory = Persistence.createEntityManagerFactory("a_persistence_unit");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private EntityManagerFactory sessionFactory;

    public EntityManagerFactory getSessionFactory() {
        return sessionFactory;
    }
}

The java stuff (The creation process):

public class CreateTest {

    // this is accessed by multiple threads to create new entities
    public static void crateNewEntityTest(String name) {
        EntityManagerFactory factory = DBHandler.get().getSessionFactory();

        EntityManager entityManager = factory.createEntityManager();

        entityManager.getTransaction().begin();

        EnTest newEnTest = new EnTest();
        newEnTest.setName(name);

        entityManager.persist(newEnTest);

        entityManager.getTransaction().commit();
        entityManager.close();
    }

       // The creation of threads and saving, this is just an example:
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread() {
                public void run() {
                    CreateTest.createNewEntityTest("Name_" + i);
                };
            }.start();
        }
    }
}

The error:

Caused by: javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1389)
    at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1317)
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:81)
    ... 2 more
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:76)
    ... 2 more
Caused by: java.sql.BatchUpdateException: Duplicate entry '3' for key 'PRIMARY'
    at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1666)
    at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1082)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
    ... 10 more

The hibernate complains about the autoincrement of my Entity!

What strategy should I take?

Must I implement a Thread pool or something to handle the multiple threads entity saving?

This thread issue wasn't to be a hibernate task?

Upvotes: 3

Views: 2947

Answers (2)

Avihai Marchiano
Avihai Marchiano

Reputation: 3927

Use identity (or sequence depend on DB termiology) or GUID or your unique natural business identity.

Upvotes: 0

Aaron Digulla
Aaron Digulla

Reputation: 328614

I couldn't find an obvious mistake.

Suggestions:

  1. The documentation says "Do not use increment in a clustered environment". That doesn't seem to be your case but make sure that you run only one process. Several threads in the same process should be OK.

  2. Use a different Generator. For MySQL, identity is a good candidate because the database will give you an ID. Note that you need to give the columns the correct type, too.

Upvotes: 2

Related Questions