Categories
Database Development

PostgreSQL Docker-Compose Setup

Im vorletzten Post: PostgreSQL hatte ich beschrieben, wie ich zwei Bestandsdatenbanken analysiert und in eine PostgreSQL-DB in einem Docker Container gebracht habe, samt PGAdmin.

Jetzt möchte ich einen Schritt weiter gehen und das komplette Setup über ein Script starten können: DB, PGAdmin & SQL-Scripte. Dazu verwende ich Docker-Compose.

Ausgangslage

In PostgreSQL hatte ich bereits die zugrundeliegenden Docker-Images ermittelt: postgres:13.4-buster für die DB und dpage/pgadmin4 für PGAdmin. Inzwischen gibt es aber ein aktuelleres Image für die DB, das ich verwenden werde: postgres:13.5-bullseye

docker pull postgres:13.5-bullseye
docker pull dpage/pgadmin4

Für die SQL-Daten werde ich auf den Artikel PostgreSQL IDs zurückgreifen und daraus zwei Scripte machen, eines für das Schema der DB und eines mit den "Masterdaten" mit denen das Schema initial befüllt werden soll.

CREATE SEQUENCE object_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

CREATE TABLE person (
    object_id integer NOT NULL DEFAULT nextval('object_id_seq'::regclass),
    vorname varchar(255),
    nachname varchar(255),
    CONSTRAINT person_pkey PRIMARY KEY (object_id)
);

CREATE TABLE adresse (
    object_id integer NOT NULL DEFAULT nextval('object_id_seq'::regclass),
    strasse varchar(255),
    ort varchar(255),
    CONSTRAINT adresse_pkey PRIMARY KEY (object_id)
);
INSERT INTO person (vorname, nachname) VALUES ('Max', 'Mustermann');
INSERT INTO person (vorname, nachname) VALUES ('Peter', 'Person');
INSERT INTO person (vorname, nachname) VALUES ('Donald', 'Demo');

INSERT INTO adresse (strasse, ort) VALUES ('Beispielstrasse', 'Beispielstadt');
INSERT INTO adresse (strasse, ort) VALUES ('Erpelweg', 'Entenhausen');
INSERT INTO adresse (strasse, ort) VALUES ('Bruchstrasse', 'Berlin');

Auf der Seite von Docker Compose bringe ich in Erfahrung, dass die aktuelle Version von Docker Compose 3.9 ist und erstelle schon mal die Datei:

version: "3.9"  # optional since v1.27.0

Ordernstrucktur:

Images starten

Bisher habe ich PostgreSQL und PGAdmin über folgende Kommandos gestartet:

docker run --name myapp-db -p 5432:5432 -e POSTGRES_PASSWORD=PASSWORD -d postgres:13.4-buster
docker run --name myapp-pgadmin -p 80:80 -e PGADMIN_DEFAULT_EMAIL=admin@admin.com -e PGADMIN_DEFAULT_PASSWORD=admin -d dpage/pgadmin4

Diese Kommandos werden in ein Docker-Compose Script transferiert:

version: "3.9"  # optional since v1.27.0
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin

Jetzt können beide Images mit einem einfachen Befehl gestartet werden:

\myapp> docker-compose up
Creating network "myapp_default" with the default driver
Creating myapp_myapp-db_1      ... done
Creating myapp_myapp-pgadmin_1 ... done
Attaching to myapp_myapp-db_1, myapp_myapp-pgadmin_1

PGAdmin im Browser starten: http://localhost:80
Login wie bisher mit admin@admin.com / admin

Im nächsten Schritt gibt es bereits eine entscheidende Änderung:
Während bisher beide Container isoliert nebeneinander liefen und nur über den Host-Rechner kommunizieren konnten, wurde durch Docker-Compose automatisch beim Start ein Netzwerk ("myapp_default") angelegt, in dem beide Container laufen. Außerdem sind beide Container über ihren Servicenamen ("myapp-db" & "myapp-pgadmin") erreichbar.

Dadurch muss nicht mehr die IP des Host-Rechners ermittelt werden (die sich manchmal ändert), sondern es kann der Name genommen werden:

Datenbank erstellen

In der PostgreSQL Instanz muss jetzt eine Datenbank erzeugt werden, in der die Anwendungsdaten gespeichert werden.

Hierzu gehen wir in den DB Container.
Allerdings ist der Name anders als bisher: Es wurde der Verzeichnisname als Präfix davor und eine 1 (für die 1. und in unserem Fall einzige Instanz) als Postfix dahinter gehangen und so lautet der Name : myapp_myapp-db_1

docker exec -it myapp_myapp-db_1 bash

Im Container erzeugen wir die DB:

su postgres
createdb myappdb
exit

So war es zumindest bisher, einfacher geht es mit Docker-Compose und dem Setzten der Environment-Variablen POSTGRES_DB wodurch die DB automatisch angelegt und verwendet wird.
Sicherlich hätte ich das auch bisher im Docker Kommando so nehmen können, aber im letzten Post hatte ich es zum einen mit zwei DBs zu tun und zum anderen musste ich eh auf die Kommandozeile um die DBs einzuspielen.

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myappdb
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin

Datenbank befüllen

Bisher war der Weg, die SQL-Dateien in den Container zu kopieren und von dort einzuspielen:

