Prerequisites

Before using the auto-generator, make sure you have:

  1. Java 21+ installed
  2. Spring Boot project running on port 8585
  3. Admin credentials set up (from .env file)
  4. A JPA Entity you want to create a table for
  5. A JPA Repository for that entity

Example Entity Structure:

@Entity
@Table(name = "games")
public class Game {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String type;
    private Long personId;
    private Double amount;
    // ... more fields
}

Example Repository:

public interface GameJPARepository extends JpaRepository<Game, Long> {
    // Your custom queries
}

How It Works: The Magic of Reflection

The auto-generator uses Java Reflection to inspect your entity class at runtime and automatically discover all its fields.

What is Reflection?

Reflection is Java’s ability to examine and manipulate classes, methods, and fields at runtime. Instead of hardcoding each field name, the system asks the entity class: “What fields do you have?”

The Core Components

  1. TableConfigBuilder - Uses reflection to scan entity fields
    • Skips: static fields, transients, collections
    • Detects: String, Number, Boolean, Date types
    • Auto-generates: column labels (camelCase → Title Case)
  2. auto-table.html - Generic Thymeleaf fragment
    • Renders ANY entity table
    • Dynamic columns based on TableConfig
    • Type-aware rendering (TEXT, CHECKBOX, LINK, etc.)
  3. GenerateTablePage.java - One-file automation script
    • Creates controller with all CRUD endpoints
    • Generates read.html (table view)
    • Generates edit.html (create/update form)

Step 1: Configure the Generator

Open GenerateTablePage.java in your project root and edit 3 lines:

// ═══════════════════════════════════════════════════════════════════
// ✏️  EDIT THESE LINES - Everything else is automatic!
// ═══════════════════════════════════════════════════════════════════
static final String ENTITY_NAME = "Game";                                   // ← Your entity class name
static final String ENTITY_PACKAGE = "com.open.spring.mvc.rpg.games";      // ← Where your entity class is
static final String PAGE_NAME = "Games";                                    // ← Creates /mvc/games/read
// ═══════════════════════════════════════════════════════════════════

Configuration Guide

Field Example Purpose
ENTITY_NAME "Game" Name of your JPA entity class
ENTITY_PACKAGE "com.open.spring.mvc.rpg.games" Full package path to entity
PAGE_NAME "Games" Controls URL and file paths

Result:

  • Controller: src/main/java/com/open/spring/mvc/games/GamesMvcController.java
  • Views: src/main/resources/templates/games/read.html + edit.html
  • URL: http://localhost:8585/mvc/games/read

Step 2: Run the Generator

Execute the generator from your project root:

java GenerateTablePage.java

Expected Output:

╔════════════════════════════════════════════════════════════╗
║     AUTO TABLE PAGE GENERATOR                              ║
╚════════════════════════════════════════════════════════════╝

Entity:          Game
Entity Package:  com.open.spring.mvc.rpg.games
Controller Pkg:  com.open.spring.mvc.games
URL Path:        /mvc/games/read

Generating files...
════════════════════════════════════════════════════════════
✓ Created: src/main/java/com/open/spring/mvc/games/GamesMvcController.java
✓ Created: src/main/resources/templates/games/read.html
✓ Created: src/main/resources/templates/games/edit.html
════════════════════════════════════════════════════════════

╔════════════════════════════════════════════════════════════╗
║  ✓ SUCCESS! Everything ready to use.                      ║
╚════════════════════════════════════════════════════════════╝

What Just Happened?

  • ✅ Controller with 5 CRUD endpoints created
  • ✅ Table view with column toggles generated
  • ✅ Edit form with auto-discovered fields created
  • ✅ All endpoints secured with admin-only access

Step 3: Restart Spring Boot

Since we generated new Java files, restart your Spring Boot application:

# Stop the current server (Ctrl+C in terminal)
./mvnw spring-boot:run

Or use VSCode:

  1. Stop the running application
  2. Click Run on Main.java

Wait for:

Tomcat started on port(s): 8585 (http)
Started Main in X.XXX seconds

Step 4: Test Your New Admin Table!

Open the Table View

Navigate to: http://localhost:8585/mvc/games/read

Login Required:

  • Username: toby (or your admin username)
  • Password: From your .env file (ADMIN_PASSWORD)

What You Should See

Column Toggle Buttons at the top:

  • Click any button to show/hide that column
  • Active (green) = visible, Inactive (gray) = hidden

The Table:

  • All entity fields automatically displayed
  • Update/Delete buttons for each row
  • “Create New Game” button at bottom

