2016-01-12 14 views
6

Tôi đã đọc tệp và tạo một đối tượng từ nó và lưu trữ vào cơ sở dữ liệu postgresql. Tệp của tôi có 100.000 tài liệu mà tôi đọc từ một tệp và chia nhỏ tệp đó và cuối cùng lưu trữ vào cơ sở dữ liệu. Tôi không thể tạo List<> và lưu trữ tất cả tài liệu trong List<> vì RAM của tôi ít. Mã của tôi để đọc và ghi vào cơ sở dữ liệu như sau. Nhưng My JVM Heap đầy và không thể tiếp tục lưu trữ thêm tài liệu. Cách đọc tệp và lưu trữ cơ sở dữ liệu hiệu quả.Cách chèn dữ liệu càng nhanh càng tốt với Hibernate

public void readFile() { 
    StringBuilder wholeDocument = new StringBuilder(); 
    try { 
     bufferedReader = new BufferedReader(new FileReader(files)); 
     String line; 
     int count = 0; 
     while ((line = bufferedReader.readLine()) != null) { 
      if (line.contains("<page>")) { 
       wholeDocument.append(line); 
       while ((line = bufferedReader.readLine()) != null) { 
        wholeDocument = wholeDocument.append("\n" + line); 
        if (line.contains("</page>")) { 
         System.out.println(count++); 
         addBodyToDatabase(wholeDocument.toString()); 

         wholeDocument.setLength(0); 
         break; 
        } 
       } 
      } 
     } 
     wikiParser.commit(); 
    } catch (FileNotFoundException e) { 
     e.printStackTrace(); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } finally { 
     try { 
      bufferedReader.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 
} 

public void addBodyToDatabase(String wholeContent) { 
    Page page = new Page(new Timestamp(System.currentTimeMillis()), 
      wholeContent); 
    database.addPageToDatabase(page); 
} 

public static int counter = 1; 

public void addPageToDatabase(Page page) { 
    session.save(page); 
    if (counter % 3000 == 0) { 
     commit(); 
    } 
    counter++; 
} 
+0

Có lẽ bạn cần thêm 'StringBuilder wholeDocument = StringBuilder mới(); 'đâu đó bên trong vòng lặp của bạn –

+1

bằng cách này, StringBuilder được có thể thay đổi, bạn không cần phải làm điều này' wholeDocument = wholeDocument.append (" \ n "+ dòng);', chỉ sử dụng 'wholeDocument.append (" \ n "+ dòng);' –

+0

'commit()' làm gì? –

Trả lời

1

Tôi sử dụng câu trả lời @RookieGuy. stackoverflow.com/questions/14581865/hibernate-commit-and-flush

tôi sử dụng

session.flush(); 
session.clear(); 

và cuối cùng sau khi đọc tất cả các tài liệu và lưu trữ chúng vào cơ sở dữ liệu

tx.commit(); 
session.close(); 

và thay đổi

wholeDocument = wholeDocument.append("\n" + line); 

để

wholeDocument.append("\n" + line); 
+0

Tôi nghĩ bạn đã trả lời câu hỏi của bạn. Và với tôi câu trả lời sẽ giải quyết vấn đề của bạn. Nếu nó giải quyết vấn đề của bạn, bạn có thể muốn chấp nhận câu trả lời của riêng bạn. – Atul

0

Tôi không chắc chắn lắm về cấu trúc tệp dữ liệu của bạn. Sẽ dễ hiểu nếu bạn có thể cung cấp mẫu tệp của mình.

Nguyên nhân gốc của mức tiêu thụ bộ nhớ là cách đọc/lặp lại tệp. Khi một cái gì đó được đọc, vẫn còn trong bộ nhớ. Bạn nên sử dụng hoặc là java.io.FileInputStream hoặc org.apache.commons.io.FileUtils.

Dưới đây là một số mẫu mã để lặp với java.io.FileInputStream

try (
     FileInputStream inputStream = new FileInputStream("/tmp/sample.txt"); 
     Scanner sc = new Scanner(inputStream, "UTF-8") 
) { 
    while (sc.hasNextLine()) { 
     String line = sc.nextLine(); 
     addBodyToDatabase(line); 
    } 
} catch (FileNotFoundException e) { 
    e.printStackTrace(); 
} catch (IOException e) { 
    e.printStackTrace(); 
} 

Dưới đây là một số mẫu mã để lặp với org.apache.commons.io.FileUtils

File file = new File("/tmp/sample.txt"); 
LineIterator it = FileUtils.lineIterator(file, "UTF-8"); 
try { 
    while (it.hasNext()) { 
     String line = it.nextLine(); 
     addBodyToDatabase(line); 
    } 
} finally { 
    LineIterator.closeQuietly(it); 
} 
0

Bạn nên bắt đầu một giao dịch, thực hiện tiết kiệm hoạt động và cam kết một giao dịch . (Đừng bắt đầu một giao dịch sau khi lưu!). Bạn có thể thử sử dụng StatelessSession để loại trừ mức tiêu thụ bộ nhớ bằng bộ nhớ cache.

Và sử dụng nhiều giá trị ít hơn, cho một ví dụ 20, trong mã này

if (counter % 20 == 0) 

Bạn có thể cố gắng để vượt qua StringBuilder như là đối số của một phương pháp càng nhiều càng tốt.

8

Trước hết, bạn nên áp dụng cách tiếp cận fork-join tại đây.

Tác vụ chính phân tích tệp và gửi lô tối đa 100 mục đến một số ExecutorService. Các ExecutorService nên có một số chủ đề công nhân bằng với số lượng các kết nối cơ sở dữ liệu có sẵn. Nếu bạn có 4 lõi CPU, giả sử rằng cơ sở dữ liệu có thể lấy 8 kết nối đồng thời mà không làm nhiều chuyển đổi ngữ cảnh.

Bạn nên định cấu hình connection poolingDataSource và có một minSize bằng maxSize và bằng 8. Hãy thử HikariCP hoặc ViburDBCP để kết nối tổng hợp.

Sau đó, bạn cần định cấu hình JDBC batching. Nếu bạn đang sử dụng MySQL, trình tạo IDENTITY sẽ vô hiệu hóa việc tắm. Nếu bạn đang sử dụng cơ sở dữ liệu hỗ trợ trình tự, hãy đảm bảo bạn cũng sử dụng trình tạo mã định danh nâng cao (chúng là tùy chọn mặc định trong Hibernate 5.x).

Bằng cách này, quá trình chèn thực thể được song song và tách riêng của chuỗi phân tích cú pháp chính. Các chủ đề chính nên chờ đợi cho các ExecutorService để kết thúc xử lý tất cả các nhiệm vụ trước khi tắt.

2

Thực sự rất khó để đề xuất cho bạn mà không làm hồ sơ thực sự và tìm hiểu điều gì làm cho mã của bạn chậm hoặc không hiệu quả.

Tuy nhiên có một vài điều chúng ta có thể nhìn thấy từ mã của bạn

  1. Bạn đang sử dụng StringBuilder không hiệu quả

    wholeDocument.append("\n" + line); nên được viết như wholeDocument.append("\n").append(line); thay

    Bởi vì những gì bạn gốc wrote sẽ được dịch theo trình biên dịch đến whileDocument.append(new StringBuilder("\n").append(line).toString()). Bạn có thể xem có bao nhiêu StringBuilder s không cần thiết bạn đã tạo ra :)

  2. cân nhắc trong việc sử dụng Hibernate

    Tôi không chắc chắn làm thế nào bạn quản lý bạn session hoặc làm thế nào bạn thực hiện commit() của bạn, tôi giả sử bạn đã làm điều đó đúng, vẫn còn nhiều điều cần xem xét:

    • Bạn đã thiết lập đúng kích thước lô trong Hibernate chưa? (hibernate.jdbc.batch_size) Theo mặc định, kích thước lô JDBC là khoảng 5. Bạn có thể muốn chắc chắn rằng bạn đặt nó ở kích thước lớn hơn (để Hibernate nội bộ sẽ gửi chèn vào một lô lớn hơn).

    • Cho rằng bạn không cần các đối tượng trong bộ nhớ cache cấp 1 để sử dụng sau, bạn có thể muốn làm phiên liên tục flush() + clear() để

      1. chèn kích hoạt hàng loạt nêu tại điểm trước
      2. rõ ràng ra mức độ đầu tiên bộ nhớ cache
  3. Chuyển xa Hibernate cho tính năng này.

    Hibernate rất tuyệt nhưng không phải là thuốc chữa bách bệnh cho mọi thứ. Cho rằng trong tính năng này bạn chỉ cần lưu các bản ghi vào DB dựa trên nội dung tập tin văn bản. Bạn không cần bất kỳ hành vi thực thể nào, cũng như bạn không cần phải sử dụng bộ nhớ cache cấp đầu tiên để xử lý sau này, không có nhiều lý do để sử dụng Hibernate ở đây do quá trình xử lý bổ sung và không gian trên không. Chỉ cần thực hiện JDBC với việc xử lý hàng loạt thủ công sẽ giúp bạn tiết kiệm rất nhiều rắc rối.

Các vấn đề liên quan