LordLamer
  • Home
  • About Me
  • Familie
  • Knowledgeroot
  • Impressum
KEEP IN TOUCH

Posts in category Software Architektur

Spring JdbcClient mit KeyHolder: Auto-Increment-Werte zuverlässig abrufen

Jun29
2025
Written by lordlamer

Das Problem: Generierte IDs nach einem INSERT abrufen

Jeder Entwickler kennt das Szenario: Nach dem Einfügen eines neuen Datensatzes in eine Datenbank benötigen wir oft die automatisch generierte ID für weitere Operationen. Sei es für Logging, für die Rückgabe an den Client oder für nachfolgende Datenbankoperationen – der generierte Primärschlüssel ist essentiell.

Mit Spring Boot 3.2 wurde der neue JdbcClient eingeführt, der eine moderne, fluent API für JDBC-Operationen bietet. In diesem Artikel zeigen wir, wie Sie mit dem JdbcClient und KeyHolder zuverlässig auf Auto-Increment-Werte zugreifen können.

Beispiel-Szenario: User-Verwaltung

Nehmen wir eine einfache User-Tabelle als Beispiel:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Und eine entsprechende Java-Klasse:

public class User {
    private Integer id;
    private String username;
    private String email;
    private LocalDateTime createdAt;
    
    // Konstruktoren, Getter und Setter...
}

Die Lösung: JdbcClient mit KeyHolder

Der bewährte und zuverlässige Ansatz verwendet den KeyHolder in Kombination mit dem modernen JdbcClient:

@Repository
public class UserRepository {
    
    private final JdbcClient jdbcClient;
    
    public UserRepository(JdbcClient jdbcClient) {
        this.jdbcClient = jdbcClient;
    }
    
    public int createUser(User user) {
        KeyHolder keyHolder = new GeneratedKeyHolder();

        int update = jdbcClient.sql("""
                INSERT INTO users (username, email, created_at) 
                VALUES (?, ?, ?)
                """)
                .params(
                        user.getUsername(),
                        user.getEmail(),
                        user.getCreatedAt()
                )
                .update(keyHolder);

        Assert.state(update == 1, "Failed to create user: " + user.getUsername());

        return keyHolder.getKey().intValue();
    }
}

Was passiert hier im Detail?

1. KeyHolder erstellen

KeyHolder keyHolder = new GeneratedKeyHolder();

Der GeneratedKeyHolder ist die Standard-Implementierung des KeyHolder-Interfaces und sammelt automatisch alle von der Datenbank generierten Schlüssel.

2. SQL mit Parametern ausführen

int update = jdbcClient.sql("""
        INSERT INTO users (username, email, created_at) 
        VALUES (?, ?, ?)
        """)
        .params(user.getUsername(), user.getEmail(), user.getCreatedAt())
        .update(keyHolder);

Der entscheidende Punkt ist der Aufruf von .update(keyHolder) anstatt dem einfachen .update(). Dadurch wird Spring angewiesen, die generierten Schlüssel zu erfassen.

3. Generierte ID abrufen

return keyHolder.getKey().intValue();

Nach der Ausführung enthält der KeyHolder die generierte ID, die wir als Integer zurückgeben können.

Vollständiges Service-Beispiel

@Service
@Transactional
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User createUser(String username, String email) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setCreatedAt(LocalDateTime.now());
        
        int generatedId = userRepository.createUser(user);
        user.setId(generatedId);
        
        log.info("Created user with ID: {} and username: {}", generatedId, username);
        
        return user;
    }
}

Error Handling und Best Practices

Robuste Fehlerbehandlung

public int createUserSafely(User user) {
    try {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        int rowsAffected = jdbcClient.sql("""
                INSERT INTO users (username, email, created_at) 
                VALUES (?, ?, ?)
                """)
                .params(user.getUsername(), user.getEmail(), user.getCreatedAt())
                .update(keyHolder);
        
        if (rowsAffected != 1) {
            throw new DataAccessException("Expected 1 row to be affected, but was: " + rowsAffected) {};
        }
        
        Number key = keyHolder.getKey();
        if (key == null) {
            throw new DataAccessException("No generated key returned") {};
        }
        
        return key.intValue();
        
    } catch (DataAccessException e) {
        log.error("Failed to create user: {}", user.getUsername(), e);
        throw new ServiceException("Could not create user", e);
    }
}

