SQL Forge — a Spring Boot database framework providing JSON CRUD API, type-safe entity operations, SQL template engine, Apache Calcite cross-database federated queries, Amis low-code visual management, and MCP (Model Context Protocol) server for AI tool integration. Ready to use, import on demand.
Import the starters you need:
<!-- Core database operations (required) -->
<dependency>
<groupId>io.github.wb04307201</groupId>
<artifactId>sql-forge-spring-boot-starter</artifactId>
<version>1.5.12</version>
</dependency>
<!-- Calcite cross-database federated queries (optional) -->
<dependency>
<groupId>io.github.wb04307201</groupId>
<artifactId>sql-forge-calcite-spring-boot-starter</artifactId>
<version>1.5.12</version>
</dependency>
<!-- Amis templates + Web Console (optional) -->
<dependency>
<groupId>io.github.wb04307201</groupId>
<artifactId>sql-forge-web-spring-boot-starter</artifactId>
<version>1.5.12</version>
</dependency>For @Id, @Table, @Column annotations in the Entity module, additionally import:
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>| Starter | Description |
|---|---|
| sql-forge-spring-boot-starter | Core starter: database executor, JSON CRUD API, type-safe entity operations (Entity), SQL template engine, Record operation aspects, built-in user authentication (Session + ApiKey) |
| sql-forge-calcite-spring-boot-starter | Apache Calcite-based cross-database federated query executor for MySQL, PostgreSQL, and more |
| sql-forge-web-spring-boot-starter | Amis low-code template management API, Web Console UI, user/role management |
| sql-forge-mcp | Model Context Protocol (MCP) server for AI tool integration — exposes database metadata, SQL execution, and Amis template management as MCP tools via stdio |
Provides core database capabilities including executor management, JSON CRUD API, Entity chain operations, SQL template engine, and Record aspect extensions.
sql-forge-spring-boot-starter automatically registers an executor named database, directly using the DataSource already configured in your Spring project.
sql:
forge:
schemata: # Configure schema
- PUBLICImplement the IExecutor interface and register it as a Spring Bean to extend with custom executors.
@Component
public class MyCustomExecutor implements IExecutor {
@Override
public String getExecutorName() {
return "myCustom";
}
// ... implement other methods
}Provides the ability to execute SQL directly (disabled by default, must be manually enabled).
sql:
forge:
api:
database:
enabled: true # Enable direct database API
select-only: true # true=SELECT only, false=allow all operationsPOST /sql/forge/api/database/execute?executorName=database- Execute SQL
Allows frontend to operate the database without writing backend code — describe the desired data structure and operations via JSON, and the backend automatically generates and executes the corresponding SQL.
- Request Path:
sql/forge/api/json/{method}/{tableName}?executorName={executorName} - Request Method:
POST - Content Type:
application/json - Path Parameters:
{method}: Operation type (delete, insert, select, selectPage, update){tableName}: Database table name{executorName}: Executor name, defaults todatabaseif not provided
{
"@where": [
{
"column": "field_name",
"condition": "condition_type",
"value": "value"
}
],
"@with_select": {
// Query JSON after deletion
}
}Parameters
@where: Delete condition array- column: Field name to match
- condition: Condition type (EQ, NOT_EQ, GT, LT, GTEQ, LTEQ, LIKE, NOT_LIKE, LEFT_LIKE, RIGHT_LIKE, BETWEEN, NOT_BETWEEN, IN, NOT_IN, IS_NULL, IS_NOT_NULL)
- value: Value to match
@with_select: Optional, execute a query after deletion
{
"@set": {
"field1": "value1",
"field2": "value2"
},
"@with_select": {
// Query JSON after insertion
}
}Parameters
@set: Key-value pairs of fields and values to insert, at least one field required@with_select: Optional, execute a query after insertion
{
"@column": ["field1", "field2"],
"@where": [
{
"column": "field_name",
"condition": "condition_type",
"value": "value"
}
],
"@join": [
{
"type": "JOIN type",
"joinTable": "join_table_name",
"on": "join_condition"
}
],
"@order": ["field_name ASC", "field_name DESC"],
"@group": ["field_name"],
"@distince": false
}Parameters
@column: Array of fields to query, queries all fields if empty@where: Query condition array@join: Join condition array@order: Sort field array@group: Group by field array@distince: Whether to deduplicate
{
"@column": ["field1", "field2"],
"@where": [
{
"column": "field_name",
"condition": "condition_type",
"value": "value"
}
],
"@page": {
"pageIndex": 0,
"pageSize": 10
},
"@join": [
{
"type": "JOIN type",
"joinTable": "join_table_name",
"on": "join_condition"
}
],
"@order": ["field_name ASC", "field_name DESC"],
"@distince": false
}Parameters
@column: Array of fields to query, queries all fields if empty@where: Query condition array@page: Pagination parameters (pageIndex starts from 0, pageSize is page size)@join: Join condition array@order: Sort field array@distince: Whether to deduplicate
{
"@set": {
"field1": "new_value1",
"field2": "new_value2"
},
"@where": [
{
"column": "field_name",
"condition": "condition_type",
"value": "value"
}
],
"@with_select": {
// Query JSON after update
}
}Parameters
@set: Key-value pairs of fields and new values, at least one field required@where: Update condition array@with_select: Optional, execute a query after update
- Query
POST http://localhost:8080/sql/forge/api/json/select/orders o
Content-Type: application/json
{
"@column": [
"u.username",
"o.total_amount",
"p.name AS product_name",
"oi.unit_price",
"oi.quantity",
"p.price"
],
"@where": [
{
"column": "u.username",
"condition": "IS_NOT_NULL",
"value": null
}
],
"@join": [
{
"type": "JOIN",
"joinTable": "users u",
"on": "o.user_id = u.id"
},
{
"type": "JOIN",
"joinTable": "order_items oi",
"on": "o.id = oi.order_id"
},
{
"type": "JOIN",
"joinTable": "products p",
"on": "oi.product_id = p.id"
}
],
"@order": [
"o.order_date"
]
}- Paginated Query
Simply add the @page parameter to a query JSON:
POST http://localhost:8080/sql/forge/api/json/selectPage/orders o
Content-Type: application/json
{
"@column": ["o.total_amount", "p.name AS product_name"],
"@join": [
{
"type": "JOIN",
"joinTable": "products p",
"on": "o.product_id = p.id"
}
],
"@order": ["o.order_date"],
"@page": {
"pageIndex": 0,
"pageSize": 5
}
}- Insert
POST http://localhost:8080/sql/forge/api/json/insert/users
Content-Type: application/json
{
"@set": {
"id": "26a05ba3-913d-4085-a505-36d40021c8d1",
"username": "wb04307201",
"password": "123456",
"enabled": true,
"category": "user"
},
"@with_select": {
"@where": [
{
"column": "id",
"condition": "EQ",
"value": "26a05ba3-913d-4085-a505-36d40021c8d1"
}
]
}
}- Update
POST http://localhost:8080/sql/forge/api/json/update/users
Content-Type: application/json
{
"@set": {
"password": "newpassword"
},
"@where": [
{
"column": "id",
"condition": "EQ",
"value": "26a05ba3-913d-4085-a505-36d40021c8d1"
}
],
"@with_select": {
"@where": [
{
"column": "id",
"condition": "EQ",
"value": "26a05ba3-913d-4085-a505-36d40021c8d1"
}
]
}
}- Delete
POST http://localhost:8080/sql/forge/api/json/delete/users
Content-Type: application/json
{
"@where": [
{
"column": "id",
"condition": "EQ",
"value": "26a05ba3-913d-4085-a505-36d40021c8d1"
}
],
"@with_select": {
"@where": [
{
"column": "id",
"condition": "EQ",
"value": "26a05ba3-913d-4085-a505-36d40021c8d1"
}
]
}
}Customize JSON adjustments before method execution by implementing the IBeforeRecordExecutor interface — for password encryption, auto-timestamps, access control, logging, auditing, etc.
For example, logging on Insert:
@Slf4j
@Component
public class LogInsertExecute implements IBeforeRecordExecutor<Insert> {
@Override
public Boolean support(String tableName, Insert insert) {
return true;
}
@Override
public Insert before(String tableName, Insert insert) {
log.info("LogInsertExecute tableName: {} record: {}", tableName, insert);
return insert;
}
}The core starter includes a built-in user authentication system. All APIs require authentication by default, supporting both Session login and ApiKey — either one is sufficient to access.
Pass the ApiKey via the X-Api-Key request header to access all APIs without logging in:
sql:
forge:
api-keys: # ApiKey list (empty by default, meaning ApiKey authentication is disabled)
- sk-your-api-key-here
- sk-another-keyRequest example:
GET http://localhost:8080/sql/forge/api/json/select/users
X-Api-Key: sk-your-api-key-hereObtain a Session via the login endpoint; subsequent requests automatically carry the Session cookie:
POST /sql/forge/api/auth/login- User login (Body:{"username": "admin", "password": "admin123"})POST /sql/forge/api/auth/logout- User logoutGET /sql/forge/api/auth/status- Get current login statusGET /sql/forge/api/auth/user- Get current user info
Default admin account:
admin/admin123
Request → Valid ApiKey? → Allow
→ Session logged in? → Allow
→ Neither → 401 Denied
Whitelisted paths (login endpoints, static resources) are accessible without authentication.
Provides SQL template functionality supporting conditionals (<if>), loops (<foreach>), and variable binding (#{var}), dynamically generating and executing SQL based on parameters.
PUT /sql/forge/api/template/sql- Save/Update SQL template- id: Template ID
- executorName: Executor name, defaults to
database - context: Template content
GET /sql/forge/api/template/sql/{id}- Get SQL template by IDGET /sql/forge/api/template/sql- Get SQL template listDELETE /sql/forge/api/template/sql/{id}- Delete SQL template by IDPOST /sql/forge/api/template/sql/{id}- Execute SQL template by ID (Body is a parameter Map)
Template configuration:
PUT http://localhost:8080/sql/forge/api/template/sql
content-type: application/json
{
"id": "sql-template-database",
"type": "templateSql",
"executorName": "database",
"context": "SELECT * FROM users WHERE 1=1\r\n<if test=\"name != null && name != ''\">AND username = #{name}</if>\r\n<if test=\"ids != null && !ids.isEmpty()\"><foreach collection=\"ids\" item=\"id\" open=\"AND id IN (\" separator=\",\" close=\")\">#{id}</foreach></if>\r\n<if test=\"(name == null || name == '') && (ids == null || ids.isEmpty()) \">AND 0=1</if>\r\nORDER BY username DESC"
}Execute template:
POST http://localhost:8080/sql/forge/api/template/sql/sql-template-database
content-type: application/json
{
"name": "alice",
"ids": null
}Response:
[
{
"ID": "1",
"USERNAME": "alice"
}
]Uses in-memory storage by default. Implement ITemplateSqlStorage for custom persistence.
Provides type-safe entity operation builders with compile-time safe field references via Lambda expressions, supporting chain calls.
- Entity — Static utility class providing
select/insert/update/delete/save/selectPageentry points - EntityExecutor — Executes builder database operations
- Chain calls for concise code
- Lambda expressions for compile-time field reference checking
- Builder pattern for flexible query condition configuration
- Unified database operation entry point
Define a user entity class:
@Data
@Table(name = "users")
public class User {
@Id
private String id;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "category")
private String category;
}Database operations using Entity:
@Autowired
private EntityExecutor entityExecutor;
// Query operation
EntitySelect<User> select = Entity.select(User.class)
.distinct(true)
.columns(User::getId, User::getUsername, User::getCategory)
.orders(User::getUsername)
.in(User::getUsername, "alice", "bob");
List<User> users = entityExecutor.run(select);
// Paginated query operation
EntitySelectPage<User> selectPage = Entity.selectPage(User.class)
.columns(User::getId, User::getUsername, User::getCategory)
.orders(User::getUsername)
.page(0, 10);
SelectPageResult<User> result = entityExecutor.run(selectPage);
// Insert operation
EntityInsert<User> insert = Entity.insert(User.class)
.set(User::getId, UUID.randomUUID().toString())
.set(User::getUsername, "wb04307201")
.set(User::getPassword, "123456");
entityExecutor.run(insert);
// Update operation
EntityUpdate<User> update = Entity.update(User.class)
.set(User::getPassword, "newpassword")
.eq(User::getId, id);
int count = entityExecutor.run(update);
// Delete operation
EntityDelete<User> delete = Entity.delete(User.class)
.eq(User::getId, id);
count = entityExecutor.run(delete);
// Object save (auto-detects insert or update)
User user = new User();
user.setUsername("wb04307201");
user.setPassword("123456");
user = entityExecutor.run(Entity.save(user)); // id is null, performs insert
user.setPassword("newpassword");
user = entityExecutor.run(Entity.save(user)); // id is not null, performs update
// Object delete
count = entityExecutor.run(Entity.delete(user));1. Column Selection
column(SFunction<T, ?> column)- Select single columncolumns(SFunction<T, ?>... columns)- Select multiple columns
2. Query Conditions
eq(SFunction<T, ?> column, Object value)- Equalsneq(SFunction<T, ?> column, Object value)- Not equalsgt(SFunction<T, ?> column, Object value)- Greater thanlt(SFunction<T, ?> column, Object value)- Less thangteq(SFunction<T, ?> column, Object value)- Greater than or equalslteq(SFunction<T, ?> column, Object value)- Less than or equalslike(SFunction<T, ?> column, Object value)- LikenotLike(SFunction<T, ?> column, Object value)- Not like (NOT LIKE)leftLike(SFunction<T, ?> column, Object value)- Left likerightLike(SFunction<T, ?> column, Object value)- Right likebetween(SFunction<T, ?> column, Object value1, Object value2)- BetweennotBetween(SFunction<T, ?> column, Object value1, Object value2)- Not betweenin(SFunction<T, ?> column, Object... value)- InnotIn(SFunction<T, ?> column, Object... value)- Not inisNull(SFunction<T, ?> column)- Is NULLisNotNull(SFunction<T, ?> column)- Is NOT NULL
3. Sorting
orderAsc(SFunction<T, ?> column)- Ascending sortorderDesc(SFunction<T, ?> column)- Descending sortorders(SFunction<T, ?>... columns)- Multi-column sorting (default ascending)
4. Pagination
page(Integer pageIndex, Integer pageSize)- Set pagination parameters
5. Deduplication
distinct(Boolean distinct)- Set whether to deduplicate
Determines primary key field based on @Id annotation. Throws IllegalArgumentException if no primary key field exists.
- Insert condition: Executes insert when primary key value is
nullStringtype primary key: Automatically generatesUUIDas primary key value- Other type primary keys: Uses database auto-generated primary key value
- Update condition: Executes update when primary key value is not
null- Uses primary key value as update condition
Note: To insert a new record with a pre-set primary key value, use
Entity.insert()instead ofEntity.save(), becausesave()performs an update when the primary key is notnull.
Based on Apache Calcite, enables cross-database federated queries — join data from MySQL, PostgreSQL, and other databases in a single SQL statement.
Depends on the core starter (
sql-forge-spring-boot-starter), which is automatically included.
sql:
forge:
calcite:
enabled: true # Enable Calcite
configuration: classpath:model.json # Calcite model configuration file path
schemata: # Configure schema
- MYSQL
- POSTGRESmodel.json describes the Calcite data source connection information. Refer to the Apache Calcite documentation.
Once enabled, a calcite executor is automatically registered. Use executorName=calcite in any API:
POST http://localhost:8080/sql/forge/api/json/select/orders?executorName=calcite
Content-Type: application/json
{
"@column": ["o.id", "u.username", "p.name"],
"@join": [
{
"type": "JOIN",
"joinTable": "MYSQL_DB.users u",
"on": "o.user_id = u.id"
},
{
"type": "JOIN",
"joinTable": "POSTGRES_DB.products p",
"on": "o.product_id = p.id"
}
]
}Provides Amis low-code template management, Web Console UI, and user/role management.
Depends on the core starter (
sql-forge-spring-boot-starter), which is automatically included.
Use Amis together with JSON API and SQL Template API to rapidly build web pages.
PUT /sql/forge/api/template/amis- Save/Update Amis template- id: Template ID
- name: Template name
- description: Template description
- context: Amis JSON Schema content
GET /sql/forge/api/template/amis/{id}- Get template by IDGET /sql/forge/api/template/amis- Get template listDELETE /sql/forge/api/template/amis/{id}- Delete template by ID
PUT http://localhost:8080/sql/forge/api/template/amis
content-type: application/json
{
"id": "amis-template-users",
"name": "User Management",
"context": "{ \"type\": \"page\", \"body\": { \"type\": \"crud\", ... } }"
}Rendered page:
Uses in-memory storage by default. Implement ITemplateAmisStorage for custom persistence.
Provides a visual web interface at: /sql/forge/web
- Database metadata viewing and SQL debugging (requires
sql.forge.api.database.enabled=true)
- JSON API debugging