# Dateien in Container kopieren
docker cp database/Masterdata.sql myapp_myapp-db_1:/tmp
docker cp database/Schema.sql myapp_myapp-db_1:/tmp
# In Container wechseln
docker exec -it myapp_myapp-db_1 bash

Im Container SQLs einspielen:

su postgres
psql myappdb < /tmp/Schema.sql
psql myappdb < /tmp/Masterdata.sql
exit

Einfacher geht es über Docker-Compose und den Mechanismus, dass PostgreSQL automatisch die Dateien importiert, die im Verzeichnis /docker-entrypoint-initdb.d/ liegen. Und zwar in alphabetischer Reihenfolge.

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myappdb
    volumes:
      - ./database/Schema.sql:/docker-entrypoint-initdb.d/1-Schema.sql
      - ./database/Masterdata.sql:/docker-entrypoint-initdb.d/2-Masterdata.sql
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin

PGAdmin Einstellungen persistieren

Während der Entwicklung öfters mal die DB platt macht und komplett neu aufsetzt: Mit Docker ist das schnell gemacht. Mit Docker-Compose sind es jetzt nur noch zwei Befehle:

# stop and remove stopped containers
docker-compose down
# start containers
docker-compose up

Einen Nachteil gibt es allerdings: Die Einstellungen im PGAdmin gehen ebenfalls flöten und müssen neu eingegeben werden.
Die Lösung: Der PGAdmin Container bekommt ein persistentes Volume, das ein docker-compose down übersteht. Und wenn es doch mal neu aufgesetzt werden muss, ist das einfach über das -v Flag umsetzbar: docker-compose down -v

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myappdb
    volumes:
      - ./database/Schema.sql:/docker-entrypoint-initdb.d/1-Schema.sql
      - ./database/Masterdata.sql:/docker-entrypoint-initdb.d/2-Masterdata.sql
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    volumes:
      - pgadminvolume:/var/lib/pgadmin
volumes:
  pgadminvolume: {}

DB Image mit Schema und Masterdata

An Schema und Stammdaten wird sich erstmal nichts ändern. Daher wäre es gut, wenn beim Neubau der Container die DB bereits mit Schema und Stammdaten gestartet wird und nicht diese erst aufbauen muss.

Das wird dadurch erreicht, dass ein Image gebaut wird, dass die PostgreSQL DB sowie Schema und Stammdaten enthält.

Im Verzeichnis database wird eine Datei Dockerfile angelegt. Dieses Dockerfile enthält die Informationen zum Bau des DB Images.

FROM postgres:13.5-bullseye
 ENV POSTGRES_PASSWORD PASSWORD
 ENV POSTGRES_DB myappdb
COPY ./Schema.sql /docker-entrypoint-initdb.d/1-Schema.sql
COPY ./Masterdata.sql /docker-entrypoint-initdb.d/2-Masterdata.sql
version: "3.9"
services:
  myapp-db:
    build: ./database
    ports:
      - "5432:5432"
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    volumes:
      - pgadminvolume:/var/lib/pgadmin
volumes:
  pgadminvolume: {}

Gestartet wird wie gewohnt:

docker-compose up

Sollte es erforderlich sein, das Image neu zu bauen, zB wenn ich das Schema verändert hat:

docker-compose up --build
# or
docker-compose build

UPDATE: Das war leider nix mit dem vorgefüllten Image

Das Dockerfile enthält zwar den Schritt die SQL Dateien in das Image zu kopieren. Es fehlt aber der Schritt, bei dem diese Dateien in die Datenbank hineinmigriert werden. Das geschieht wie zuvor auch erst beim Starten des Containers. Mein Ziel, einen Image zu haben, dass diese Daten bereits enthält, habe ich damit also leider nicht erreicht. Das Dockerfile enthält keine Informationen, die nicht zuvor auch schon im Docker Compose enthalten waren.

Da mich das herumkaspern mit diesem Problem heute den ganzen Tag gekostet und abgesehen vom Erkenntnisgewinn leider nichts gebracht hat, kehre ich zur Docker Compose Variante zurück, bei der die DB beim Starten des Containers gebaut wird. Für das Beispiel auf dieser Seite macht das praktisch gesehen keinen Unterschied, da die Scripte winzig sind. Für mein Projekt leider schon, da benötigt der Aufbau der DB ein paar Minuten. Für die Anwendungsentwicklung, bei der ich die DB alle paar Tage mal neu aufsetze, ist das durchaus OK. Für Tests, die mit einer frischen DB starten sollen, die am Ende weggeschmissen wird, ist das schon ein Problem.

Also Rückkehr zu Docker Compose, diesmal mit einem extra Volume für die DB. Das kann ich dann gezielt löschen.

version: "3.9"
services:
  myapp-db:
    image: postgres:13.5-bullseye
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=PASSWORD
      - POSTGRES_DB=myapp
    volumes:
      - postgresvolume:/var/lib/postgresql/data
      - ./database/Schema.sql:/docker-entrypoint-initdb.d/1-Schema.sql
      - ./database/Masterdata.sql:/docker-entrypoint-initdb.d/2-Masterdata.sql
  myapp-pgadmin:
    image: dpage/pgadmin4
    ports:
      - "80:80"
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@admin.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    volumes:
      - pgadminvolume:/var/lib/pgadmin
