Letzte Änderungen
- Details
Java ist eine der ältesten noch breit eingesetzten Programmiersprachen der Industrie. Sie prägt Unternehmensanwendungen, Android-Apps und große Datenverarbeitungssysteme. Dieser Artikel führt in die Grundlagen ein und ordnet ein, wann Java die richtige Wahl ist.
Warum Java eine eigene Laufzeitumgebung hat
Java-Code wird nicht direkt in Maschinencode übersetzt, sondern in einen Zwischencode (Bytecode). Dieser läuft auf der Java Virtual Machine (JVM) – einem Programm, das die Zielplattform bereitstellt. Daraus folgen die Eigenschaften, die Java bekannt gemacht haben:
- Plattformunabhängigkeit: Dasselbe kompilierte Programm läuft auf Linux, Windows und macOS. „Write once, run anywhere“ war das ursprüngliche Versprechen.
- Automatische Speicherverwaltung: Die Garbage Collection räumt nicht mehr erreichbare Objekte auf. Manuelle Speicherfreigabe wie in C entfällt.
- Laufzeitoptimierung: Die JVM übersetzt häufig durchlaufene Codepfade im Betrieb in Maschinencode (Just-in-Time-Kompilierung). Programme werden dadurch mit der Laufzeit schneller.
Grundstruktur
// Datei: Begruessung.java — Klassenname und Dateiname muessen uebereinstimmen
public class Begruessung {
// Einstiegspunkt: exakt diese Signatur
public static void main(String[] args) {
System.out.println("Hallo, " + (args.length > 0 ? args[0] : "Welt"));
int anzahl = 3;
double preis = 19.99;
boolean aktiv = true;
String name = "Server"; // String, nicht string
var kurz = "ab Java 10"; // Typ wird abgeleitet
// Arrays sind Objekte mit fester Laenge
String[] dienste = {"nginx", "postgresql", "redis"};
System.out.println(dienste.length);
}
}
javac Begruessung.java # kompilieren -> Begruessung.class
java Begruessung Welt # ausfuehren (ohne .class!)
Klassen und Objekte
Java ist streng objektorientiert: Fast alles steht in einer Klasse. Ein modernes Beispiel mit den seit Java 14 stabilen Records, die für reine Datenklassen viel Schreibarbeit sparen:
public record Server(String name, String ip, int port) {
// Konstruktor, Getter, equals, hashCode und toString automatisch
public String adresse() {
return ip + ":" + port;
}
}
// Verwendung
var s = new Server("web01", "10.0.0.10", 443);
System.out.println(s.adresse());
System.out.println(s.name()); // Getter heisst name(), nicht getName()
// Klassische Klasse mit Verhalten
public class Konto {
private double stand; // private = nur innerhalb der Klasse
public Konto(double start) {
this.stand = start;
}
public void einzahlen(double betrag) {
if (betrag <= 0) throw new IllegalArgumentException("Betrag muss positiv sein");
this.stand += betrag;
}
public double getStand() { return stand; }
}
Vererbung, Schnittstellen, Generics
// Schnittstelle: beschreibt Faehigkeiten, ohne Implementierung
public interface Speicherbar {
void speichern(); // automatisch public abstract
default String beschreibung() { // Standardimplementierung moeglich
return "speicherbares Objekt";
}
}
// Vererbung mit extends (nur eine Basisklasse!)
public class DateiSpeicher implements Speicherbar {
@Override
public void speichern() {
System.out.println("in Datei geschrieben");
}
}
// Generics: Typsicherheit bei Sammlungen
List<String> namen = new ArrayList<>();
namen.add("alpha");
// namen.add(42); // Kompilierfehler - gut so!
Map<String, Integer> zaehler = new HashMap<>();
zaehler.merge("treffer", 1, Integer::sum);
Ausnahmen
try {
var inhalt = Files.readString(Path.of("/etc/hostname"));
System.out.println(inhalt.trim());
} catch (IOException e) {
System.err.println("Lesefehler: " + e.getMessage());
} finally {
System.out.println("wird immer ausgefuehrt");
}
// Eigene Ausnahme
public class KonfigFehler extends Exception {
public KonfigFehler(String msg) { super(msg); }
}
Java unterscheidet geprüfte und ungeprüfte Ausnahmen: Geprüfte müssen im Code behandelt oder weitergereicht werden – der Compiler erzwingt das. Das ist am Anfang lästig, verhindert aber vergessene Fehlerbehandlung.
Modernes Java: Streams und Lambdas
record Dienst(String name, boolean laeuft) {}
var dienste = List.of(
new Dienst("nginx", true),
new Dienst("redis", false),
new Dienst("postgres", true)
);
var laufend = dienste.stream()
.filter(Dienst::laeuft) // Methodenreferenz
.map(Dienst::name)
.sorted()
.toList();
System.out.println(laufend); // [nginx, postgres]
var kaputt = dienste.stream()
.filter(d -> !d.laeuft())
.count();
Werkzeuge und Ökosystem
- Build: Maven und Gradle. Beide verwalten Abhängigkeiten und Build-Abläufe; Gradle ist flexibler, Maven strenger standardisiert.
- Test: JUnit 5 ist der Standard. Mockito ergänzt für Attrappen von Abhängigkeiten.
- Frameworks: Spring Boot dominiert bei Web- und Unternehmensanwendungen; Quarkus und Micronaut optimieren auf schnellen Start und wenig Speicher – relevant für Container.
- Laufzeit: OpenJDK in verschiedenen Distributionen (Temurin, Corretto, Zulu). Seit Java 17 sind die LTS-Versionen 17 und 21 breit im Einsatz.
Wann Java passt – und wann nicht
Geeignet: umfangreiche Unternehmensanwendungen mit langer Lebensdauer, Systeme mit vielen Entwicklern, Android-Apps, Datenverarbeitung mit großen Mengen, Anwendungen, die nach dem Anlaufen dauerhaft hohe Last fahren.
Weniger geeignet: kurze Skripte (dafür ist Python oder Bash besser), sehr speicherknappe Umgebungen (JVM-Grundlast liegt bei einigen Dutzend Megabyte), und Situationen, in denen schnelle Startzeit wichtiger ist als Spitzenleistung – hier sind Go oder Rust oft die passendere Wahl.
Verwandte Grundlagen: JVM, Virtualisierung, Rust: Ownership, Java-Befehle.
- Details
SQL ist die Sprache, mit der man Daten in relationalen Datenbanken abfragt und verändert. Die Grundlagen sind in wenigen Stunden gelernt; die eigentliche Kunst liegt darin, Abfragen zu formulieren, die auch bei Millionen Zeilen schnell bleiben. Dieser Artikel führt von einfachen Abfragen zu Verknüpfungen und Indizes.
\nDie vier Grundoperationen
\n-- Lese n (SELECT)\nSELECT id, name, port FROM services WHERE aktiv = 1 ORDER BY name LIMIT 10;\n\n-- Einfuegen (INSERT)\nINSERT INTO services (name, port, aktiv) VALUES ('web01', 443, 1);\n\n-- Aendern (UPDATE) -- WHERE nie vergessen!\nUPDATE services SET aktiv = 0 WHERE name = 'web01';\n\n-- Loeschen (DELETE)\nDELETE FROM services WHERE id = 12;\nDie wichtigste Regel beim Ändern: Ein UPDATE oder DELETE ohne WHERE trifft alle Zeilen. Wer das einmal versehentlich in Produktion ausgelöst hat, prüft danach für immer. Bewährter Ablauf: Erst mit SELECT dieselbe Bedingung testen und die Zahl der Treffer prüfen, dann ändern.
Aggregatfunktionen und Gruppierung
\n-- Anzahl, Summe, Durchschnitt\nSELECT COUNT(*) AS anzahl, AVG(antwortzeit_ms) AS mittel FROM anfragen;\n\n-- Gruppieren: je Kunde eine Zeile\nSELECT kunde_id,\n COUNT(*) AS anfragen,\n AVG(antwortzeit_ms) AS mittel,\n MAX(antwortzeit_ms) AS maximum\nFROM anfragen\nGROUP BY kunde_id\nHAVING COUNT(*) > 100 -- HAVING filtert nach der Gruppierung\nORDER BY mittel DESC;\n\n-- Bedingte Aggregation in einem Durchlauf\nSELECT\n SUM(CASE WHEN status = 200 THEN 1 ELSE 0 END) AS ok,\n SUM(CASE WHEN status >= 500 THEN 1 ELSE 0 END) AS fehler\nFROM anfragen\nWHERE zeitpunkt >= NOW() - INTERVAL 1 DAY;\nDer Unterschied zwischen WHERE und HAVING ist prüfungsrelevant und im Alltag entscheidend: WHERE filtert vor der Gruppierung einzelne Zeilen, HAVING filtert nach der Gruppierung die Ergebnisgruppen. WHERE COUNT(*) > 100 ist ein Fehler.
Verknüpfungen (JOIN)
\n-- INNER JOIN: nur Zeilen, die in beiden Tabellen vorkommen\nSELECT b.id, b.titel, a.name AS autor\nFROM buecher b\nJOIN autoren a ON a.id = b.autor_id;\n\n-- LEFT JOIN: alle Buecher, auch ohne Autor (dann NULL)\nSELECT b.id, b.titel, a.name AS autor\nFROM buecher b\nLEFT JOIN autoren a ON a.id = b.autor_id;\n\n-- Zeilen ohne Gegenstueck finden (Anti-Join)\nSELECT b.id, b.titel\nFROM buecher b\nLEFT JOIN autoren a ON a.id = b.autor_id\nWHERE a.id IS NULL;\n\n-- Ausleihvorgaenge mit Anzahl je Buch\nSELECT b.titel, COUNT(a.id) AS ausleihen\nFROM buecher b\nLEFT JOIN ausleihen a ON a.buch_id = b.id\nGROUP BY b.id, b.titel\nORDER BY ausleihen DESC;\nDer klassische Fehler: Beim Join über mehrere Tabellen entstehen schnell unerwartet viele Zeilen (Kreuzprodukt), wenn eine Verknüpfungsbedingung fehlt oder zu unscharf ist. Zählregel: Nach jedem neuen Join die Zeilenzahl mit COUNT(*) prüfen – wächst sie unerwartet stark, ist die Bedingung falsch.
Indizes: warum Abfragen schnell oder langsam sind
\nOhne Index liest die Datenbank für jede Abfrage die gesamte Tabelle (Full Table Scan). Ein Index ist eine sortierte Zusatzstruktur, in der die Datenbank Zieldatensätze direkt findet – vergleichbar mit dem Register eines Buchs.
\n-- Index anlegen\nCREATE INDEX idx_anfragen_kunde ON anfragen (kunde_id);\nCREATE INDEX idx_anfragen_zeit ON anfragen (zeitpunkt);\n\n-- Mehrspaltig: Reihenfolge ist entscheidend (linke Spalte zuerst nutzbar)\nCREATE INDEX idx_anfragen_kunde_zeit ON anfragen (kunde_id, zeitpunkt);\n\n-- Eindeutig\nCREATE UNIQUE INDEX idx_benutzer_mail ON benutzer (email);\n\n-- Pruefen, ob der Index genutzt wird\nEXPLAIN SELECT * FROM anfragen WHERE kunde_id = 7;\nEXPLAIN ANALYZE SELECT ... ; -- tatsaechliche Ausfuehrungszeiten\nWann ein Index (nicht) hilft:
\n- \n
- Hilft bei
WHERE,JOIN-Bedingungen,ORDER BYundGROUP BYauf indizierten Spalten. \n - Hilft nicht, wenn eine Funktion auf der Spalte angewendet wird:
WHERE YEAR(zeitpunkt) = 2026ist langsam,WHERE zeitpunkt >= '2026-01-01' AND zeitpunkt < '2027-01-01'nutzt den Index. \n - Hilft nicht bei sehr geringer Selektivität – eine Spalte mit nur zwei Ausprägungen wie ein Ja/Nein-Feld ist als einzelner Index meist nutzlos. \n
- Kostet Schreibgeschwindigkeit und Speicher. Jeder Index muss bei jedem
INSERTundUPDATEmitgepflegt werden. \n
Unterabfragen und Window-Funktionen
\n-- Unterabfrage im WHERE\nSELECT name FROM produkte\nWHERE preis > (SELECT AVG(preis) FROM produkte);\n\n-- EXISTS statt IN bei grossen Ergebnismengen\nSELECT k.* FROM kunden k\nWHERE EXISTS (SELECT 1 FROM bestellungen b WHERE b.kunde_id = k.id);\n\n-- Window-Funktion: Rang ohne Gruppierung zu verlieren\nSELECT kunde_id, betrag,\n ROW_NUMBER() OVER (PARTITION BY kunde_id ORDER BY betrag DESC) AS rang,\n SUM(betrag) OVER (PARTITION BY kunde_id) AS summe\nFROM bestellungen;\nWindow-Funktionen sind seit SQL:2003 im Standard und in allen gängigen Systemen verfügbar. Sie lösen Aufgaben, für die man früher verschachtelte Abfragen oder Selbstverknüpfungen brauchte.
\nPraktische Regeln
\n- \n
- Nur die Spalten wählen, die man braucht.
SELECT *verhindert in manchen Systemen die Nutzung eines Covering Index und verschwendet Bandbreite. \n - Bei großen Ergebnissen immer
LIMITsetzen – auch beim Testen. \n EXPLAINvor dem Optimieren. Ohne Ausführungsplan ist jede Verbesserung geraten. \n - Indizes an den Abfragen ausrichten, nicht an den Tabellen. Ein Index, den keine Abfrage nutzt, bremst nur das Schreiben. \n
- Parameter verwenden, keine Zeichenkettenverkettung. Das verhindert SQL-Injection und lässt die Datenbank Abfragepläne wiederverwenden. \n
- Transaktionen bewusst setzen. Mehrere zusammengehörige Änderungen gehören in
BEGIN/COMMIT, damit ein Fehler sie vollständig zurücknimmt. \n
Verwandte Grundlagen: SQL: Abfragen und Grundlagen, MySQL-Befehle, SQL-Injection, SQL-Index.
- Details
Bash ist die Standard-Shell auf den meisten Linux-Systemen und die Sprache, in der Systemadministratoren den größten Teil ihrer Arbeit erledigen. Wer Bash beherrscht, muss für Automatisierung selten ein Skript in einer anderen Sprache schreiben. Dieser Artikel führt durch die Grundlagen mit praxistauglichen Beispielen.
Grundgerüst eines Skripts
#!/usr/bin/env bash
set -euo pipefail # Wichtigste Zeile! Siehe unten.
# Variablen (kein Leerzeichen um das Gleichheitszeichen!)
NAME="Server"
anzahl=3
# Verwendung mit ${...} — sicherer als $NAME
echo "Hallo ${NAME}, ${anzahl} Instanzen"
# Schreibgeschützt
readonly PFAD="/var/log"
# Umgebung mit Standardwert
PORT="${PORT:-8080}"
echo "Port: ${PORT}"
Die Zeile set -euo pipefail ist in Skripten praktisch Pflicht:
-e– bricht bei einem fehlgeschlagenen Befehl ab (statt weiterzulaufen).-u– Fehler bei der Verwendung nicht gesetzter Variablen (statt leer zu expandieren).-o pipefail– ein Fehler in einer Pipe führt zum Abbruch, nicht nur der letzte Befehl zählt.
Ohne diese Optionen läuft ein Skript nach einem Fehler fröhlich weiter und richtet womöglich Schaden an.
Bedingungen
# Dateien und Verzeichnisse prüfen
if [[ -f "$DATEI" ]]; then echo "Datei existiert"; fi
if [[ -d "$ORDNER" ]]; then echo "Verzeichnis existiert"; fi
if [[ -r "$DATEI" && -w "$DATEI" ]]; then echo "lesbar und schreibbar"; fi
# Zeichenketten
if [[ -z "$VAR" ]]; then echo "leer"; fi
if [[ -n "$VAR" ]]; then echo "nicht leer"; fi
if [[ "$A" == "$B" ]]; then echo "gleich"; fi
if [[ "$A" =~ ^[0-9]+$ ]]; then echo "nur Ziffern"; fi
# Zahlen (bei [[ ]] mit -lt etc., oder (( )) für Arithmetik)
if (( anzahl > 5 )); then echo "mehr als fuenf"; fi
if (( anzahl % 2 == 0 )); then echo "gerade"; fi
# case für mehrere Faelle
case "$1" in
start) echo "starten" ;;
stop) echo "stoppen" ;;
restart) echo "neu starten" ;;
*) echo "unbekannt: $1" >&2; exit 1 ;;
esac
[[ ]] verwenden, nicht [ ]: Die doppelten Klammern sind ein Bash-Schlüsselwort mit sichererem Verhalten – ungesetzte Variablen und Wortaufteilung führen nicht zu überraschenden Ergebnissen.
Schleifen
# Über eine Liste
for dienst in nginx postgresql redis; do
systemctl is-active --quiet "$dienst" || echo "$dienst laeuft nicht"
done
# Über Dateien (immer mit null-Trennung bei Leerzeichen im Namen!)
while IFS= read -r -d '' datei; do
echo "gefunden: $datei"
done < <(find /var/log -name '*.log' -print0)
# Zählen
for ((i = 1; i <= 5; i++)); do echo "Durchlauf $i"; done
# Solange bis Bedingung erfuellt
versuche=0
until curl -sf http://localhost:8080/health > /dev/null; do
versuche=$((versuche + 1))
if (( versuche > 30 )); then echo "aufgeben"; exit 1; fi
sleep 2
done
Funktionen und Argumente
log() {
local stufe="$1"; shift # shift entfernt das erste Argument
printf '%s [%s] %s\n' "$(date -Is)" "$stufe" "$*" >&2
}
log INFO "Sicherung startet"
log WARN "Speicher zu ${PROZENT}% voll"
# Argumente des Skripts selbst
# $0 = Skriptname $1..$n = Argumente $# = Anzahl $@ = alle
if (( $# < 1 )); then
echo "Aufruf: $0 <verzeichnis>" >&2
exit 2
fi
local in Funktionen ist wichtig, damit Variablen nicht versehentlich global wirken. Und "$@" immer in Anführungszeichen verwenden, sonst zerfallen Argumente mit Leerzeichen.
Praxismuster: mit Fehlerbehandlung und Aufräumen
#!/usr/bin/env bash
set -euo pipefail
QUELLE="${1:?Pflichtargument: Quellverzeichnis}"
ZIEL="/srv/backup/$(date +%Y-%m-%d)"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT # laeuft in JEDEM Fall
[[ -d "$QUELLE" ]] || { echo "Kein Verzeichnis: $QUELLE" >&2; exit 1; }
mkdir -p "$ZIEL"
tar -czf "${TMP}/archiv.tar.gz" -C "$QUELLE" .
mv "${TMP}/archiv.tar.gz" "$ZIEL/"
# Pruefsumme zur Kontrolle
sha256sum "$ZIEL/archiv.tar.gz" > "$ZIEL/archiv.sha256"
echo "Fertig: $ZIEL"
trap ... EXIT ist das Gegenstück zum finally-Block anderer Sprachen: Das temporäre Verzeichnis wird entfernt, egal ob das Skript erfolgreich endet oder abbricht.
Häufige Stolpersteine
- Leerzeichen sind entscheidend.
VAR = wertist ein Aufruf des ProgrammsVAR, keine Zuweisung. Richtig:VAR=wert. - Immer quoten.
$dateizerfällt bei Leerzeichen im Pfad in mehrere Wörter."$datei"ist die Regel, nicht die Ausnahme. - Nicht
lsparsen. Ausgaben vonlssind für Menschen gedacht. Für Dateilistenfindmit-print0oder Glob-Muster nutzen. - Exit-Codes prüfen. Nach jedem Befehl mit möglichem Fehler
|| { echo ...; exit 1; }ergänzen. - Kein
catin Pipelines.grep muster dateistattcat datei | grep muster– kürzer und ohne zusätzlichen Prozess. - Berechnungen mit
(( )).$((a + b))rechnet,$(( ))in Bedingungen prüft. Beides ist schnell, aber nicht für Fließkomma geeignet – dafürbcoderawk.
Verwandte Grundlagen: Shell, Cron und Crontab, Terminal-Werkzeuge, bash-Befehle.
- Details
JavaScript ist die Sprache des Webs. Sie läuft in jedem Browser, seit 2009 mit Node.js auch auf dem Server, in Desktop-Anwendungen und in der Industrieautomatisierung. Wer heute Automatisierung, Web-Frontends oder kleine Werkzeuge bauen will, kommt an ihr kaum vorbei.
Variablen und Datentypen
Modernes JavaScript kennt let und const. Die alte var hat Tücken beim Gültigkeitsbereich (Hoisting, Funktion-Scope) und gehört in neuen Projekten nicht mehr verwendet.
const name = "Lübeck"; // unveränderliche Bindung
let counter = 0; // veränderlich
counter += 1;
// Typen sind dynamisch
typeof name // "string"
typeof counter // "number"
typeof undefined // "undefined"
typeof null // "object" (alte Eigenheit der Sprache)
typeof [] // "object"
Wichtig: Zeichenketten mit Backticks sind Template Literals und erlauben Interpolation.
const gruss = `Hallo ${name}, Zähler: ${counter}`;
Funktionen
Neben klassischen Funktionen gibt es Arrow Functions mit kürzerer Syntax und anderem this-Verhalten.
function add(a, b) { return a + b; }
const add2 = (a, b) => a + b;
// Standardwerte und Rest-Parameter
const begruessen = (wer = "Welt", ...extra) => `Hallo ${wer}${extra.length ? "!" : ""}`;
Arrays und Objekte
const zahlen = [4, 8, 15, 16, 23, 42];
zahlen.map(n => n * 2); // [8, 16, 30, 32, 46, 84]
zahlen.filter(n => n % 2 === 0); // [4, 8, 16, 42]
zahlen.reduce((s, n) => s + n, 0); // 108
const server = { host: "example.de", port: 443, tls: true };
const { host, port } = server; // Destrukturierung
Diese drei Methoden – map, filter, reduce – lösen die meisten Schleifenaufgaben eleganter als for und sind der Kern modernen JavaScript-Stils.
Das DOM: HTML aus JavaScript steuern
const el = document.querySelector("#status");
el.textContent = "Verbunden";
el.classList.add("ok");
document.querySelector("#btn").addEventListener("click", async () => {
const antwort = await fetch("/api/status");
const daten = await antwort.json();
el.textContent = daten.status;
});
fetch liefert ein Promise. Mit async/await schreibt man asynchronen Code fast so lesbar wie synchronen – ohne die berüchtigte Callback-Verschachtelung.
Asynchrones Arbeiten
async function holeAlle(ids) {
const ergebnisse = await Promise.all(ids.map(id => fetch(`/api/${id}`)));
return Promise.all(ergebnisse.map(r => r.json()));
}
try {
const daten = await holeAlle([1, 2, 3]);
console.log(daten.length);
} catch (err) {
console.error("Abruf fehlgeschlagen:", err.message);
}
Module und Node.js
Code wird über import/export in Module geteilt – im Browser mit type="module", in Node.js mit .mjs oder "type": "module" in der package.json.
// lib/math.js
export const quadrat = x => x * x;
// app.js
import { quadrat } from "./lib/math.js";
Was man früh verinnerlichen sollte
- Immer
constversuchen,letnur bei Bedarf. - Strikte Vergleiche
===statt==(kein Typzwang). async/awaitstatt verschachtelter Callbacks.- Fehler immer behandeln – ein unbehandeltes Promise scheitert still.
- Fakten und Fallstricke der Sprache: MDN ist die verlässlichste Referenz.
Siehe auch
- JavaScript-Befehle: Schnellreferenz
- Python für Einsteiger
- REST – wie Web-APIs aufgebaut sind
- Details
SQL (Structured Query Language) ist die Abfragesprache relationaler Datenbanken – MySQL, MariaDB, PostgreSQL, SQL Server und SQLite sprechen sie in sehr ähnlicher Form. Die folgenden Bausteine decken den Alltag zu weit über neunzig Prozent ab.
Die Beispieldaten
CREATE TABLE kunden (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
ort VARCHAR(100),
erstellt DATE
);
CREATE TABLE bestellungen (
id INT PRIMARY KEY,
kunden_id INT NOT NULL REFERENCES kunden(id),
betrag DECIMAL(10,2) NOT NULL,
status VARCHAR(20) NOT NULL
);
SELECT, WHERE, ORDER BY, LIMIT
SELECT id, name, ort
FROM kunden
WHERE ort = 'Lübeck'
AND erstellt >= '2026-01-01'
ORDER BY name ASC
LIMIT 20;
Wichtig: NULL ist kein Wert, sondern „unbekannt". Vergleiche mit = NULL funktionieren nicht – man schreibt where ort IS NULL bzw. IS NOT NULL.
JOINs: Tabellen verbinden
Daten liegen normalisiert in mehreren Tabellen. JOIN führt sie über Schlüssel zusammen.
-- INNER JOIN: nur Kunden MIT Bestellungen
SELECT k.name, b.id AS bestellnr, b.betrag
FROM kunden k
JOIN bestellungen b ON b.kunden_id = k.id;
-- LEFT JOIN: ALLE Kunden, Bestellungen optional
SELECT k.name, COALESCE(SUM(b.betrag), 0) AS umsatz
FROM kunden k
LEFT JOIN bestellungen b ON b.kunden_id = k.id
GROUP BY k.id, k.name;
Der Unterschied ist entscheidend: Ein INNER JOIN verschwindet Kunden ohne Bestellung, ein LEFT JOIN behält sie mit NULL.
Aggregate, GROUP BY und HAVING
SELECT status,
COUNT(*) AS anzahl,
SUM(betrag) AS summe,
AVG(betrag) AS durchschnitt
FROM bestellungen
GROUP BY status
HAVING SUM(betrag) > 1000
ORDER BY summe DESC;
Merksatz für die Reihenfolge: WHERE filtert vor der Gruppierung (einzelne Zeilen), HAVING nach der Gruppierung (Ergebnisgruppen).
Daten ändern
INSERT INTO kunden (id, name, ort, erstellt)
VALUES (101, 'Muster GmbH', 'Lübeck', '2026-09-26');
UPDATE kunden SET ort = 'Hamburg' WHERE id = 101;
DELETE FROM kunden WHERE id = 101;
Vorsicht: Ein UPDATE oder DELETE ohne WHERE trifft alle Zeilen. Vorher mit demselben WHERE ein SELECT ausführen und das Ergebnis prüfen.
Indizes: der wichtigste Hebel für Tempo
Ein Index ist wie das Stichwortverzeichnis eines Buchs: Ohne ihn muss die Datenbank jede Zeile lesen (full table scan).
CREATE INDEX idx_bestellungen_kunden ON bestellungen (kunden_id);
CREATE INDEX idx_kunden_ort ON kunden (ort);
Faustregeln: Indizes auf Spalten, die in WHERE, JOIN und ORDER BY auftauchen; zusammengesetzte Indizes in der Reihenfolge der Filter. Zu viele Indizes kosten Schreibleistung und Speicher – also messen statt raten. Wie man Abfragen analysiert, zeigt die jeweilige EXPLAIN-Ausgabe der Datenbank.
Siehe auch
- SQL-Befehle: Schnellreferenz
- Datenbanken im Wissensbereich
- NoSQL – die Alternative für nicht-relationale Daten
- Details
Go: Konzepte für nebenläufige Systeme
Go (Golang) wurde bei Google entwickelt und ist für Server, CLI-Tools und Cloud-native Dienste beliebt. Sein Markenzeichen ist einfache Nebenläufigkeit.
Goroutines
Eine Goroutine ist ein leichtgewichtiger Thread: go funktion() startet sie. Tausende Goroutines sind normal – der Scheduler verteilt sie auf wenige OS-Threads.
Channels
Channels sind typisierte Kommunikationsröhren zwischen Goroutines:
ch := make(chan int)
go func() { ch <- 42 }()
wert := <-chSlices und Maps
slice := []int{1,2,3}, m := map[string]int{}. Slices wachsen dynamisch, Maps liefern value, ok := m[key].
Interfaces
Interfaces werden implizit erfüllt: Ein Typ genügt einem Interface, wenn er die Methoden hat. Das ermöglicht elegante Abstraktionen ohne Vererbung.
Typische Einsatzgebiete
- Web-APIs (net/http, Gin, Echo).
- CLI-Tools (Docker, kubectl, Hugo sind in Go geschrieben).
- Netzwerkdienste und Proxys.
Weiterführend: Programmiersprachen.
- Details
Rust: Sicherheit durch Ownership
Rust verbindet die Performance von C/C++ mit Speichersicherheit – ohne Garbage Collector. Das Herzstück ist das Ownership-Modell.
Ownership-Regeln
- Jeder Wert hat genau einen Besitzer.
- Wird der Besitzer verlassen, wird der Wert freigegeben.
- Ausleihen (Borrowing) ist möglich, aber nur einer darf schreiben oder viele dürfen lesen.
Beispiel
fn main() {
let s = String::from("Hallo");
let laenge = berechne(&s); // borrow
println!("{}: {}", s, laenge);
}
fn berechne(s: &String) -> usize { s.len() }Warum wichtig
Viele Sicherheitslücken (Buffer Overflows, Use-after-free) sind in C/C++ möglich, weil Speicher manuell verwaltet wird. Rust fängt diese Fehler zur Compile-Zeit ab.
Einsatzgebiete
- Systemprogrammierung (Kernel-Module, Treiber).
- WebAssembly.
- Hochperformante Web-Services (Axum, Actix).
Weiterführend: Programmiersprachen.
- Details
Python für Einsteiger: Der pragmatische Einstieg in die Programmierung
Python ist seit Jahren eine der meistgenutzten Programmiersprachen der Welt – und das aus gutem Grund. Die Syntax ist lesbar, die Einstiegshürde niedrig, und trotzdem reicht das Ökosystem von kleinen Skripten bis zu großen KI- und Webanwendungen. Dieser Artikel zeigt den pragmatischen Einstieg: Was Python ausmacht, wie man es installiert und die ersten sinnvollen Programme schreibt.
Warum Python?
Python wurde 1991 von Guido van Rossum veröffentlicht und setzt von Anfang an auf Lesbarkeit. Einrückungen strukturieren den Code statt geschweifter Klammern – das erzwingt sauberen Stil. Drei Eigenschaften machen Python besonders attraktiv:
- Große Standardbibliothek: Dateien, Netzwerk, JSON, Datenbanken – vieles ist ohne Zusatzpakete nutzbar.
- Riesiges Ökosystem: Mit
pipinstalliert man über 500.000 Pakete, von Web-Frameworks (Django, FastAPI) bis zu Datenanalyse (pandas) und KI (PyTorch, Transformers). - Breite Einsatzgebiete: Automatisierung, Web-APIs, Datenauswertung, Machine Learning und DevOps-Skripte.
Installation und erste Schritte
Unter Linux ist Python 3 meist vorinstalliert – prüfen mit:
python3 --version
Fehlt es, installiert man es über den Paketmanager (Debian/Ubuntu: sudo apt install python3 python3-pip, Arch: sudo pacman -S python python-pip). Unter Windows lädt man den Installer von python.org und aktiviert beim Installieren die Option „Add Python to PATH".
Das erste Programm als Datei hallo.py:
#!/usr/bin/env python3
name = input("Wie heißt du? ")
print(f"Hallo {name}, willkommen bei Python!")
Ausführen mit python3 hallo.py. Die f-Zeichenkette setzt Variablen direkt in den Text – eine der elegantesten Python-Eigenschaften.
Variablen, Datentypen und Kontrollfluss
Python ist dynamisch typisiert: Variablen brauchen keine Typdeklaration.
alter = 30 # int
name = "Michael" # str
groesse = 1.82 # float
ist_admin = True # bool
hobbys = ["Linux", "KI"] # Liste
if alter >= 18 and ist_admin:
print("Zugriff erlaubt")
for hobby in hobbys:
print(f"- {hobby}")
Listen, Tupel, Dictionaries und Mengen decken die meisten Datenstrukturen ab. Ein Dictionary (ähnlich einer JSON-Struktur):
server = {"name": "web01", "cpu": 4, "ram_gb": 16}
print(server["name"])
server["ram_gb"] = 32
Funktionen und Module
Funktionen kapseln Logik und machen Code wiederverwendbar:
def quadrat(x):
"""Berechnet das Quadrat einer Zahl."""
return x * x
print(quadrat(7)) # 49
Eigene Module sind einfach Python-Dateien. Der Import funktioniert über den Dateinamen ohne Endung:
import quadratmodul # lädt quadratmodul.py
from quadratmodul import quadrat
Virtuelle Umgebungen – der wichtigste Reflex
Pro Projekt eine eigene Umgebung: So kollidieren Paketversionen nicht. Ab Python 3.3 ist venv an Bord:
python3 -m venv .venv
source .venv/bin/activate # Linux/macOS
# Windows: .venv\Scripts\activate
pip install requests
Die aktivierte Umgebung erkennt man am Präfix (.venv) in der Shell. Verlassen mit deactivate. In Produktion und CI/CD sind virtuelle Umgebungen oder Container (Docker) Pflicht, damit Builds reproduzierbar bleiben.
Praxisbeispiel: Web-Request mit requests
import requests
antwort = requests.get("https://api.github.com", timeout=10)
if antwort.status_code == 200:
daten = antwort.json()
print(f"API läuft: {daten.get('current_user_url')}")
else:
print(f"Fehler: {antwort.status_code}")
Das Muster – Request, Status prüfen, JSON parsen – wiederholt sich in unzähligen Automatisierungen. Zusammen mit schedule oder Cron lassen sich daraus vollständige Monitoring- und Reporting-Skripte bauen (siehe auch Systemadministration).
Typische Anfängerfehler und wie man sie vermeidet
- Einrückung mischen: Nie Tabs und Leerzeichen kombinieren; der Editor sollte Tabs in Leerzeichen umwandeln.
- Pakete global installieren: Immer
venvverwenden – sonst „funktioniert es auf meinem Rechner, aber nicht auf dem Server". - Ausnahmen verschlucken: Ein nacktes
except:versteckt Fehler. Besser:except requests.RequestException as e:und den Fehler loggen. - Python 2 denken:
printist eine Funktion, Division/liefert Float. Moderner Code zielt auf Python 3.10+.
Dateien lesen und schreiben – die Basis jeder Automatisierung
Kaum ein Skript kommt ohne Dateizugriff aus. Python macht das angenehm kurz:
# Datei zeilenweise lesen
with open("server.log", encoding="utf-8") as f:
for zeile in f:
if "ERROR" in zeile:
print(zeile.strip())
# Datei schreiben
with open("bericht.txt", "w", encoding="utf-8") as f:
f.write("Ergebnis: OK\n")
Das with-Konstrukt schließt die Datei automatisch – auch bei Fehlern. Für strukturierte Daten ist JSON der Standard:
import json
daten = {"server": "web01", "status": "online"}
with open("status.json", "w", encoding="utf-8") as f:
json.dump(daten, f, indent=2)
with open("status.json", encoding="utf-8") as f:
geladen = json.load(f)
print(geladen["status"])
Dieses Muster – Logs durchsuchen, Ergebnisse als JSON ablegen – deckt einen großen Teil der täglichen Server-Automatisierung ab. Kombiniert mit schedule oder Cron entstehen daraus vollwertige Report- und Monitoring-Skripte.
Fehler behandeln statt abstürzen
Produktiver Code fängt Fehler gezielt ab:
try:
antwort = requests.get(url, timeout=10)
antwort.raise_for_status()
except requests.Timeout:
print("Zeitüberschreitung – Server antwortet nicht")
except requests.HTTPError as e:
print(f"HTTP-Fehler: {e.response.status_code}")
else:
print("Erfolgreich geladen")
Die Regel: So spezifisch wie möglich fangen, den Fehler loggen, und dem Nutzer eine verständliche Meldung geben. Ein pauschales except Exception am Ende eines Skripts ist nur als letztes Auffangnetz akzeptabel.
Weiterführende Schritte
Wer Python ernsthaft einsetzen will, sollte als Nächstes Web-APIs mit FastAPI oder Flask bauen, Daten mit pandas auswerten oder sich der Automatisierung von Serveraufgaben widmen. Für die Arbeit mit Sprachmodellen und KI-Agenten ist Python ohnehin die dominierende Sprache – dazu mehr unter KI & Automatisierung.
Verwandte Artikel: PHP für Webentwicklung · Linux-Systemadministration · KI & Automatisierung
- Details
PHP für Webentwicklung: Moderne Praxis statt Legacy-Code
PHP treibt noch immer rund drei Viertel aller Websites an – von kleinen Blogs bis zu großen Systemen wie WordPress, Joomla oder Laravel-Anwendungen. Der schlechte Ruf aus den 2000er-Jahren ist längst überholt: Modernes PHP (ab Version 8) ist typsicher, schnell und gut testbar. Dieser Artikel zeigt, wie zeitgemäße PHP-Entwicklung aussieht und worauf es in der Praxis ankommt.
Warum PHP 2026 immer noch relevant ist
- Verbreitung: Kaum ein Shared-Hosting-Anbieter ohne PHP-Unterstützung – günstig und überall verfügbar.
- Reife Ökosysteme: Composer als Paketmanager, Frameworks wie Laravel und Symfony, CMS wie Joomla und WordPress.
- Performance: Mit PHP 8.x und OpCache ist PHP für die meisten Web-Anwendungen schnell genug – und mit FrankenPHP (Go-basierter App-Server) sogar für anspruchsvollere Last.
- Einfacher Einstieg: Eine Datei, ein Webserver, fertig. Ideal für Prototypen und kleinere Dienste.
Grundlagen: Variablen, Arrays, Funktionen
<?php
$name = "Michael";
$alter = 42;
$preise = [12.99, 8.50, 3.20]; // Array
$nutzer = ["id" => 1, "name" => "ki"]; // Assoziatives Array
function brutto(float $netto, float $satz = 0.19): float {
return $netto * (1 + $satz);
}
echo brutto(100.0); // 119.0
?>
Wichtig: PHP-Dateien, die nur Logik enthalten, schließen ohne ?> – das verhindert versehentlich ausgegebenen Whitespace, der Header-Probleme verursacht.
Moderne Sprachelemente (PHP 8+)
Seit PHP 8.0 (November 2020) hat sich die Sprache fundamental verbessert:
- Named Arguments:
function erstelle_user(name: "ki", admin: true)– Argumente beim Namen nennen. - Union Types:
function finde(int|string $id): ?User– präzise Typen statt Docblocks. - Match-Ausdruck: Ersetzt lange switch-Ketten:
$status = match($code) { 200 => 'ok', 404 => 'missing', default => 'unknown' }; - Nullsafe-Operator:
$user?->profil?->email– bricht sauber bei null ab. - Readonly-Klassen und Enums: Seit 8.1/8.2 für klare Domänenmodelle.
Projektstruktur und Composer
Composer ist der Paketmanager – so wie pip für Python oder npm für JavaScript:
composer require guzzlehttp/guzzle # HTTP-Client
composer require vlucas/phpdotenv # .env-Konfiguration
Eine saubere, kleine Struktur für ein Web-Projekt:
projekt/
public/ # DocumentRoot – nur hier liegt Erreichbares
index.php # Front-Controller (Router)
src/ # Anwendungscode
config/ # Konfiguration
vendor/ # Composer-Abhängigkeiten (nie ins Git!)
Der Front-Controller leitet alle Requests an index.php weiter. Dazu eine .htaccess (Apache) oder eine nginx-Konfiguration mit try_files $uri $uri/ /index.php?$query_string;.
Sicherheit: Die vier häufigsten Fallen
- SQL-Injection: Nie Werte direkt in Queries concatenaten. Immer Prepared Statements (PDO):
$stmt = $pdo->prepare("SELECT * FROM user WHERE email = ?");
$stmt->execute([$email]);
- XSS (Cross-Site Scripting): Jede Ausgabe escapen:
<?= htmlspecialchars($wert, ENT_QUOTES, 'UTF-8') ?> - CSRF: Bei Formularen ein Token mitsenden und prüfen (z.B.
$_SESSION['csrf']). - Datei-Uploads: Erlaubte MIME-Typen whitelisten, Dateien außerhalb des Webroots speichern und per Hash umbenennen.
Wer diese vier Regeln befolgt, ist bereits sicherer als die Mehrheit der PHP-Anwendungen im Internet.
Praxis: Kleine REST-API mit PDO
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
$pdo = new PDO(
'mysql:host=localhost;dbname=app;charset=utf8mb4',
getenv('DB_USER'), getenv('DB_PASS'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
header('Content-Type: application/json');
$stmt = $pdo->query("SELECT id, titel FROM artikel ORDER BY id DESC LIMIT 10");
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
So entstehen in wenigen Minuten funktionierende JSON-Endpunkte – das Grundmuster hinter vielen Web-APIs, auch im Hanse-Computing-Umfeld (z.B. Webradio-API, siehe HTTP/HTTPS-Grundlagen).
Deployment in der Praxis
- OpCache aktivieren (
opcache.enable=1,opcache.validate_timestamps=0in Produktion). - Fehleranzeige aus:
display_errors=0, stattdessen Logs (error_log). - Composer mit
composer install --no-dev --optimize-autoloader. - Dateirechte: Webserver darf nur lesen, Schreibzugriff nur wo nötig (Uploads, Cache).
- Backups und Deploy-Strategie: Siehe Systemadministration.
Fehlerbehandlung und Logging
Fehler verschlucken ist die häufigste PHP-Sünde. Modernes PHP unterscheidet sauber zwischen Exceptions (programmgesteuert) und Fehlern (Laufzeit):
try {
$stmt = $pdo->prepare("SELECT * FROM user WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
if (!$user) {
throw new RuntimeException("User nicht gefunden");
}
} catch (PDOException $e) {
error_log("DB-Fehler: " . $e->getMessage());
http_response_code(500);
echo json_encode(["error" => "Interner Fehler"]);
}
Wichtig: Dem Nutzer niemals rohe Fehlermeldungen zeigen – die enthüllen Datenbankstruktur und Pfade. In Produktion gilt display_errors=0, alles geht ins Log. Für strukturierte Fehlerüberwachung bieten sich Sentry oder einfache Log-Aggregation an (siehe Monitoring in der Systemadministration).
Gemeinsame Nutzung mit Datenbanken: PDO richtig einsetzen
PDO ist der moderne Datenbank-Zugang (MySQL/MariaDB, PostgreSQL, SQLite). Die wichtigsten Details:
$pdo = new PDO(
'mysql:host=localhost;dbname=app;charset=utf8mb4',
$user, $pass,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
// Transaktion: entweder alles oder nichts
$pdo->beginTransaction();
try {
$pdo->exec("UPDATE konto SET saldo = saldo - 100 WHERE id = 1");
$pdo->exec("UPDATE konto SET saldo = saldo + 100 WHERE id = 2");
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e;
}
Transaktionen verhindern, dass bei einem Fehler halbe Buchungen zurückbleiben. ATTR_EMULATE_PREPARES = false nutzt echte Prepared Statements des Servers – schneller und sicherer. Die Grundlagen zu SQL selbst finden Sie im Artikel SQL-Grundlagen.
Fazit
PHP ist 2026 eine pragmatische, gut wartbare Wahl – besonders dort, wo Hosting-Umgebungen vorgegeben sind (Joomla, WordPress) oder schnelle Ergebnisse zählen. Wer moderne Features nutzt, auf Sicherheit achtet und sauber strukturiert, entwickelt damit genauso professionell wie mit jeder anderen Sprache. Die Grundlagen zu SQL-Datenbanken finden sich im Artikel SQL-Grundlagen.
Verwandte Artikel: Python für Einsteiger · SQL-Grundlagen · Systemadministration