- SQL template management and debugging

- Amis template management and debugging

The authentication system (Session login + ApiKey) is built into the core starter. See 1.4 Authentication & ApiKey for details. This section only describes user/role management features specific to Web Console.
GET /sql/forge/api/user- Get user listPUT /sql/forge/api/user- Save/Update userDELETE /sql/forge/api/user/{id}- Delete user
GET /sql/forge/api/role- Get role listPUT /sql/forge/api/role- Save/Update role (admin required)DELETE /sql/forge/api/role/{id}- Delete role (admin required)GET /sql/forge/api/role-template?role={roleId}- Get template IDs associated with a rolePUT /sql/forge/api/role-template- Set role-template associations (admin required)GET /sql/forge/api/user-role?userId={userId}- Get role IDs for a user (admin required)PUT /sql/forge/api/user-role- Set user-role associations (admin required)
All storage uses in-memory implementations by default. Replace with database persistence by implementing the following interfaces:
| Interface | Description |
|---|---|
| IUserStorage | User storage |
| IUserRoleStorage | User-role association storage |
| IRoleStorage | Role storage |
| IRoleTemplateStorage | Role-template association storage |
Simply implement the interface and register as a Spring Bean to automatically replace the default implementation (@ConditionalOnMissingBean).
A Model Context Protocol (MCP) server that exposes sql-forge backend capabilities as AI tools via stdio transport. AI assistants (Claude Desktop, Cursor, Windsurf, etc.) can query database metadata, execute SQL, and manage Amis templates through MCP tool calls.
Requires a running sql-forge backend (with
sql.forge.api.database.enabled=true).
| Tool | Description |
|---|---|
getSystems |
Get all configured system information |
getMetaDataDatabase |
Get database product name and version for a system |
sqlForgeMetaDataTables |
Get all tables in a system's database |
getMetaDataTableInfo |
Get table structure: columns, primary keys, foreign keys, indexes |
executeSQL |
Execute a SQL query and return the result set |
amisTemplateSave |
Save an Amis page JSON template configuration |
Use jbang to run the MCP server without local installation — configure your MCP client (Claude Desktop, Cursor, etc.) as follows:
{
"mcpServers": {
"sql-forge-mcp": {
"command": "jbang.cmd",
"args": [
"io.github.wb04307201:sql-forge-mcp:1.5.12",
"--sql.forge.mcp.systems[0].name=OrderSystem",
"--sql.forge.mcp.systems[0].url=http://localhost:8081",
"--sql.forge.mcp.systems[0].description=Order system containing system tables: users, roles, dictionaries, etc. Business tables: products, orders, payment records, user addresses, inventory, order logistics, product categories, product reviews, etc.",
"--sql.forge.mcp.systems[0].apiKey=test"
]
}
}
}Configure multiple systems to connect one MCP server to several sql-forge backends:
{
"mcpServers": {
"sql-forge-mcp": {
"command": "jbang.cmd",
"args": [
"io.github.wb04307201:sql-forge-mcp:1.5.12",
"--sql.forge.mcp.systems[0].name=OrderSystem",
"--sql.forge.mcp.systems[0].url=http://localhost:8081",
"--sql.forge.mcp.systems[0].description=Order system",
"--sql.forge.mcp.systems[0].apiKey=test",
"--sql.forge.mcp.systems[1].name=InventorySystem",
"--sql.forge.mcp.systems[1].url=http://localhost:8082",
"--sql.forge.mcp.systems[1].description=Inventory system",
"--sql.forge.mcp.systems[1].apiKey=test"
]
}
}
}sql:
forge:
schemata: # Configure schema names
- PUBLIC
api-keys: # ApiKey list (optional, enables X-Api-Key header access without login)
- sk-your-api-key
calcite:
enabled: true # Enable Calcite cross-database federated queries
configuration: classpath:model.json
api:
database:
enabled: true # Enable direct database API
select-only: true # true=SELECT only
json:
enabled: true # Enable JSON CRUD API (default true)
template:
sql:
enabled: true # Enable SQL template API (default true)
amis:
enabled: true # Enable Amis template API (default true)
console:
enabled: true # Enable Web Console (default true)