Import/Export Controls:

  • Export All → Download as JSON
  • Import JSON → Upload data

Step 5: Test CRUD Operations

Create a New Record

  1. Click “Create New Game” button
  2. Fill out the auto-generated form:
    • All entity fields shown as form inputs
    • Textareas for fields with “details”, “description”, “summary”
    • Checkboxes for Boolean fields
    • Regular inputs for everything else
  3. Click Save
  4. Redirected to table → new record appears!

Update an Existing Record

  1. Click Update button on any row
  2. Form pre-filled with current values
  3. Modify fields
  4. Click Save
  5. Changes reflected in table

Delete a Record

  1. Click Delete button on any row
  2. Record removed immediately
  3. Table refreshes

All operations are admin-only! Non-admin users see “Access denied” message.

Verification: Is It Actually Working?

Check 1: Controller Exists

Look for generated controller:

ls src/main/java/com/open/spring/mvc/games/GamesMvcController.java

Check 2: Views Exist

ls src/main/resources/templates/games/read.html
ls src/main/resources/templates/games/edit.html

Check 3: Database Has Data

If table shows 0 rows, check SQLite database:

# From project root
sqlite3 volumes/sqlite.db

# In SQLite shell
SELECT COUNT(*) FROM games;
SELECT * FROM games LIMIT 5;

If count is 0:

  • Entity’s init() method may not be called
  • Check ModelInit.java for seeding logic
  • Create test records via the UI

Check 4: Endpoints Are Accessible

Test URLs (while logged in as admin):

  • http://localhost:8585/mvc/games/read
  • http://localhost:8585/mvc/games/new
  • http://localhost:8585/mvc/games/edit/1

Understanding the Generated Code

The Controller (5 Endpoints)

@Controller
@RequestMapping("/mvc/games")
public class GamesMvcController {
    
    @Autowired
    private GameJPARepository gameJPARepository;
    
    // Checks if user is admin
    private boolean isAdmin(Authentication authentication) { ... }
    
    // Builds table config via reflection
    private TableConfig buildTableConfig() {
        return TableConfigBuilder.fromEntity(Game.class)
                .withEntityName("games")
                .withPaths("/mvc/games/edit", "/mvc/games/delete")
                .build();
    }
    
    @GetMapping("/read")     // List all records
    @GetMapping("/new")      // Show create form
    @GetMapping("/edit/{id}") // Show edit form
    @PostMapping("/save")    // Create or update
    @GetMapping("/delete/{id}") // Delete record
}

The Read View

One line renders the entire table using the auto-table fragment with dynamic columns!

The Edit View

Form fields auto-generated from tableConfig.columns:

  • Loops through discovered fields
  • Skips id, ACTIONS, IMPORT_EXPORT
  • Renders appropriate input type based on field type

Troubleshooting Common Issues

❌ Error: “Ambiguous mapping”

Symptom:

Ambiguous mapping. Cannot map 'gamesMvcController' method
to {GET [/mvc/games/read]}: There is already 'gameMvcController'

Cause: Two controllers mapping to the same URL

Fix:

  1. Find old controller (e.g., GameMvcController.java)
  2. Either delete it or change its @RequestMapping path:
    @RequestMapping("/mvc/games-old") // Rename to avoid conflict
    

❌ Page Loads Forever (ERR_INCOMPLETE_CHUNKED_ENCODING)

Symptom: Browser shows “Loading…” indefinitely

Possible Causes:

  1. Template syntax error - Check Spring Boot console for Thymeleaf errors
  2. Missing fragment - Ensure fragments/auto-table.html exists
  3. Repository not found - Check repository naming matches REPO_NAME

Fix: Check console logs for specific error, restart Spring Boot


❌ Table Shows 0 Rows (But Entity Exists)

Symptom: Table renders but shows empty

Cause: Database table empty

Fix:

// Check ModelInit.java - entity seeding may be disabled
// Example:
Game[] games = Game.init();
for (Game g : games) {
    gameJPARepository.save(g);
}

Or create records via UI: Click “Create New”


❌ Repository Not Found Error

Symptom:

Could not autowire. No beans of 'GameJPARepository' type found.

Cause: Repository naming doesn’t match REPO_NAME assumption

Fix: Generator assumes ENTITY_NAME + "JPARepository". If your repo has a different name (e.g., UnifiedGameRepository), manually edit generated controller to use correct repo name.

Advanced: How Reflection Works

Let’s peek under the hood at TableConfigBuilder.java:

