java - por - spring jpa bulk insert
Pase lo que pase, no puedo enviar por lotes las instrucciones INSERT de MySQL en Hibernate (1)
Es probable que sus consultas se reescriban, pero no sabría si al ver los registros de Hibernate SQL. Hibernate no reescribe las instrucciones de inserción, el controlador MySQL las reescribe. En otras palabras, Hibernate enviará varias instrucciones de inserción al controlador, y luego el controlador las volverá a escribir. Por lo tanto, los registros de Hibernate solo le muestran qué SQL Hibernate envió al controlador, no qué SQL envió el controlador a la base de datos.
Puede verificar esto habilitando el parámetro profileSQL de MySQL en la url de conexión:
<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&rewriteBatchedStatements=true&profileSQL=true" />
Usando un ejemplo similar al tuyo, este es el aspecto de mi salida:
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
insert into Person (firstName, lastName, id) values (?, ?, ?)
Wed Feb 05 13:29:52 MST 2014 INFO: Profiler Event: [QUERY] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) duration: 1 ms, connection-id: 81, statement-id: 33, resultset-id: 0, message: insert into Person (firstName, lastName, id) values (''person1'', ''Name'', 1),(''person2'', ''Name'', 2),(''person3'', ''Name'', 3),(''person4'', ''Name'', 4),(''person5'', ''Name'', 5),(''person6'', ''Name'', 6),(''person7'', ''Name'', 7),(''person8'', ''Name'', 8),(''person9'', ''Name'', 9),(''person10'', ''Name'', 10)
Las primeras 10 líneas están siendo registradas por Hibernate, aunque esto no es lo que realmente se envía a la base de datos MySQL. La última línea proviene del controlador MySQL y muestra claramente una inserción de un solo lote con varios valores y eso es lo que realmente se envía a la base de datos MySQL.
Actualmente estoy enfrentando el conocido y común problema del lote de inserciones de Hibernate.
Necesito guardar lotes de 5 millones de filas de largo. Primero estoy tratando con una carga útil mucho más ligera. Dado que tengo que insertar entidades de solo 2 tipos (primero todos los registros del tipo A, luego todos los registros del tipo B, todos apuntando al tipo común C del padre ManyToOne
), me gustaría aprovechar al máximo el inserto por lotes JDBC.
Ya he leído mucha documentación, pero ninguna que haya probado ha funcionado.
- Sé que para usar inserciones por lotes no debo usar un generador de entidades. Así que
AUTO_INCREMENT
ID deAUTO_INCREMENT
y estoy configurando el ID con un truco:SELECT MAX(ID) FROM ENTITIES
y aumentar cada vez. - Sé que debo vaciar la sesión regularmente. Publicaré el código adelante, pero de todos modos realizo una transacción cada 500 elementos.
- Sé que tengo que configurar
hibernate.jdbc.batch_size
consistente con el tamaño masivo de mi aplicación, así que lo establezco enLocalSessionFactoryBean
(integración de Spring ORM) - Sé que debo habilitar la reescritura de declaraciones por lotes en la URL de conexión.
Aqui estan mis entidades
Entidad matriz común. Esto se inserta primero en una sola transacción. No me importa la columna de incremento automático aquí . Sólo un registro por trabajo por lotes
@Entity
@Table(...)
@SequenceGenerator(...)
public class Deal
{
@Id
@Column(
name = "DEAL_ID",
nullable = false)
@GeneratedValue(
strategy = GenerationType.AUTO)
protected Long id;
................
}
Uno de los niños (digamos 2.5M registros por lote)
@Entity
@Table(
name = "TA_LOANS")
public class Loan
{
@Id
@Column(
name = "LOAN_ID",
nullable = false)
protected Long id;
@ManyToOne(
optional = false,
targetEntity = Deal.class,
fetch = FetchType.LAZY)
@JoinColumn(
name = "DEAL_ID",
nullable = false)
protected Deal deal;
.............
}
El otro tipo de niños. Digamos los otros registros de 2.5M.
@Entity
@Table(
name = "TA_BONDS")
public class Bond
{
@Id
@Column(
name = "BOND_ID")
@ManyToOne(
fetch = FetchType.LAZY,
optional = false,
targetEntity = Deal.class)
@JoinColumn(
name = "DEAL_ID",
nullable = false,
updatable = false)
protected Deal deal;
}
Código simplificado que inserta registros.
long loanIdCounter = loanDao.getMaxId(), bondIdCounter = bondDao.getMaxId(); //Perform SELECT MAX(ID)
Deal deal = null;
List<Bond> bondList = new ArrayList<Bond>(COMMIT_BATCH_SIZE); //500 constant value
List<Loan> loanList = new ArrayList<Loan>(COMMIT_BATCH_SIZE);
for (String msg: inputStreamReader)
{
log.debug(msg.toString());
if (this is a deal)
{
Deal deal = parseDeal(msg.getMessage());
deal = dealManager.persist(holder.deal); //Called in a separate transaction using Spring annotation @Transaction(REQUIRES_NEW)
}
else if (this is a loan)
{
Loan loan = parseLoan(msg.getMessage());
loan.setId(++loanIdCounter);
loan.setDeal(deal);
loanList.add(loan);
if (loanList.size() == COMMIT_BATCH_SIZE)
{
loanManager.bulkInsert(loanList); //Perform a bulk insert in a single transaction, not annotated but handled manually this time
loanList.clear();
}
}
else if (this is a bond)
{
Bond bond = parseBond(msg.getMessage());
bond.setId(++bondIdCounter);
bond.setDeal(deal);
bondList.add(bond);
if (bondList.size() == COMMIT_BATCH_SIZE) //As above
{
bondManager.bulkInsert(bondList);
bondList.clear();
}
}
}
if (!bondList.isEmpty())
bondManager.bulkInsert(bondList);
if (!loanList.isEmpty())
loanManager.bulkInsert(loanList);
//Flush remaining items, not important
Implementación de bulkInsert
:
@Override
public void bulkInsert(Collection<Bond> bonds)
{
// StatelessSession session = sessionFactory.openStatelessSession();
Session session = sessionFactory.openSession();
try
{
Transaction t = session.beginTransaction();
try
{
for (Bond bond : bonds)
// session.persist(bond);
// session.insert(bond);
session.save(bond);
}
catch (RuntimeException ex)
{
t.rollback();
}
finally
{
t.commit();
}
}
finally
{
session.close();
}
}
Como puede ver en los comentarios, he intentado varias combinaciones de session
estado / sin estado. Ninguno funciono
Mi dataSource
es una ComboPooledDataSource
con la siguiente URL
<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&rewriteBatchedStatements=true" />
Mi SessionFactory
<b:bean id="sessionFactory" class="class.that.extends.org.springframework.orm.hibernate3.LocalSessionFactoryBean" lazy-init="false" depends-on="dataSource">
<b:property name="dataSource" ref="phoenixDataSource" />
<b:property name="hibernateProperties">
<b:props>
<b:prop key="hibernate.dialect">${hibernate.dialect}</b:prop> <!-- MySQL5InnoDb-->
<b:prop key="hibernate.show_sql">${hibernate.showSQL}</b:prop>
<b:prop key="hibernate.jdbc.batch_size">500</b:prop>
<b:prop key="hibernate.jdbc.use_scrollable_resultset">false</b:prop>
<b:prop key="hibernate.cache.use_second_level_cache">false</b:prop>
<b:prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</b:prop>
<b:prop key="hibernate.cache.use_query_cache">false</b:prop>
<b:prop key="hibernate.validator.apply_to_ddl">false</b:prop>
<b:prop key="hibernate.validator.autoregister_listeners">false</b:prop>
<b:prop key="hibernate.order_inserts">true</b:prop>
<b:prop key="hibernate.order_updates">true</b:prop>
</b:props>
</b:property>
</b:bean>
Incluso si mi clase de todo el proyecto extiende LocalSessionFactoryBean
, no anula sus métodos (solo agrega algunos métodos de todo el proyecto)
Me estoy enojando desde hace unos días. Leí algunos artículos y ninguno me ayudó a habilitar las inserciones por lotes. @Autowire
todo mi código de las pruebas JUnit instrumentadas con el contexto de Spring (por lo que puedo @Autowire
mis clases). Todos mis intentos solo producen un montón de INSERT
separadas
- https://stackoverflow.com/questions/12011343/how-do-you-enable-batch-inserts-in-hibernate
- https://stackoverflow.com/questions/3469364/faster-way-to-batch-saves-with-hibernate
- https://forum.hibernate.org/viewtopic.php?p=2374413
- https://stackoverflow.com/questions/3026968/high-performance-hibernate-insert
¿Qué me estoy perdiendo?