Validierung der Eingangsdaten

public int createUser(User user) {
    // Validierung vor dem Insert
    if (user.getUsername() == null || user.getUsername().trim().isEmpty()) {
        throw new IllegalArgumentException("Username cannot be null or empty");
    }
    
    if (user.getEmail() == null || !isValidEmail(user.getEmail())) {
        throw new IllegalArgumentException("Valid email is required");
    }
    
    KeyHolder keyHolder = new GeneratedKeyHolder();
    
    int update = jdbcClient.sql("""
            INSERT INTO users (username, email, created_at) 
            VALUES (?, ?, ?)
            """)
            .params(user.getUsername(), user.getEmail(), user.getCreatedAt())
            .update(keyHolder);

    Assert.state(update == 1, "Failed to create user: " + user.getUsername());
    
    return keyHolder.getKey().intValue();
}

Batch-Operationen

Auch bei mehreren Einfügungen funktioniert der KeyHolder-Ansatz zuverlässig:

public List<Integer> createMultipleUsers(List<User> users) {
    List<Integer> generatedIds = new ArrayList<>();
    
    for (User user : users) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        jdbcClient.sql("""
                INSERT INTO users (username, email, created_at) 
                VALUES (?, ?, ?)
                """)
                .params(user.getUsername(), user.getEmail(), user.getCreatedAt())
                .update(keyHolder);
        
        generatedIds.add(keyHolder.getKey().intValue());
    }
    
    return generatedIds;
}

Datenbankkompatibilität

Der KeyHolder-Ansatz funktioniert zuverlässig mit allen gängigen Datenbanken:

  • MySQL: AUTO_INCREMENT wird automatisch erkannt
  • PostgreSQL: SERIAL und IDENTITY Spalten werden unterstützt
  • SQL Server: IDENTITY Spalten funktionieren out-of-the-box
  • Oracle: SEQUENCE-basierte Auto-Increment wird unterstützt

Testing

Unit Test mit H2 In-Memory-Datenbank

@DataJdbcTest
class UserRepositoryTest {
    
    @Autowired
    private JdbcClient jdbcClient;
    
    private UserRepository userRepository;
    
    @BeforeEach
    void setUp() {
        userRepository = new UserRepository(jdbcClient);
    }
    
    @Test
    void shouldReturnGeneratedIdWhenCreatingUser() {
        // Given
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setCreatedAt(LocalDateTime.now());
        
        // When
        int generatedId = userRepository.createUser(user);
        
        // Then
        assertThat(generatedId).isGreaterThan(0);
    }
    
    @Test
    void shouldCreateMultipleUsersWithUniqueIds() {
        // Given
        User user1 = createTestUser("user1", "user1@example.com");
        User user2 = createTestUser("user2", "user2@example.com");
        
        // When
        int id1 = userRepository.createUser(user1);
        int id2 = userRepository.createUser(user2);
        
        // Then
        assertThat(id1).isNotEqualTo(id2);
        assertThat(id2).isGreaterThan(id1);
    }
    
    private User createTestUser(String username, String email) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setCreatedAt(LocalDateTime.now());
        return user;
    }
}

Warum KeyHolder der zuverlässige Standard ist

Nach ausgiebigen Tests verschiedener Ansätze hat sich der KeyHolder-Pattern als der stabilste und konsistenteste Weg erwiesen, um mit Auto-Increment-Werten zu arbeiten:

Vorteile des KeyHolder-Ansatzes:

  • Zuverlässigkeit: Funktioniert konsistent über alle Datenbanktypen hinweg
  • Explizite Kontrolle: Klare Trennung zwischen Insert-Operation und Key-Abruf
  • Fehlerbehandlung: Einfache Überprüfung, ob ein Schlüssel generiert wurde
  • Flexibilität: Unterstützt auch Szenarien mit mehreren generierten Schlüsseln
  • Bewährt: Lange etablierter Standard in der Spring-Community

