Вы могли попробовать PgBulkInsert, который осуществляет Двойной Протокол Копии PostgreSQL:
Это также доступно от Центрального репозитория Знатока.
Отказ от ответственности: Я - автор проекта.
Набор из двух предметов PostgreSQL копирует протокол
Я не хочу просто рекламировать свой проект, но также и писать о протоколе.
First of all I have written a class PgBinaryWriter
, which wraps a DataOutputStream
and has methods for writing the Binary Protocol Header, a method to start a new row (the Binary Copy Protocol requires you to write the number of columns for each row you are going to insert) and a write
method, which takes an IValueHandler
for writing a given Java type.
PgBinaryWriter
осуществляет AutoClosable
, потому что необходимо написать -1
к потоку прежде, чем смыть и закрыть поток.
The IValueHandler
takes a DataOutputStream
and a value. It is responsible for writing the given value with the PostgreSQL Binary Protocol Format.
PgBinaryWriter
// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.pgsql;
import de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.exceptions.BinaryWriteFailedException;
import de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.pgsql.handlers.IValueHandler;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.OutputStream;
public class PgBinaryWriter implements AutoCloseable {
/** The ByteBuffer to write the output. */
private transient DataOutputStream buffer;
public PgBinaryWriter() {
}
public void open(final OutputStream out) {
buffer = new DataOutputStream(new BufferedOutputStream(out));
writeHeader();
}
private void writeHeader() {
try {
//11 bytes required header
buffer.writeBytes("PGCOPY\n\377\r\n\0");
//32 bit integer indicating no OID
buffer.writeInt(0);
//32 bit header extension area length
buffer.writeInt(0);
} catch(Exception e) {
throw new BinaryWriteFailedException(e);
}
}
public void startRow(int numColumns) {
try {
buffer.writeShort(numColumns);
} catch(Exception e) {
throw new BinaryWriteFailedException(e);
}
}
public void write(final IValueHandler handler, final TTargetType value) {
handler.handle(buffer, value);
}
@Override
public void close() {
try {
buffer.writeShort(-1);
buffer.flush();
buffer.close();
} catch(Exception e) {
throw new BinaryWriteFailedException(e);
}
}
}
ValueHandler
IValueHandler
является простым интерфейсом, у которого есть ручка
метод, чтобы взять DataOutputStream
и стоимость.
// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.pgsql.handlers;
import java.io.DataOutputStream;
import java.lang.reflect.Type;
public interface IValueHandler extends ValueHandler {
void handle(DataOutputStream buffer, final TTargetType value);
Type getTargetType();
}
Важно знать о протоколе, что необходимо написать -1
, когда стоимость пустая. Для этого я написал абстрактный базовый класс, который обращается со случаем.
// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.pgsql.handlers;
import de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.exceptions.BinaryWriteFailedException;
import java.io.DataOutputStream;
public abstract class BaseValueHandler implements IValueHandler {
@Override
public void handle(DataOutputStream buffer, final T value) {
try {
if (value == null) {
buffer.writeInt(-1);
return;
}
internalHandle(buffer, value);
} catch (Exception e) {
throw new BinaryWriteFailedException(e);
}
}
protected abstract void internalHandle(DataOutputStream buffer, final T value) throws Exception;
}
Тогда укладчики для различных Явских типов могут осуществленный. Вот является пример для долго
. Можно найти
другие внедрения в хранилище GitHub (укладчики).
// Copyright (c) Philipp Wagner. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
package de.bytefish.PgBulkInsert.de.bytefish.PgBulkInsert.pgsql.handlers;
import java.io.DataOutputStream;
import java.lang.reflect.Type;
public class LongValueHandler extends BaseValueHandler {
@Override
protected void internalHandle(DataOutputStream buffer, final Long value) throws Exception {
buffer.writeInt(8);
buffer.writeLong(value);
}
@Override
public Type getTargetType() {
return Long.class;
}
}
Используя PgBinaryWriter
Теперь это наконец приходит к соединению частей. Пожалуйста, обратите внимание на то, что я резюмировал еще некоторые части. Это могло бы быть необходимо для поиска больше деталей внедрения в коде.
public abstract class PgBulkInsert {
//...
public void saveAll(PGConnection connection, Stream entities) throws SQLException {
CopyManager cpManager = connection.getCopyAPI();
CopyIn copyIn = cpManager.copyIn(getCopyCommand());
int columnCount = columns.size();
try (PgBinaryWriter bw = new PgBinaryWriter()) {
//Wrap the CopyOutputStream in our own Writer:
bw.open(new PGCopyOutputStream(copyIn));
//Insert all entities:
entities.forEach(entity -> {
//Start a New Row:
bw.startRow(columnCount);
//Insert the Column Data:
columns.forEach(column -> {
try {
column.getWrite().invoke(bw, entity);
} catch (Exception e) {
throw new SaveEntityFailedException(e);
}
});
});
}
}
private String getCopyCommand()
{
String commaSeparatedColumns = columns.stream()
.map(x -> x.columnName)
.collect(Collectors.joining(", "));
return String.format("COPY %1$s(%2$s) FROM STDIN BINARY",
table.GetFullQualifiedTableName(),
commaSeparatedColumns);
}
}
PgBulkInsert
PgBulkInsert поддерживает следующие типы данных PostgreSQL.
Основное использование
Предположите, что большое количество людей должно быть большой частью, вставленной в базу данных PostgreSQL. Каждый у Человека
есть имя, фамилия и дата рождения.
Таблица базы данных
Стол в базе данных PostgreSQL мог бы быть похожим на это:
CREATE TABLE sample.unit_test
(
first_name text,
last_name text,
birth_date date
);
Модель предметной области
The Модель предметной области in the application might look like this:
private class Person {
private String firstName;
private String lastName;
private LocalDate birthDate;
public Person() {}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
}
Оптовая вставка
Then you have to implement the PgBulkInsert
, which defines the mapping between the table and the Модель предметной области.
public class PersonBulkInserter extends PgBulkInsert
{
public PersonBulkInserter() {
super("sample", "unit_test");
MapString("first_name", Person::getFirstName);
MapString("last_name", Person::getLastName);
MapDate("birth_date", Person::getBirthDate);
}
}
Using the Оптовая вставка
И наконец мы можем написать Тест Единицы, чтобы вставить 100000
Люди в базу данных. Можно найти весь Тест Единицы на GitHub: IntegrationTest.java.
@Test
public void bulkInsertPersonDataTest() throws SQLException {
//Create a large list of Persons:
List persons = getPersonList(100000);
//Create the BulkInserter:
PersonBulkInserter personBulkInserter = new PersonBulkInserter();
//Now save all entities of a given stream:
personBulkInserter.saveAll(PostgreSqlUtils.getPGConnection(connection), persons.stream());
//And assert all have been written to the database:
Assert.assertEquals(100000, getRowCount());
}
private List getPersonList(int numPersons) {
List persons = new ArrayList<>();
for (int pos = 0; pos < numPersons; pos++) {
Person p = new Person();
p.setFirstName("Philipp");
p.setLastName("Wagner");
p.setBirthDate(LocalDate.of(1986, 5, 12));
persons.add(p);
}
return persons;
}