Skip to content

Commit 05931ad

Browse files
codebase/text-to-sql-implementation-using-spring-ai [BAEL-9333] (#18616)
* init project structure * implement text-to-sql chatbot * add test cases * fix indentation * use package-private visibility for test bean * rename modules * downgrade MySQL version * add aggregate query test case * use system prompt
1 parent 075614d commit 05931ad

18 files changed

+652
-0
lines changed

pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,7 @@
764764
<module>spring-ai</module>
765765
<module>spring-ai-2</module>
766766
<module>spring-ai-3</module>
767+
<module>spring-ai-modules</module>
767768
<module>spring-aop</module>
768769
<module>spring-aop-2</module>
769770
<module>spring-batch</module>
@@ -1195,6 +1196,7 @@
11951196
<module>spring-ai</module>
11961197
<module>spring-ai-2</module>
11971198
<module>spring-ai-3</module>
1199+
<module>spring-ai-modules</module>
11981200
<module>spring-aop</module>
11991201
<module>spring-aop-2</module>
12001202
<module>spring-batch</module>

spring-ai-modules/pom.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<artifactId>spring-ai-modules</artifactId>
7+
<version>0.0.1</version>
8+
<packaging>pom</packaging>
9+
<name>spring-ai-modules</name>
10+
11+
<parent>
12+
<groupId>com.baeldung</groupId>
13+
<artifactId>parent-boot-3</artifactId>
14+
<version>0.0.1-SNAPSHOT</version>
15+
<relativePath>../parent-boot-3</relativePath>
16+
</parent>
17+
18+
<modules>
19+
<module>spring-ai-text-to-sql</module>
20+
</modules>
21+
22+
</project>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<parent>
7+
<groupId>com.baeldung</groupId>
8+
<artifactId>spring-ai-modules</artifactId>
9+
<version>0.0.1</version>
10+
<relativePath>../pom.xml</relativePath>
11+
</parent>
12+
13+
<groupId>com.baeldung</groupId>
14+
<artifactId>spring-ai-text-to-sql</artifactId>
15+
<version>0.0.1</version>
16+
<name>spring-ai-text-to-sql</name>
17+
18+
<dependencies>
19+
<!-- Core dependencies -->
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-web</artifactId>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.springframework.ai</groupId>
26+
<artifactId>spring-ai-starter-model-anthropic</artifactId>
27+
<version>${spring-ai.version}</version>
28+
</dependency>
29+
30+
<!-- Database dependencies -->
31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-starter-data-jpa</artifactId>
34+
</dependency>
35+
<dependency>
36+
<groupId>com.mysql</groupId>
37+
<artifactId>mysql-connector-j</artifactId>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.flywaydb</groupId>
41+
<artifactId>flyway-mysql</artifactId>
42+
</dependency>
43+
44+
<!-- Test dependencies -->
45+
<dependency>
46+
<groupId>org.springframework.boot</groupId>
47+
<artifactId>spring-boot-starter-test</artifactId>
48+
<scope>test</scope>
49+
</dependency>
50+
<dependency>
51+
<groupId>org.springframework.boot</groupId>
52+
<artifactId>spring-boot-testcontainers</artifactId>
53+
<scope>test</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.testcontainers</groupId>
57+
<artifactId>mysql</artifactId>
58+
<scope>test</scope>
59+
</dependency>
60+
</dependencies>
61+
62+
<properties>
63+
<java.version>21</java.version>
64+
<spring-ai.version>1.0.0</spring-ai.version>
65+
</properties>
66+
67+
<build>
68+
<plugins>
69+
<plugin>
70+
<groupId>org.springframework.boot</groupId>
71+
<artifactId>spring-boot-maven-plugin</artifactId>
72+
</plugin>
73+
</plugins>
74+
</build>
75+
76+
</project>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.baeldung.texttosql;
2+
3+
import org.springframework.http.ProblemDetail;
4+
import org.springframework.web.bind.annotation.ExceptionHandler;
5+
import org.springframework.web.bind.annotation.RestControllerAdvice;
6+
import org.springframework.web.server.ResponseStatusException;
7+
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
8+
9+
@RestControllerAdvice
10+
class ApiExceptionHandler extends ResponseEntityExceptionHandler {
11+
12+
@ExceptionHandler(ResponseStatusException.class)
13+
ProblemDetail handle(ResponseStatusException exception) {
14+
return ProblemDetail.forStatusAndDetail(exception.getStatusCode(), exception.getReason());
15+
}
16+
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.baeldung.texttosql;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
@SpringBootApplication
7+
class Application {
8+
9+
public static void main(String[] args) {
10+
SpringApplication.run(Application.class, args);
11+
}
12+
13+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.baeldung.texttosql;
2+
3+
import org.springframework.ai.chat.client.ChatClient;
4+
import org.springframework.ai.chat.model.ChatModel;
5+
import org.springframework.ai.chat.prompt.PromptTemplate;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.core.io.Resource;
10+
11+
import java.io.IOException;
12+
import java.nio.charset.Charset;
13+
14+
@Configuration
15+
class ChatbotConfiguration {
16+
17+
@Bean
18+
PromptTemplate systemPrompt(
19+
@Value("classpath:system-prompt.st") Resource systemPrompt,
20+
@Value("classpath:db/migration/V01__creating_database_tables.sql") Resource ddlSchema
21+
) throws IOException {
22+
PromptTemplate template = new PromptTemplate(systemPrompt);
23+
template.add("ddl", ddlSchema.getContentAsString(Charset.defaultCharset()));
24+
return template;
25+
}
26+
27+
@Bean
28+
ChatClient chatClient(ChatModel chatModel, PromptTemplate systemPrompt) {
29+
return ChatClient
30+
.builder(chatModel)
31+
.defaultSystem(systemPrompt.render())
32+
.build();
33+
}
34+
35+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.baeldung.texttosql;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.web.server.ResponseStatusException;
5+
6+
class EmptyResultException extends ResponseStatusException {
7+
8+
EmptyResultException(String reason) {
9+
super(HttpStatus.NOT_FOUND, reason);
10+
}
11+
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.baeldung.texttosql;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.web.server.ResponseStatusException;
5+
6+
class InvalidQueryException extends ResponseStatusException {
7+
8+
InvalidQueryException(String reason) {
9+
super(HttpStatus.BAD_REQUEST, reason);
10+
}
11+
12+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.baeldung.texttosql;
2+
3+
import org.springframework.http.ResponseEntity;
4+
import org.springframework.web.bind.annotation.PostMapping;
5+
import org.springframework.web.bind.annotation.RequestBody;
6+
import org.springframework.web.bind.annotation.RestController;
7+
8+
import java.util.List;
9+
10+
@RestController
11+
class QueryController {
12+
13+
private final SqlExecutor sqlExecutor;
14+
private final SqlGenerator sqlGenerator;
15+
16+
QueryController(SqlExecutor sqlExecutor, SqlGenerator sqlGenerator) {
17+
this.sqlExecutor = sqlExecutor;
18+
this.sqlGenerator = sqlGenerator;
19+
}
20+
21+
@PostMapping(value = "/query")
22+
ResponseEntity<QueryResponse> query(@RequestBody QueryRequest queryRequest) {
23+
String sqlQuery = sqlGenerator.generate(queryRequest.question());
24+
List<?> result = sqlExecutor.execute(sqlQuery);
25+
return ResponseEntity.ok(new QueryResponse(result));
26+
}
27+
28+
record QueryRequest(String question) {
29+
}
30+
31+
record QueryResponse(List<?> result) {
32+
}
33+
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.baeldung.texttosql;
2+
3+
import jakarta.persistence.EntityManager;
4+
import org.springframework.stereotype.Service;
5+
6+
import java.util.List;
7+
8+
@Service
9+
class SqlExecutor {
10+
11+
private final EntityManager entityManager;
12+
13+
SqlExecutor(EntityManager entityManager) {
14+
this.entityManager = entityManager;
15+
}
16+
17+
List<?> execute(String query) {
18+
List<?> result = entityManager
19+
.createNativeQuery(query)
20+
.getResultList();
21+
if (result.isEmpty()) {
22+
throw new EmptyResultException("No results found for the provided query.");
23+
}
24+
return result;
25+
}
26+
27+
}

0 commit comments

Comments
 (0)