Fazit

Der Spring JdbcClient in Kombination mit dem KeyHolder bietet eine moderne, saubere und zuverlässige Lösung für den Umgang mit automatisch generierten Datenbankschlüsseln. Dieser Ansatz sollte in allen neuen Spring-Projekten verwendet werden, wo Auto-Increment-Werte benötigt werden.

Die wichtigsten Punkte zum Mitnehmen:

  1. Verwenden Sie immer einen KeyHolder beim Einfügen von Datensätzen mit Auto-Increment
  2. Prüfen Sie die Anzahl der betroffenen Zeilen für zusätzliche Sicherheit
  3. Implementieren Sie robuste Fehlerbehandlung für Produktionsumgebungen
  4. Der KeyHolder-Ansatz ist datenbankagnostisch und funktioniert überall

Mit diesem Pattern können Sie Auto-Increment-Werte zuverlässig und performant in Ihren Spring-Anwendungen handhaben – eine solide Grundlage für robuste Datenbankoperationen.

Posted in Java

Free Branch Analysis in SonarQube Cummunity Edition – Here’s How!

Feb02
2025
Written by lordlamer

Hey there! Are you using SonarQube Community Edition and missing the branch analysis feature that’s only available in the paid versions? Don’t worry – I’ll show you how to add this functionality to your setup. With a community plugin and Docker, you’ll have it running in no time!

Quick Disclaimer First

Before we dive in, a quick heads-up: The plugin we’ll be using isn’t officially from SonarSource – it’s a community project under the GNU LGPL v3 license. If you’re planning to use this for large, business-critical projects, you might want to check out the official Developer Edition. But for most projects, this solution works perfectly fine!

What You’ll Need

– Docker and Docker Compose installed on your machine
– Git
– A Bash shell (Git Bash works fine on Windows)
– Basic Docker knowledge is helpful

Let’s Get Started!

1. Setting Up the Project

First, create a directory for your project:

mkdir sonarqube-docker
cd sonarqube-docker

2. Configuration Files

Let’s start with the .env file. This is where you set the versions you want to use:

SONARQUBE_VERSION=24.12.0.100206-community
BRANCH_PLUGIN_VERSION=1.23.0

Next up is the docker-compose.yml. It’s a bit longer, but don’t worry – you won’t need to modify anything:

services:
  sonarqube:
    depends_on:
      db:
        condition: service_healthy
    build:
      context: .
      dockerfile: Dockerfile
      args:
        - SONARQUBE_VERSION=${SONARQUBE_VERSION}
        - BRANCH_PLUGIN_VERSION=${BRANCH_PLUGIN_VERSION}
    container_name: sonarqube
    ports:
      - "9000:9000"
    networks:
      - sonarnet
    environment:
      - SONAR_JDBC_URL=jdbc:postgresql://db:5432/sonar
      - SONAR_JDBC_USERNAME=sonar
      - SONAR_JDBC_PASSWORD=sonar
    volumes:
      - sonarqube_conf:/opt/sonarqube/conf
      - sonarqube_data:/opt/sonarqube/data
  db:
    image: postgres:16
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U sonar" ]
      interval: 10s
      timeout: 5s
      retries: 5
    hostname: db
    container_name: postgres
    networks:
      - sonarnet
    environment:
      - POSTGRES_USER=sonar
      - POSTGRES_PASSWORD=sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_conf:
  sonarqube_data:
  postgresql:
  postgresql_data:

networks:
  sonarnet:

Now for the Dockerfile – this is where we integrate the plugin into the SonarQube image:

ARG SONARQUBE_VERSION
FROM sonarqube:${SONARQUBE_VERSION}
ARG BRANCH_PLUGIN_VERSION
# Copy the plugin
COPY plugins/sonarqube-community-branch-plugin-${BRANCH_PLUGIN_VERSION}.jar /opt/sonarqube/extensions/plugins/
ARG PLUGIN_VERSION
ENV PLUGIN_VERSION=1.23.0
ENV SONAR_WEB_JAVAADDITIONALOPTS="-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar=web"
ENV SONAR_CE_JAVAADDITIONALOPTS="-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-${PLUGIN_VERSION}.jar=ce"

