1NF
Tout d'abord, je pense que la conception de votre table est mauvaise car elle n'est pas 1NF conforme. Chaque champ ne devrait contenir que des attributs atomiques, mais ce n'est pas le cas. Pourquoi pas un tableau comme :
CREATE TABLE my_table (
id,
ip inet,
port int
)
Dónde id
est le numéro de votre ligne dans le fichier source et ip
/ port
une des adresses de cette ligne ? Exemple de données :
id | ip | port
-----------------------
1 | 10.10.10.1 | 80
1 | 10.10.10.2 | 443
2 | 10.10.10.3 | 8080
2 | 10.10.10.4 | 4040
...
Ainsi, vous pourrez interroger votre base de données sur une seule adresse (trouver toutes les adresses associées, retourner vrai si deux adresses sont sur la même ligne, tout ce que vous voulez...).
Charger les données
Mais supposons que vous savez ce que vous faites. Le principal problème ici est que votre fichier de données d'entrée est dans un format spécial. Il peut s'agir d'un fichier CSV à une seule colonne, mais ce serait un fichier CSV très dégénéré. Quoi qu'il en soit, vous devez transformer les lignes avant de les insérer dans la base de données. Vous avez deux possibilités :
- vous lisez chaque ligne du fichier d'entrée et vous faites un
INSERT
(cela peut prendre un certain temps) ;
- vous convertissez le fichier d'entrée en un fichier texte avec le format attendu et utilisez
COPY
.
Insérer un par un
La première option semble facile : pour la première ligne du fichier csv, {(10.10.10.1,80),(10.10.10.2,443)}
vous devez exécuter la requête :
INSERT INTO my_table VALUES (ARRAY[('10.10.10.1',80),('10.10.10.2',443)]::address[], 4)
Pour ce faire, il suffit de créer une nouvelle chaîne :
String value = row.replaceAll("\\{", "ARRAY[")
.replaceAll("\\}", "]::address[]")
.replaceAll("\\(([0-9.]+),", "'$1'");
String sql = String.format("INSERT INTO my_table VALUES (%s)", value);
Et exécutez la requête pour chaque ligne du fichier d'entrée (ou pour une meilleure sécurité, utilisez un fichier déclaration préparée ).
Insertion avec COPY
Je vais développer la deuxième option. Vous devez l'utiliser dans le code Java :
copyManager.copyIn(sql, from);
Où la requête de copie est une COPY FROM STDIN
et from
est un lecteur. La déclaration sera :
COPY my_table (addresses) FROM STDIN WITH (FORMAT text);
Pour alimenter le copy manager, vous avez besoin de données comme (notez les guillemets) :
{"(10.10.10.1,80)","(10.10.10.2,443)"}
{"(10.10.10.3,8080)","(10.10.10.4,4040)"}
Avec un fichier temporaire
La façon la plus simple d'obtenir les données dans le bon format est de créer un fichier temporaire. Vous lisez chaque ligne du fichier d'entrée et remplacez (
par "(
y )
par )"
. Écrivez cette ligne traitée dans un fichier temporaire. Passez ensuite un lecteur sur ce fichier au gestionnaire de copie.
A la volée
Avec deux fils Vous pouvez utiliser deux fils :
-
Le thread 1 lit le fichier d'entrée, traite les lignes une par une et les écrit dans un fichier de type PipedWriter
.
-
Le fil 2 passe un PipedReader
connecté à l'ancien PipedWriter
au responsable de la copie.
La principale difficulté consiste à synchroniser les threads de manière à ce que le thread 2 commence à lire le code de l'utilisateur. PipedReader
avant que le thread 1 ne commence à écrire des données dans le PipedWriter
. Voir ce projet qui est le mien pour un exemple.
Avec un lecteur personnalisé Le site from
Le lecteur pourrait être une instance de quelque chose comme (version naïve) :
class DataReader extends Reader {
PushbackReader csvFileReader;
private boolean wasParenthese;
public DataReader(Reader csvFileReader) {
this.csvFileReader = new PushbackReader(csvFileReader, 1);
wasParenthese = false;
}
@Override
public void close() throws IOException {
this.csvFileReader.close();
}
@Override
public int read(char[] cbuf, int off, int len) throws IOException {
// rely on read()
for (int i = off; i < off + len; i++) {
int c = this.read();
if (c == -1) {
return i-off > 0 ? i-off : -1;
}
cbuf[i] = (char) c;
}
return len;
}
@Override
public int read() throws IOException {
final int c = this.csvFileReader.read();
if (c == '(' && !this.wasParenthese) {
this.wasParenthese = true;
this.csvFileReader.unread('(');
return '"'; // add " before (
} else {
this.wasParenthese = false;
if (c == ')') {
this.csvFileReader.unread('"');
return ')'; // add " after )
} else {
return c;
}
}
}
}
(Il s'agit d'une version naïve, car la bonne façon de procéder serait de remplacer seulement public int read(char[] cbuf, int off, int len)
. Mais vous devez ensuite traiter le cbuf
pour ajouter les guillemets et stocker les caractères supplémentaires poussés vers la droite : c'est un peu fastidieux). Maintenant, si r
est le lecteur du fichier :
{(10.10.10.1,80),(10.10.10.2,443)}
{(10.10.10.3,8080),(10.10.10.4,4040)}
Il suffit d'utiliser :
Class.forName("org.postgresql.Driver");
Connection connection = DriverManager
.getConnection("jdbc:postgresql://db_host:5432/db_base", "user", "passwd");
CopyManager copyManager = connection.unwrap(PGConnection.class).getCopyAPI();
copyManager.copyIn("COPY my_table FROM STDIN WITH (FORMAT text)", new DataReader(r));
Sur le chargement en vrac
Si vous chargez une énorme quantité de données, n'oubliez pas les conseils de base : désactivez l'autocommit, supprimez les index et les contraintes, et utilisez la fonction TRUNCATE
y ANALYZE
comme suit :
TRUNCATE my_table;
COPY ...;
ANALYZE my_table;
Cela accélérera le chargement.