用于更好的内存分配的 StringBuilder 替代方案

StringBuilder alternative for better memory allocation

我正在使用此 StringBuilder 以在查询中添加内容:

Integer lastEntryInEntityId = 1;//acquired through another query
Integer tmpValueForEntityId;
Integer lastEntryInEntity2Id = 1;//acquired through another query

StringBuilder queryString = new StringBuilder("insert 
into entity(column,column_1,column_2,column_3) values");
StringBuilder queryString2 = new StringBuilder("insert 
into entity2(column,column_1,column_2,column_3) values");

for(Object[] entityToCopy : entitiesToCopy){
    Entity entity= (Entity )entityToCopy[0];
    tmpValueForEntityId= lastEntryInEntityId ;
    queryString.append("("+ lastEntryInEntityId ++ +","+entity.getProperty()+","+entity[1]+","+entity.getProperty2()+"),");

    for(Entity2 entity2 : entity.getEntity2Collection()){
        queryString2.append("("+lastEntryInEntity2Id ++ +","+tmpValueForEntityId+","+entity.getProperty2()+","+entity.getProperty3()+"),");
    }
}

此代码占用太多时间和内存。一段时间后(当 entitiesToCopy 太多时),它实际上会在添加到第二个 StringBuilder 时抛出 OutOfMemoryException。

我还能如何编写此代码以使其更快并使用更少的内存?

注意:首选 java 8 解决方案。

注意 2:我使用 EntityManager。

您应该在 StringBuilder

中使用 concat() 而不是 +
    for(Object[] entityToCopy : entitiesToCopy){
    Entity entity= (Entity )entityToCopy[0];
    tmpValueForEntityId= lastEntryInEntityId ;
    queryString.append("(").append(lastEntryInEntityId++).append(",").append(entity.getProperty()).append(",").append(entity[1]).append(",").append(entity.getProperty2()).append("),");

    for(Entity2 entity2 : entity.getEntity2Collection()){
        queryString2.append("(").append(lastEntryInEntity2Id ++).append(",").append(tmpValueForEntityId).append(",").append(entity.getProperty2()).append(",").append(entity.getProperty3()).append("),");
    }
}

为了更好的性能,在交易中使用PreparedStatement

dbCon.setAutoCommit(false);
    var pst = dbCon.prepareStatement("insert into entity (columnID, column_1, column_2, column_3) values (?, ?, ?, ?)";
for(Object[] entityToCopy : entitiesToCopy){
   var entity = (Entity )entityToCopy[0];
   tmpValueForEntityId = lastEntryInEntityId;
   pst.setInt(1, lastEntryInEntityId);
   pst.setString(2, entity.getProperty());
   pst.setString(3, entity[1]);
   pst.setString(4, entity.getProperty2());
   pst.addBatch();
}
pst.executeBatch();
dbCon.commit();
dbCon.setAutoCommit(true);

每个?代表一列。第一个代表ID,第二个代表column_1,依此类推。保持每一个的顺序。

注意:如果您使用的是 1.10 之前的 Java,请将 var 更改为 PreparedStatement


并发连接(多个线程插入数据库):

  1. 提交后不要关闭数据库连接(在程序退出时关闭)
  2. 插入数据的方法应该是synchronized
  3. 不要使用 prepareStatement(),而是使用 createStatement()Pattern(正则表达式)以避免 SQL 注入。

注意:PreparedStatement很好,又快又安全。

数据库保留了一个准备好的语句池,以避免每次都创建新的。但同时,在一个线程引用现有语句 -> PreparedStatement 后,另一个线程可以使用它并且事务处理很慢(等待新实例或对现有语句的新引用)。同时发生了很多很多次。


EntityManager 示例:

    var em = emf.createEntityManager();
EntityTransaction transaction = null;
try {
    transaction = em.getTransaction();
    transaction.begin();

    for(Object[] entityToCopy : entitiesToCopy){
         var entity = (Entity )entityToCopy[0];
         ...//insert here
    }

    tx.commit();
} catch (RuntimeException e) {
    if (transaction != null && transaction.isActive()) {
         tx.rollback();
         e.printStackTrace();
    }
} finally {
    em.close();
}

我每 x 次迭代执行一次查询,这样查询就不会变得太大。这解决了我的问题。

int count = 0;
for(Object[] entityToCopy : entitiesToCopy){
   Entity entity= (Entity )entityToCopy[0];
   tmpValueForEntityId= lastEntryInEntityId ;
   queryString.append("("+ lastEntryInEntityId ++ 
      +","+entity.getProperty()+","+entity[1]+","+entity.getProperty2()+"),");

    for(Entity2 entity2 : entity.getEntity2Collection()){
        queryString2.append("("+lastEntryInEntity2Id ++ 
  +","+tmpValueForEntityId+","+entity.getProperty2()+","+entity.getProperty3()+"),");
    }
   count++;
   if(count%2000 == 0 || entitiesToCopy.size() == count){
       em.executeQuery(queryString);
       queryString = "";
       em.executeQuery(queryString2);
       queryString2 = "";
   }
}