3. Helper Scripts

The download-plugin.sh script automatically fetches the plugin for you:

#!/bin/bash
mkdir -p plugins
curl -L -o plugins/sonarqube-community-branch-plugin-${BRANCH_PLUGIN_VERSION}.jar \
 https://github.com/mc1arke/sonarqube-community-branch-plugin/releases/download/${BRANCH_PLUGIN_VERSION}/sonarqube-community-branch-plugin-${BRANCH_PLUGIN_VERSION}.jar

And the start.sh script makes launching everything super easy:

#!/bin/bash
# Check if .env exists
if [ ! -f .env ]; then
    echo "Error: .env file not found!"
    exit 1
fi
# Load and export environment variables
export $(cat .env | grep -v '^#' | xargs)
# Check if required variables are set
if [ -z "$SONARQUBE_VERSION" ] || [ -z "$BRANCH_PLUGIN_VERSION" ]; then
    echo "Error: Required environment variables are not set!"
    echo "Please check SONARQUBE_VERSION and BRANCH_PLUGIN_VERSION in .env file"
    exit 1
fi
# Download plugin
./download-plugin.sh
# Start Docker Compose
docker-compose up -d

4. Almost There!

Make the scripts executable:

chmod +x download-plugin.sh start.sh

5. Launch Time!

Now just run the start script:

./start.sh

The script takes care of everything for you:
– Checks if all settings are correct
– Downloads the plugin
– Starts the Docker containers

6. Your SonarQube is Ready

After a few seconds, you can access SonarQube:
– URL: http://localhost:9000
– Login: admin
– Password: admin (you’ll need to change this on first login)

Using Branch Analysis

Now you can use branch analysis in your projects. Just specify the branch name when running a scan:

sonar-scanner \
-Dsonar.projectKey=my-awesome-project \
-Dsonar.branch.name=feature/new-feature

Common Issues and Solutions

1. SonarQube won’t start? Check the logs:

docker-compose logs sonarqube

2. On Linux, you might need this setting:

sudo sysctl -w vm.max_map_count=262144

3. Plugin not found? Check the plugins directory:

ls -l plugins/

Wrapping Up

That’s all there is to it! You now have a fully functional SonarQube installation with branch analysis. I’m using this setup myself for various projects and it works great.

Remember: For larger enterprises, the Developer Edition might be the better choice. But for most projects, this solution is more than adequate!

Useful Links

– The Community Branch Plugin on GitHub
– Official SonarQube Documentation
– SonarQube on Docker Hub

Posted in Company, docker

Der Weg zur besseren Software-Architektur – Ein Erfahrungsbericht

Nov18
2024
Written by lordlamer

Als Software-Entwickler stehe ich täglich vor der Herausforderung, die richtige Balance zwischen technischer Exzellenz und praktischer Umsetzbarkeit zu finden. In diesem Artikel möchte ich meine Erfahrungen teilen, wie man eine nachhaltige und wartbare Software-Architektur entwickeln kann, auch unter realen Bedingungen und Zeitdruck.

Die Grundlagen: Die richtigen Fragen stellen

Bevor wir uns in die technischen Details stürzen, sollten wir uns zunächst die wichtigsten Fragen stellen:

  • Welches konkrete Problem soll die Software lösen?
  • Wer ist die Zielgruppe und wie groß ist diese?
  • Welche Änderungen oder Erweiterungen sind in Zukunft zu erwarten?

Diese fundamentalen Fragen ermöglichen es uns, erste architektonische Entscheidungen zu treffen. Ein Beispiel aus unserer Praxis: Wir entwickeln Zusatzsoftware zur Datenextraktion aus ERP-Systemen. Da diese Software nicht direkt von Endbenutzern bedient wird, können wir hier andere architektonische Maßstäbe anlegen als bei einer benutzerorientierten Anwendung.