volumes:
  postgresvolume: {}
  pgadminvolume: {}

Docker Befehle

# Create and start containers
docker-compose up
# Image neu bauen
docker-compose up --build
# or
docker-compose build

# Stop containers
docker-compose stop
# Start containers
docker-compose start

# Stop containers and remove them
docker-compose down
# Stop containers, remove them and remove volumes
docker-compose down -v

Categories
Database Development

PostgreSQL IDs

Angenommen, wir haben eine Tabelle mit Personen:

CREATE TABLE person (
	vorname varchar(255),
    nachname varchar(255)
);

Diese Personen sollen alle eine eindeutige ID bekommen und diese soll automatisch beim Einfügen generiert werden.

Sequenz

Dazu kann man eine Sequenz anlegen und aus dieser die ID befüllen:

CREATE SEQUENCE person_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

CREATE TABLE person (
    id integer NOT NULL DEFAULT nextval('person_id_seq'::regclass),
	vorname varchar(255),
    nachname varchar(255),
    CONSTRAINT person_pkey PRIMARY KEY (id)
);

Anschließend ein paar Personen hinzufügen:

INSERT INTO person (vorname, nachname) VALUES ('Max', 'Mustermann');
INSERT INTO person (vorname, nachname) VALUES ('Peter', 'Person');
INSERT INTO person (vorname, nachname) VALUES ('Donald', 'Demo');

Und anzeigen lassen:

SELECT * FROM person;

Identity

Da es etwas lästig ist, immer für jede Tabelle jeweils eine eigene Sequenz anlegen und mit der ID verknüpfen zu müssen, wurde die Frage an mich herangetragen, ob es da nicht soetwas wie autoincrement gäbe, wie man es von MySQL kennen würde.

Nach kurze Recherche fand sich, dass es soetwas natürlich auch für PostgreSQL gibt und zwar seit der Version 10 als "IDENTITY".

CREATE TABLE adresse (
    id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
	strasse varchar(255),
    ort varchar(255)
);

Anschließend ein paar Adressen hinzufügen:

INSERT INTO adresse (strasse, ort) VALUES ('Beispielstrasse', 'Beispielstadt');
INSERT INTO adresse (strasse, ort) VALUES ('Erpelweg', 'Entenhausen');
INSERT INTO adresse (strasse, ort) VALUES ('Bruchstrasse', 'Berlin');

Und anzeigen lassen:

SELECT * FROM adresse;

Der Vorteil der Identity, was man so auf den ersten Blick sieht, ist also, dass man sich etwas stumpfe Tipparbeit spart und keine Sequenz anlegen, mit der ID verknüpfen und die ID als Primary Key definieren muss.

Eine Betrachtung der Unterschiede zwischen SEQUENCE und IDENTITY habe ich leider nicht finden können.

Vermutlich gibt es da keine großen technischen Unterschiede, die IDENTITY scheint mir eine anonyme SEQUENCE zu sein.

Object ID Sequenz

Die IDENTITY kann man nur für einen TABLE nutzen, die SEQUENCE könnte man für mehrere Tabellen nutzen und so eine datenbankweite eindeutige ID verwenden.

Beispielsweise eine eindeutige Object ID Sequenz anlegen und für die Tabellen Person und Adresse verwenden:

CREATE SEQUENCE object_id_seq
    START WITH 1
    INCREMENT BY 1
    NO MINVALUE
    NO MAXVALUE
    CACHE 1;

CREATE TABLE person (
    object_id integer NOT NULL DEFAULT nextval('object_id_seq'::regclass),
	vorname varchar(255),
    nachname varchar(255),
    CONSTRAINT person_pkey PRIMARY KEY (object_id)
);

CREATE TABLE adresse (
    object_id integer NOT NULL DEFAULT nextval('object_id_seq'::regclass),
	strasse varchar(255),
    ort varchar(255),
    CONSTRAINT adresse_pkey PRIMARY KEY (object_id)
);

Anschließend ein paar Personen und Adressen hinzufügen:

INSERT INTO person (vorname, nachname) VALUES ('Max', 'Mustermann');
INSERT INTO person (vorname, nachname) VALUES ('Peter', 'Person');
INSERT INTO person (vorname, nachname) VALUES ('Donald', 'Demo');

INSERT INTO adresse (strasse, ort) VALUES ('Beispielstrasse', 'Beispielstadt');
INSERT INTO adresse (strasse, ort) VALUES ('Erpelweg', 'Entenhausen');
INSERT INTO adresse (strasse, ort) VALUES ('Bruchstrasse', 'Berlin');

Und anzeigen lassen:

SELECT * FROM person;
SELECT * FROM adresse;

Wie man sehen kann, wurde die ID fortlaufend über beide Tabellen vergeben. Dadurch erhält man eine datenbankweit eindeutige, fortlaufende ID.

UUID

Und weil ich grade schon dabei bin: Seit Version 13 bringt PostgreSQL auch standartmäßig die Möglichkeit einer UUID mit.

Eine UUID ist ein Universally Unique Identifier.
Manchmal, typischerweise in Zusammenhang mit Microsoft, wird auch der Ausdruck GUID Globally Unique Identifier verwendet.

