【发布时间】:2014-02-27 02:26:19
【问题描述】:
我目前正面临众所周知且常见的 Hibernate 插入批处理问题。
我需要保存 500 万行长的批次。我首先尝试使用更轻的有效载荷。由于我只需要插入 2 种类型的实体(首先是 A 类型的所有记录,然后是 B 类型的所有记录,都指向公共类型 C ManyToOne parent),因此我想充分利用 JDBC 批量插入的优势。
我已经阅读了很多文档,但我尝试过的都没有。
-
我知道为了使用批量插入,我不能使用实体生成器。所以我删除了
AUTO_INCREMENTID,并设置了一个技巧:SELECT MAX(ID) FROM ENTITIES,并且每次都递增。 - 我知道我必须定期刷新会话。我会提前发布代码,但无论如何我每 500 个元素执行一次事务。
-
我知道我必须将
hibernate.jdbc.batch_size设置为与我的应用程序的批量大小一致,因此我将其设置在LocalSessionFactoryBean(Spring ORM 集成)中 - 我知道我必须在连接 URL 中启用重写批处理语句。
这是我的实体
共同的父实体。这首先插入到单个事务中。我不关心这里的自动增量列。每个批处理作业只有 一个 记录
@Entity
@Table(...)
@SequenceGenerator(...)
public class Deal
{
@Id
@Column(
name = "DEAL_ID",
nullable = false)
@GeneratedValue(
strategy = GenerationType.AUTO)
protected Long id;
................
}
其中一个孩子(假设每批 250 万条记录)
@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;
.............
}
其他孩子键入。假设其他 250 万条记录
@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;
}
插入记录的简化代码
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
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();
}
}
从 cmets 可以看出,我尝试了几种有状态/无状态 session 的组合。没有任何效果。
我的dataSource 是ComboPooledDataSource,带有以下网址
<b:property name="jdbcUrl" value="jdbc:mysql://server:3306/db?autoReconnect=true&rewriteBatchedStatements=true" />
我的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>
即使我的项目范围的类扩展了LocalSessionFactoryBean,它不会覆盖它的方法(只添加了几个项目范围的方法)
这几天我开始生气了。我阅读了几篇文章,但没有一篇文章帮助我启用批量插入。我从使用 Spring 上下文检测的 JUnit 测试中运行我的所有代码(所以我可以@Autowire 我的类)。我所有的尝试只会产生很多单独的INSERT 语句
- 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
我错过了什么?
【问题讨论】:
-
我真的很绝望,我需要 mysql + hibernate 批量插入,你做到了吗?
-
唯一的方法是向底层驱动程序发出直接查询。或者切换到 C#,您可以在其中使用 Entity Framework 进行本机批量插入。
-
你能看看这个吗,我会很感激的。 stackoverflow.com/questions/59153072/…
标签: java mysql hibernate jdbc batch-processing