Field Discovery

private List<Field> getAllFields(Class<?> clazz) {
    List<Field> fields = new ArrayList<>();
    
    // Add fields from current class
    fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
    
    // Add fields from superclass (inheritance support)
    if (clazz.getSuperclass() != null && clazz.getSuperclass() != Object.class) {
        fields.addAll(getAllFields(clazz.getSuperclass()));
    }
    
    return fields;
}

Field Filtering

private boolean shouldSkipField(Field field) {
    // Skip static fields
    if (Modifier.isStatic(field.getModifiers())) return true;
    
    // Skip @Transient fields
    if (field.isAnnotationPresent(Transient.class)) return true;
    
    // Skip collections/maps (avoid complex recursion)
    if (Collection.class.isAssignableFrom(field.getType())) return true;
    
    return false;
}

Type Detection

private TableColumn.ColumnType determineColumnType(Field field) {
    Class<?> type = field.getType();
    
    if (type == Boolean.class || type == boolean.class) {
        return TableColumn.ColumnType.CHECKBOX;
    }
    
    if (type == String.class || Number.class.isAssignableFrom(type)) {
        return TableColumn.ColumnType.TEXT;
    }
    
    return TableColumn.ColumnType.TEXT; // Default
}

Reflection Benefits:

  • ✅ No hardcoding field names
  • ✅ Automatically adapts to entity changes
  • ✅ Works with ANY entity structure
  • ✅ Supports inheritance

Try It Yourself: Generate a Resume Table

Let’s practice by generating a CRUD table for the Resume entity!

Exercise Instructions

  1. Open GenerateTablePage.java

  2. Edit the configuration:
    static final String ENTITY_NAME = "Resume";
    static final String ENTITY_PACKAGE = "com.open.spring.mvc.resume";
    static final String PAGE_NAME = "Resumes";
    
  3. Run the generator:
    java GenerateTablePage.java
    
  4. Restart Spring Boot

  5. Visit: http://localhost:8585/mvc/resumes/read

  6. Test CRUD:
    • Create a resume record
    • View it in the table
    • Edit it
    • Delete it

Expected Result

You should see a table showing Resume fields:

  • id
  • username
  • professionalSummary
  • experiences

With full CRUD functionality, all auto-generated in 10 seconds!

Summary: What You Learned

The Power of Automation

  • 97% code reduction: From ~100 lines → 3 configuration lines
  • 180-360x faster: From 60 minutes → 10 seconds
  • Works with ANY entity: Unlimited scalability

Key Concepts

  1. Java Reflection - Runtime type inspection
  2. Generic Templates - One template, infinite entities
  3. Builder Pattern - Fluent configuration API
  4. Admin Security - All endpoints protected

Architecture Components

  • TableConfigBuilder - Reflection engine
  • auto-table.html - Generic Thymeleaf fragment
  • GenerateTablePage.java - Code generator
  • Generated Controllers - Full CRUD endpoints
  • Generated Views - Table + Form pages

Real-World Impact

Instead of manually writing controllers, views, and forms for every entity, you can now:

  • Configure 3 lines
  • Run 1 command
  • Get complete admin CRUD in seconds

This is the power of meta-programming and automation in modern software development!

Challenge: Extend the System

Want to go further? Try these enhancements:

Easy Challenges

  1. Add Custom Field Labels
    • Modify TableConfigBuilder to read @Column(name=...) annotations
    • Use annotation names as display labels
  2. Add Data Validation
    • Generate form validation based on entity constraints
    • Show error messages for invalid inputs
  3. Custom Styling
    • Modify auto-table.html to use your own CSS classes
    • Add custom Bootstrap themes

Medium Challenges

  1. Support @ManyToOne Relationships
    • Detect relationship fields via reflection
    • Render as dropdown selects in edit form
    • Load options from related repository
  2. Add Pagination
    • Modify generated controller to accept page parameters
    • Update template to show pagination controls
  3. Export to CSV/Excel
    • Add export format options beyond JSON
    • Use Apache POI for Excel generation

Hard Challenges

  1. Generate REST API Controllers
    • Extend generator to create REST endpoints
    • Auto-generate @RestController with GET/POST/PUT/DELETE
  2. Add Search/Filter UI
    • Generate search fields based on entity types
    • Build dynamic SQL queries from user input
  3. Create Admin Dashboard
    • Auto-generate cards showing entity statistics
    • Link to all generated CRUD pages

Pick a challenge and level up your automation skills! 🚀