SELECT * FROM gen_random_uuid ();

UUIDs sind ebenfalls datenbankweit (und darüber hinaus) eindeutig. Allerdings sind die IDs nicht mehr fortlaufend.

UUIDs sind etwas langsamer als Sequenzen und verbrauchen etwas mehr Speicher.

Das Beispiel von vorhin:

CREATE TABLE person (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
	vorname varchar(255),
    nachname varchar(255)
);

CREATE TABLE adresse (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
	strasse varchar(255),
    ort varchar(255)
);

Anschließend ein paar Personen und Adressen hinzufügen:

INSERT INTO person (vorname, nachname) VALUES ('Max', 'Mustermann');
INSERT INTO person (vorname, nachname) VALUES ('Peter', 'Person');
INSERT INTO person (vorname, nachname) VALUES ('Donald', 'Demo');

INSERT INTO adresse (strasse, ort) VALUES ('Beispielstrasse', 'Beispielstadt');
INSERT INTO adresse (strasse, ort) VALUES ('Erpelweg', 'Entenhausen');
INSERT INTO adresse (strasse, ort) VALUES ('Bruchstrasse', 'Berlin');

Und anzeigen lassen:

SELECT * FROM person;
SELECT * FROM adresse;

Categories
Database Development

PostgreSQL

Für die Neu- und Weiterentwicklung einer Anwendung habe ich zur Analyse die Bestandsanwendung samt Datenbanken bekommen.
Für die Analyse musste ich zunächst die Datenbanken zum laufen bekommen und uA mit einem DB-Client einsehen.

Ich habe zum einen eine Datei dump.backup bekommen. Zunächst musste ich herausfinden, um was für eine Datei es sich dabei handelt, dazu nutzte ich das Linux Tool file:

apt update
apt install file
file /tmp/dump.backup
# /tmp/dump.backup: PostgreSQL custom database dump - v1.14-0

Es handelt sich also um einen Dump einer PostgreSQL Datenbank. Und im Dump konnte ich eine Versionsnummer 13.0 finden.

Die andere Datei mydb.sql.gz beinhaltet eine gezippte Version eines SQL Exports einer PostgreSQL DB Version 13.2 von einem Debian 13.2 Server.

Im Laufe der weiteren Analyse der DB Exporte stellte sich heraus, dass der dump.backup die PostGIS Erweiterung der PostgreSQL DB benötigt, welche mit installiert werden muss.

PostgreSQL Datenbank Docker Image

Ich werde beide Datenbanken in einer Docker Version installieren, dazu werde ich eine DB Instanz starten, in der beide DBs installiert werden.

Die Docker Seite für Postgres: Postgres - Official Image | Docker Hub

Da es sich um die Versionsnummer 13.0 und 13.2 handelt, werde ich ein aktuelles Image von Version 13 verwenden, was zum Projektzeitpunkt Version 13.4 war.

Da zumindest eine der beiden DBs auf einem Debian System gehostet ist, werde ich ein Debian Image wählen.

Die Wahl des Images fällt also auf: postgres:13.4-buster

Datenbank installieren

Zuerst das Docker Image ziehen:

docker pull postgres:13.4-buster

Datenbank starten:

docker run --name myapp-db -p 5432:5432 -e POSTGRES_PASSWORD=PASSWORD -d postgres:13.4-buster

DB-Dateien in den laufenden Docker Container kopieren:

docker cp mydb.sql.gz myapp-db:/tmp
docker cp dump.backup myapp-db:/tmp

In den laufenden Container wechseln:

docker exec -it myapp-db bash

PostGIS installieren:

apt update
apt install -y postgresql-13-postgis-3-scripts

DB von Dump erstellen:

su postgres
createdb mydb_dump
pg_restore -d mydb_dump -v /tmp/dump.backup
exit

DB von SQL erstellen:

su postgres
cd /tmp
gunzip mydb.sql.gz
createdb mydb_sql
pg_restore -d mydb_sql -v /tmp/mydb.sql
exit

Die Datenbanken sind installiert und es kann mittels eines letzten exit der Container verlassen werden.

DB Client PGAdmin installieren

Um in die Datenbanken hinein sehen zu können wird ein Client Programm benötigt. Sicherlich es gibt da bereits etwas auf der CommandLine Ebene:

su postgres
psql mydb_sql

Übersichtlich oder komfortabel ist das aber nicht. Daher möchte ich ein Tool mit einer grafischen Oberfläche verwenden. Die Wahl fiel auf pgAdmin, welches sich leicht von einem Docker Image installieren und anschließend über den Browser bedienen lässt.

Zuerst das Docker Image ziehen:

docker pull dpage/pgadmin4

pgAdmin parametrisiert starten:

docker run --name myapp-pgadmin -p 80:80 -e PGADMIN_DEFAULT_EMAIL=admin@admin.com -e PGADMIN_DEFAULT_PASSWORD=admin -d dpage/pgadmin4

Login:
* Email: admin@admin.com
* Passwort: admin

Port: 80

Der pgAdmin ist über den Browser aufrufbar: http://localhost/

Für die Konfiguration für die Verbindung zur zuvor gestarteten PostgreSQL Datenbank benötige ich die IP meines Rechners, die ich mittels ipconfig herausfinden kann.

Categories
Development Java