Die häufigsten Fallstricke

In meiner täglichen Arbeit beobachte ich immer wieder verschiedene Gründe für suboptimale Architekturentscheidungen:

  • Fehlendes fachliches Wissen
  • Zu viele technische Abhängigkeiten
  • Mangelndes technisches Know-how
  • Unzureichende Dokumentation
  • Fehlende Tests

Lösungsansätze aus der Praxis

1. Strukturierte Dokumentation mit Arc42

Eine meiner wichtigsten Erkenntnisse: Arc42 als Dokumentationsframework kann wahre Wunder bewirken. Es hilft uns dabei:

  • Software in überschaubare “Boxen” zu unterteilen
  • Probleme systematisch herunterzubrechen
  • Abhängigkeiten zwischen Anwendungsteilen klar zu definieren

Noch bevor die erste Zeile Code geschrieben wird, haben wir damit eine klare Zieldefinition für unsere Applikation.

2. Fachlichkeit vor Technik

Ein weiterer entscheidender Punkt ist die Priorisierung: Die fachliche Implementierung sollte immer vor der technischen Umsetzung stehen. Konkret bedeutet das:

  • Erst das fachliche Problem verstehen und lösen
  • Technische Implementierungsdetails bewusst zurückstellen
  • Test-Driven Development (TDD) nutzen, um fachlichen Code zu validieren

3. Kontinuierliche Weiterbildung

Entwickler sollten sich intensiv mit modernen Konzepten auseinandersetzen:

  • Domain-Driven Design (DDD)
  • SOLID-Prinzipien
  • Clean Code-Praktiken

Diese Konzepte helfen dabei, Architekturentscheidungen besser zu verstehen und nachhaltigere Software zu entwickeln.

Der Umgang mit Zeitdruck

Eine realistische Betrachtung des Zeitfaktors ist essentiell. Dabei gibt es zwei Perspektiven:

  1. Die Entwicklerseite: Wir wissen, dass Entwickler oft schlecht in Aufwandsschätzungen sind.
  2. Die Business-Seite: Kunden oder Unternehmen müssen Zeit und Geld investieren.

Mein Vorschlag: Statt nur nach Entwicklerschätzungen zu fragen, sollten wir auch die Gegenfrage stellen: “Wieviel sind Sie bereit zu investieren, um dieses Feature zu bekommen?” Diese Herangehensweise führt oft zu produktiven Diskussionen und realistischeren Erwartungen.

Fazit: Der Weg zur besseren Architektur

Zusammenfassend lassen sich folgende Kernpunkte festhalten:

  • Fokus auf fachliche statt technische Probleme
  • Technische Implementierungen so spät wie möglich festlegen
  • Strukturierte Dokumentation (z.B. mit Arc42) nutzen
  • Kontinuierlicher Aufbau von fachlichem und technischem Know-how
  • Einsatz moderner Entwicklungspraktiken wie TDD

Der Weg zu einer besseren Software-Architektur ist nicht einfach, aber er lohnt sich. Bei uns im Team sehen wir bereits deutliche Fortschritte, und ich bin zuversichtlich, dass wir auf dem richtigen Weg sind.

Dieser Artikel basiert auf meinen persönlichen Erfahrungen in der Software-Entwicklung. Ich freue mich über Ihre Gedanken und Erfahrungen zu diesem Thema!

Posted in Company

Community

  • Forum
  • GitHub
  • Knowledgeroot
  • YouTube

Categories

  • bsd (1)
  • citrix (3)
  • Company (27)
  • Debian (11)
  • docker (1)
  • Familie (75)
  • Geocaching (2)
  • Hausbau (41)
  • IPv6 (5)
  • Java (5)
  • klettern (10)
  • Knowledgeroot (16)
  • Linux (12)
  • LUG Balista (1)
  • misc (22)
  • mysql (1)
  • netscreen (2)
  • postgresql (1)
  • sap (4)
  • Software Architektur (3)
  • solr (2)
  • vim (2)

EvoLve theme by Theme4Press  •  Powered by WordPress LordLamer
Frank Habermann

We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.