DEV Community

DrSimple
DrSimple

Posted on

Spring Shell

Track my expense using Spring Shell

Spring Shell is a framework for building interactive command-line applications in Spring Boot. It lets you define commands using annotations, process user input, and display results in a structured way. It’s useful for admin tools, automation scripts, and internal developer CLIs with features like tab completion and command history.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.12</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.grazaclee</groupId>
    <artifactId>jonzy</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jonzy</name>
    <description>jonzy</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-shell.version>3.4.1</spring-shell.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.shell</groupId>
            <artifactId>spring-shell-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.shell</groupId>
                <artifactId>spring-shell-dependencies</artifactId>
                <version>${spring-shell.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>
Enter fullscreen mode Exit fullscreen mode
expense-cli/
├── src/main/java/com/example/expensecli/
│   ├── controller/
│   │   └── ExpenseController.java
│   ├── model/
│   │   └── Expense.java
│   ├── repository/
│   │   └── ExpenseRepository.java
│   ├── service/
│   │   └── ExpenseService.java
│   ├── utils/
│   │   └── DBConnection.java
│   └── ExpenseCliApplication.java
└── pom.xml
Enter fullscreen mode Exit fullscreen mode

<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- Spring Shell -->
    <dependency>
        <groupId>org.springframework.shell</groupId>
        <artifactId>spring-shell-starter</artifactId>
    </dependency>

    <!-- JDBC -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!-- MySQL Driver -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>

    <!-- Lombok (optional) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

DB Config (application.properties)

spring.datasource.url=jdbc:mysql://localhost:3306/expense_db
spring.datasource.username=root
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
Enter fullscreen mode Exit fullscreen mode

Model

package com.example.expensecli.model;

public class Expense {
    private int id;
    private String title;
    private double amount;

    public Expense() {}

    public Expense(int id, String title, double amount) {
        this.id = id;
        this.title = title;
        this.amount = amount;
    }

    public Expense(String title, double amount) {
        this.title = title;
        this.amount = amount;
    }

    // getters & setters
}
Enter fullscreen mode Exit fullscreen mode

Repository (JDBC)

package com.example.expensecli.repository;

import com.example.expensecli.model.Expense;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public class ExpenseRepository {

    private final JdbcTemplate jdbc;

    public ExpenseRepository(JdbcTemplate jdbc) {
        this.jdbc = jdbc;
    }

    public void save(Expense expense) {
        jdbc.update("INSERT INTO expenses(title, amount) VALUES (?, ?)",
                expense.getTitle(), expense.getAmount());
    }

    public List<Expense> findAll() {
        return jdbc.query("SELECT * FROM expenses",
                (rs, rowNum) -> new Expense(
                        rs.getInt("id"),
                        rs.getString("title"),
                        rs.getDouble("amount")
                ));
    }

    public Expense findById(int id) {
        return jdbc.queryForObject("SELECT * FROM expenses WHERE id = ?",
                (rs, rowNum) -> new Expense(
                        rs.getInt("id"),
                        rs.getString("title"),
                        rs.getDouble("amount")
                ), id);
    }

    public void update(int id, String title, double amount) {
        jdbc.update("UPDATE expenses SET title=?, amount=? WHERE id=?",
                title, amount, id);
    }

    public void delete(int id) {
        jdbc.update("DELETE FROM expenses WHERE id=?", id);
    }
}
Enter fullscreen mode Exit fullscreen mode

Service

package com.example.expensecli.service;

import com.example.expensecli.model.Expense;
import com.example.expensecli.repository.ExpenseRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ExpenseService {

    private final ExpenseRepository repo;

    public ExpenseService(ExpenseRepository repo) {
        this.repo = repo;
    }

    public void addExpense(String title, double amount) {
        repo.save(new Expense(title, amount));
    }

    public List<Expense> getAll() {
        return repo.findAll();
    }

    public Expense getById(int id) {
        return repo.findById(id);
    }

    public void updateExpense(int id, String title, double amount) {
        repo.update(id, title, amount);
    }

    public void deleteExpense(int id) {
        repo.delete(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

Controller (Spring Shell CLI)

package com.example.expensecli.controller;

import com.example.expensecli.model.Expense;
import com.example.expensecli.service.ExpenseService;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;

import java.util.List;

@ShellComponent
public class ExpenseController {

    private final ExpenseService service;

    public ExpenseController(ExpenseService service) {
        this.service = service;
    }

    @ShellMethod("Add new expense")
    public String add(String title, double amount) {
        service.addExpense(title, amount);
        return "Expense added!";
    }

    @ShellMethod("List all expenses")
    public List<Expense> list() {
        return service.getAll();
    }

    @ShellMethod("Get expense by ID")
    public Expense get(int id) {
        return service.getById(id);
    }

    @ShellMethod("Update expense")
    public String update(int id, String title, double amount) {
        service.updateExpense(id, title, amount);
        return "Updated!";
    }

    @ShellMethod("Delete expense")
    public String delete(int id) {
        service.deleteExpense(id);
        return "Deleted!";
    }
}
Enter fullscreen mode Exit fullscreen mode

Main app

package com.example.expensecli;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ExpenseCliApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExpenseCliApplication.class, args);
    }
}
Enter fullscreen mode Exit fullscreen mode

Database Table

CREATE TABLE expenses (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    amount DOUBLE
);
Enter fullscreen mode Exit fullscreen mode

CLI Usage

shell:>add "Food" 200
shell:>list
shell:>get 1
shell:>update 1 "Transport" 150
shell:>delete 1
Enter fullscreen mode Exit fullscreen mode

Top comments (0)