Tomcat Start beschleunigen

Das Starten des Tomcat-Servers hat für ein Projekt sehr lange gedauert. Im Eclipse kann man die Zeit bis zum Timeout hoch setzen, den Nerven des Entwicklers hilft das aber nur bedingt.

Eine Ursache für die lange Startzeit liegt darin, dass der Tomcat-Server beim Start alle jar-Files nach Taglibs durchsucht. Das Projekt hat sehr viele Libraries.

Eine Abhilfe schafft hier die Konfiguration, dass Tomcat keine jar-Files scannen soll, außer denen, in denen eine Taglib enthalten ist.

Wie man Jars mit Taglibs findet

Tomcat kann anzeigen lassen, welche Jars, die beim Start gescannt werden, Taglibs enthalten. Dazu muss das entsprechende Log-Level gesetzt werden.

In meinem Fall musste ich lediglich die logging.properties aus dem Original-Tomcat Verzeichnis in das Verzeichnis des Eclipse Tomcats kopieren:

Am Ende der logging.properties das Log-Level für den TLDScanner setzen:

[...]

org.apache.jasper.compiler.TldLocationsCache.level = FINE
org.apache.jasper.servlet.TldScanner.level = FINE

In den VM Arguments des Tomcats muss der Pfad zur logging.properties angegeben werden:

Beim Start wird jetzt angezeigt, in welchen JARs TLDs zu gefunden wurden.

Wie nur noch ausgewählte JARs gescannt werden

In aktuellen Projekt wurden folgende JARs mit TLDs gefunden:

  • standard-1.1.2.jar
  • jstl-1.2.jar
  • jsf-impl-2.2.20.jar
  • tomahawk20-1.1.14.jar

Die Konfiguration des JARScanFilters für den Tomcat Server erfolgt in der catalina.properties Datei.

Bei den jarsToSkip lasse ich alle (*.jar) skippen.

Bei den jarsToScan füge ich obige JARs hinzu:

Alleine durch diese Konfigurationsänderung konnte die Startzeit von 25 Sekunden auf 10 Sekunden reduziert werden.

JarScanner Konfiguration im Projekt

Der obige Weg beschreibt die Konfigurationsänderung im Server. Das hat den Nachteil, dass jede Serverinstanz diese Konfiguration gesetzt bekommen muss. Eine Konfiguration im Projekt selbst hat den Vorteil, dass zB alle Mitentwickler direkt mit profitieren können und nicht erst die Konfiguration selbst setzen müssen.

Die Konfiguration im Projekt erfolgt über context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
  <JarScanner>
    <JarScanFilter tldScan="standard-1.1.2.jar,jstl-1.2.jar,jsf-impl-2.2.20.jar,tomahawk20-1.1.14.jar" 
                   defaultTldScan="false" 
                   defaultPluggabilityScan="false"/>
  </JarScanner>
</Context>
Categories
Development

Icons

Was mir in dem Umfang bisher noch nicht wirklich klar war: Man kann für kleine Icons und dergleichen auch Unicode Zeichen verwenden und braucht so keine Grafiken suchen, bearbeiten oder sich um Copyright Gedanken machen 👏.

Ein Nachteil ist allerdings, dass man die konkrete Darstellung nicht mehr in der Hand hat und die Unicodes in unterschiedlichen Browsern oder Betriebssystemen unterschiedlich aussehen können. In meinem geliebten Notepad++ beispielsweise werden die klatschenden Hände 👏 lediglich in Schwarz-Weiß dargestellt.

Die Unicode Zeichen müssen natürlich erstmal gefunden werden, dafür eignen sich Seiten wie CodePoins oder Unicode-Table.

Dann gibt es auch noch Icon-Bibliotheken wie PrimeIcons oder die vielleicht bekannteste Sammlung Font Awesome.

Darüber hinaus gibt es weitere Möglichkeiten und Bibliotheken, eine interessante Suchmaschine dazu ist GlyphSearch.

Wenn Favicons benötigt werden, kann man diese bequem auf favicon.io online generieren lassen.

Categories
Development

GitHub Personal access token

Die Basic authentication wurde bei GitHub abgeschaltet.

Ein Commit mit Username & Password ist somit nicht mehr möglich. Statt dessen wird der Username & Personal access token benötigt.

Wie man einen Personal access token generiert und über die Command Line verwendet habe ich bereits hier dokumentiert.

Heute habe ich für eine Code Anpassung mit Eclipse gearbeitet und bin in diese Abschaltungsfalle gerannt. Die Generierung des Personal access tokens war leicht, ich brauchte lediglich meiner eigenen Doku folgen.

Schwieriger (nerviger) war es, Eclipse beizubringen, dass anstelle des alten, gespeicherten Passworts ein neues verwendet werden soll. (der zuvor generierte Token)

Die erste Version, mit der ich erfolgreich war:

How do I change my git credentials in eclipse?

Go to the Git Perspective -> Expand your Project -> Expand Remotes -> Expand the remote you want to save your password. Right-click on the Fetch or Push -> Select Change Credentials Enter username and password -> Select Ok.

Das war wirklich umständlich und schräg, hat aber erstmal zum erfolgreichen Commit verholfen.

Ich habe dann noch ein bisschen weiter geforscht und eine zweite Variante gefunden, die sinnvoller erscheint, aber mit einem Eclipse Neustart bezahlt werden muss:

1. From Eclipse toolbar navigate to Window > Preferences > Security > Secure Storage > Contents Tab > [Default Secure Storage] > GIT > "whatever github url"
2. Select the url and delete the current user.
3. Eclipse will ask for a restart. Do it.
4. Push new changes and this time egit will prompt to save credentials in secure storage which was removed from the previous step.
Categories
AWS

Cloud Practitioner Certificate

Finally I had some time to make my AWS Cloud Practitioner Certificate.

On-Demand-Video Course

The course of Stephane Maarek was really helpfull: https://www.udemy.com/course/aws-certified-cloud-practitioner-new/

The slides of the course: https://media.datacumulus.com/aws-ccp/AWS%20Certified%20Cloud%20Practitioner%20Slides%20v1.3.pdf

Practice Exams

I also learned with this practice exams from Udemy Business catalog:

  • aws-certified-cloud-practitioner-practice-test
  • practice-exams-aws-certified-cloud-practitioner
  • aws-certified-cloud-practitioner-practice-exams-c

Badge

My verified Badge at Credly: https://www.credly.com/badges/197060ea-4c32-4063-b483-90f45d37c68d/public_url

Categories
Development

Mein erster Token

Dieses Krypto-Zeug und diese Meme-Token ... das ist alles so verrückt, dass ich mich einfach doch mal eingehender damit beschäftigen muss.

Eine Online-IDE für Ethereum gibt es unter: http://remix.ethereum.org
Programmiert wird das ganze in Solidity, hier die Doku: https://docs.soliditylang.org/en/latest

Eine interessante Firma/Seite dazu: openzeppelin.com
Hochgradig interessant deren Repository: https://github.com/OpenZeppelin
Für den ersten Einstieg in die Welt der Crypto Contracte ist deren Wizard: https://wizard.openzeppelin.com

Einen ersten Contract Code herbeizaubern

Auf die Seite von dem Wizard gehen, alles anklicken, das wird schon so seine Richtigkeit haben, Name, Symbol und Premint anpassen:

Contract Code sichern

Den so mühsam erarbeiteten Code wollen wir natürlich für die Nachwelt sichern und speichern in in ein eigens dafür angelegtes GitHub Repository:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts@4.2.0/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts@4.2.0/token/ERC20/extensions/ERC20Snapshot.sol";
import "@openzeppelin/contracts@4.2.0/access/Ownable.sol";
import "@openzeppelin/contracts@4.2.0/security/Pausable.sol";
import "@openzeppelin/contracts@4.2.0/token/ERC20/extensions/draft-ERC20Permit.sol";

contract DagobertDoge is ERC20, ERC20Snapshot, Ownable, Pausable, ERC20Permit {
    constructor()
        ERC20("DagobertDoge", "DAGOBERTDOGE")
        ERC20Permit("DagobertDoge")
    {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }

    function snapshot() public onlyOwner {
        _snapshot();
    }

    function pause() public onlyOwner {
        _pause();
    }

    function unpause() public onlyOwner {
        _unpause();
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function _beforeTokenTransfer(address from, address to, uint256 amount)
        internal
        whenNotPaused
        override(ERC20, ERC20Snapshot)
    {
        super._beforeTokenTransfer(from, to, amount);
    }
}

Der DagobertDoge Token

In der Online IDE wird eine neue Contract Datei angelegt und dort der Code gespeichert:

Anschließend wird der Contract compiliert:

Anschließend wird der Contract (DagobertDoge auswählen) im JavaScript Environment deployed um zu sehen, ob es funktioniert.

Anschließend über Environment Injected Web3 deployen. Dann öffnet sich (zB) das MetaMask Plugin des Browsers und in diesem sollte das BNB Test Netzwerk eingerichtet sein, so dass der Token dann in diesem Test Netzwerk erzeugt wird. Es funktioniert aber auch, wenn man das richtige Smart Chain (BNB) Netzwerk eingerichtet hat.

Da ich grade überlesen habe, dass das Richtige und nicht das Testnetzwerk ausgewählt war, habe ich für 0,077 BNB den DagobertDoge Token erstellt. Glaube ich zumindest.
Laut einem Onlineumrechner sind 0,077 BNB ca. 20 Euro:

NACHTRAG: Anscheinend hatte ich mich verguckt; in meiner TrustWallet wird für die Transaktion eine Netzwerkgebühr von 0,00779352 BNB angezeigt, die dort mit 2,74$ bewertet werden.

Der DagobertDoge Contract

Die Contract ID des wundervollen DagobertDoge Contracts findet sich nach dem Deployment in der Remix IDE unter Deployed Contracts:

DagobertDoge: 0x6ca2cdbc2cdee75b5f3492c4c133f0190aa60fc6

Wenn ich diese Contract ID kopiere und zB bei PooCoin eingebe, findet sich sogar etwas mit DagobertDoge:

Categories
Development

Günstige Website

Domain

Für ein R&D-Projekt benötige ich eine eigene Domain. Diese soll möglichst günstig zu bekommen sein und das bei einem Anbieter, der eine Bezahlung über Bitcoin oä ermöglicht. Bei der Recherche bin ich dabei auf diesen Anbieter gestoßen: Porkbun

Das Projekt läuft unter dem Arbeitstitel: Dagobert Doge. Also suche ich nach einer entsprechenden Domain:

Es werden einige verfügbare Domains angezeigt:

Ich entscheide mich für DagobertDoge.space. Klingt cool und kostet nur 1,16 $ im ersten Jahr:

Registrieren, bezahlen und schon erscheint die Domain im Domain Management:

EMail

Die Einrichtung eines gehosteten Email Accounts wird in der Knowledge Base von Porkbun beschrieben. Der erste Monat ist kostenfrei.

Domain Management -> Email -> Option 1: Email Hosting -> Configure

Nach dem einmonatigem Testzeitraum wird die Gebühr für ein ganzes Jahr eingezogen, also noch flugs einen Reminder zum rechtzeitigen Löschen angelegt.

Anschließend werden die Email Configuration Settings angezeigt, sehr hilfeich. Außerdem eine DMARC Notice. Wenn man auf den Configure Button klickt, werden die Einstellungen automatisch vorgenommen. Was es mit DMARC genau auf sich hat muss ich bei Gelegenheit evaluieren.

Der Webmail Client präsentiert sich sehr aufgeräumt, nice:

Alternativ wäre auch ein dauerhaft kostenfreies Email Forwarding möglich:

Website auf Github Pages

Die Website soll auf Github Pages gehostet werden. Das ist kostenfrei und über die üblichen Git Tools editierbar. Zumindest stelle ich mir das so vor, der Test kommt jetzt:

In den Details des Domain Managements -> Quick Connect -> Manage:

Github auswählen und einen neuen Account anlegen:

Auf I Need One klicken, schon öffnet sich GitHub in einem neuen Tab:

Und siehe da, wenn wir jetzt DagobertDoge öffnen, sehen wir:

Die Website - Ein Template

Die HelloWorld-Seite sieht maximal spartanisch aus, daher habe ich ein frei verfüg- und nutzbares Template für eine fancy Website gesucht und auf html DESIGN gefunden:

Die Website - Das Projekt

Das Git Projekt auf meinen Arbeitsrechner clonen:

cd [...]/workspace
git clone https://github.com/DagobertDoge/dagobertdoge.github.io.git
cd dagobertdoge.github.io

Die Dateien aus dem Bitcypo Template werden in das dagobertdoge.github.io Verzeichnis kopiert.

Git einrichten und Änderungen in das Repository übertragen:

git config user.email "admin@dagobertdoge.space"
git config user.name "DagobertDoge"
git add *
git commit
git push
Username for 'https://github.com': DagobertDoge
Password for 'https://DagobertDoge@github.com': xxx

Dann noch ein paar kleine Anpassungen im HTML und in einem Image & dann das ganze ins GitHub Repository pushen.

Die Website - Das erste Resultat

DagobertDoge.space

Whois

Eine Whois-Abfrage ergab, dass keinerlei persönlichen Informationen von mir im Whois Record eingetragen wurden. Ich brauche also keine Angst vor zB Spam haben.

Abschluss

Die Etappenziele sind erreicht: Eine eigene Domain für schmales Geld, Webmail für zumindest einen Monat, danach wenigstens noch die Mailweiterleitung. Bei Gelegenheit sollte ich mal schauen, ob es nicht einen Dienst gibt, der einem gratis das Mailhosting übernimmt. Das müsste ja technisch möglich sein über einen entsprechenden Eintrag im MX Record.

Das Repository für den Code der Website und sogar das Hosting der (statischen) Seite gibt es for free.

Ein Template für die erste Version der Website gibt es auch for free.

Nachtrag: Basic authentication deprecation

Nach dem Checkin in GitHub erreichte mich diese EMail:

Hi @DagobertDoge,

You recently used a password to access the repository at DagobertDoge/dagobertdoge.github.io with git using git/2.20.1.

Basic authentication using a password to Git is deprecated and will soon no longer work. Visit https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/ for more information around suggested workarounds and removal dates.

Thanks,
The GitHub Team

Für die Verwendung über die Konsole benötige ich also einen personal access token, sonst ist bald Schluß mit Lustig.

Der Anleitung folgend auf das Profil Photo klicken -> Settings -> Developer Settings -> Personal access tokens -> Generate a personal access token:

Generate new token with descriptive name and permissions. To use your token to access repositories from the command line, select repo.

Es wird ein Personal access token generiert und angezeigt. Dieser ist unbedingt zu notieren, denn er kann nicht noch einmal angezeigt werden.

"Once you have a token, you can enter it instead of your password when performing Git operations over HTTPS."

Damit ich nicht bei jedem Commit etc. den Username & Token eingeben muss, aktiviere ich das Caching der Credentials:

# Set git to use the credential memory cache
$ git config --global credential.helper cache

# Set the cache to timeout after 1 hour (setting is in seconds)
$ git config --global credential.helper 'cache --timeout=3600'

Git kann jetzt wie zuvor verwendet werden. Beim ersten Befehl muss einmalig Username & Token eingegeben werden, diese werden für die darauffolgende Stunde gecached.

Categories
Database Development

DB Export & Import

Um die Daten einer MS-SQL Datenbank zu exportieren und anschließend in die geDockerte Version zu kopieren (MS-SQL DB in Docker Container) habe ich die Chains verwendet, eine propritäre Software.

Vorbereitung

Die Entwicklung und Ausführung der Scripte erfolgt auf dem Entwickler Laptop. Später werden die Scripte voraussichtlich auf den Servern ausgeführt, da grade für die produktive Umgebung eine längere Laufzeit erwartet wird.

Auf den Entwickler Laptops läuft Windows, so dass zum Ausführen der Chains das Windows Executable Chain.cmd verwendet wird. In dieser Datei sind Anpassungen vorzunehmen, so ist der JAVA_HOME Pfad inzwischen ein anderer und mMn sollte das File Encoding auf UTF-8 gesetzt werden:

#SET JAVA_HOME=%~d0/jre13
SET JAVA_HOME=%~c0/eclipse/java/java1.8

SET FIXED_PROPS=%STDPROPS% [...] -Dfile.encoding=UTF-8

Die Ausführung der einzelnen Chain muss aus dem Verzeichnis der Chain.cmd erfolgen.
Ausnahme: Auf meinem Laptop muss ich es genau anders herum machen und in das Verzeichnis der Chain gehen und Chain.cmd mit absolutem Pfad aufrufen.

cd D:\Development\workspace\chainproject\bin\
Chain.cmd ../../ChainsProject/ImportChain.chn

Datenbank Verbindungsdaten

Die Verbindungsdaten der Datenbank werden in einer eigenen Konfigurationsdatei hinterlegt:

# connection:
.db=jdbc:jtds:sqlserver://localhost:1433/CCP;TDS=8.0
.user=DonaldDemo
.password=DonaldDemo12345678

# DBObjectSQLServer options:
.objects.autorestore=false
.autorollback=false
.maxconnects=1
.initialconnects=1

# DBObjectSQLServerScript options:
.sqlecho=INFO

Export Chain

Das Chain Command Script ist relativ übersichtlich, da lediglich ein Schritt ausgeführt werden muss. Für diesen Schritt ist das Prefix und die zu verwendende Java Klasse zu definieren. Außerdem sind noch die Verbindungsdaten der Datenbank zu includieren:

#
chainmanager.process-1.prefix=dbscr
dbscr.chain.class=package.name.db.DBObjectSQLServerScript
#
# Scripts:
dbscr.script1=ExportChain.sql

# include configuration:
dbscr.chain.include=MSSQL-RealServer-Connect.inc

Das Chain "SQL" Script ermittelt erst alle Tabellen der angegebenen Schema und speichert diese in der Datei TABLES.csv:

#DEFINE CSVFILE TABLES.csv

#QUERY_CSV (ECHO) [CSVFILE]
  SELECT table_catalog, table_schema, table_name, table_type
    FROM CCP.INFORMATION_SCHEMA.TABLES
   WHERE TABLE_TYPE = 'BASE TABLE'
     AND TABLE_SCHEMA IN ('DEMO_SCHEMA')
ORDER BY TABLE_SCHEMA, TABLE_NAME
#INFO [SELECTED] rows exported into File [CSVFILE]

Anschließend wird über alle Tabellen iteriert, deren Daten gelesen und in einer CSV-Datei gespeichert:

#FOREACH IDX [CSVFILE]
#INFO Start export of: [IDX:table_schema] [IDX:table_name]
#DEFINE CSVFILE_TABLE [IDX:table_schema]/[IDX:table_name].csv
#QUERY_CSV (ECHO) [CSVFILE_TABLE]
  SELECT *
    FROM [IDX:table_schema].[IDX:table_name]
#INFO [SELECTED] rows exported into File [CSVFILE_TABLE]
#NEXT IDX

Import Chain

Das Chain Command Script entpricht weitgehend dem Export Script:

#
chainmanager.process-1.prefix=dbscr
dbscr.chain.class=package.name.db.DBObjectSQLServerScript
#
# Scripts:
dbscr.script1=ImportChain.sql

# include configuration:
dbscr.chain.include=MSSQL-DockerServer-Connect.inc

Als Vorbereitung muss im Verzeichnis DEMO_SCHEMA eine Datei csvlist angelegt werden, in dieser stehen die zu importierenden Tabellendaten-CSV-Dateinamen. Der reine Import ist ein Einzeiler, dem diese csvlist Datei übergeben wird und das Schema, in das diese Tabellendaten importiert werden sollen:

#IMPORT_CSV_DIR (ECHO) (ENCODING=UTF-8) (ERRORLOG) *DEMO_SCHEMA/csvlist DEMO_SCHEMA

Das ganze Script benötigt noch weitere Befehle, Details dazu siehe MS-SQL DB in Docker Container:

#INFO 
#INFO [PROCESS] start...
#INFO DBUser: [DBUSER]  //  Database-URL: [DBURL]

-- disable all constraints
EXEC sp_MSforeachtable "ALTER TABLE ? NOCHECK CONSTRAINT all"

--
SET IDENTITY_INSERT ccp_fdt.performance ON

#IMPORT_CSV_DIR (ECHO) (ENCODING=UTF-8) (ERRORLOG) *DEMO_SCHEMA/csvlist DEMO_SCHEMA

-- enable all constraints
exec sp_MSforeachtable @command1="print '?'", @command2="ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"