Struts2 – Fetch Records Without Page Refresh Using jQuery AJAX
This tutorial shows how to load data from a MySQL database into a Struts2 page without reloading — using jQuery AJAX. The user types a name in a search box; jQuery sends an AJAX GET request to a Struts2 action, which queries the database and returns an HTML fragment; jQuery inserts that fragment into a <div> on the current page without any full-page reload.
How It Works
User types in search box
↓
jQuery $.get("searchStudent?name=Alice")
↓
Struts2 action: StudentSearchAction.execute()
↓
DAO queries MySQL with PreparedStatement
↓
Results stored in action field
↓
Struts2 forwards to studentResults.jsp (HTML fragment only — no <html> or <body>)
↓
jQuery receives HTML string and inserts it into #results div
MySQL Table
CREATE TABLE student (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
class VARCHAR(255)
);
INSERT INTO student (name, class) VALUES
('Alice', 'Class A'),
('Bob', 'Class B'),
('Arun', 'Class A'),
('Charlie', 'Class C');
StudentBean.java
package com.java9r.model;
public class StudentBean {
private long id;
private String name;
private String studentClass;
public long getId() { return id; }
public String getName() { return name; }
public String getStudentClass() { return studentClass; }
public void setId(long id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setStudentClass(String studentClass) { this.studentClass = studentClass; }
}
StudentDAO.java
The original post used string concatenation in SQL — a SQL injection vulnerability. This rewrite uses PreparedStatement with a LIKE pattern so partial name matches work safely.
package com.java9r.dao;
import com.java9r.db.DBConnection;
import com.java9r.model.StudentBean;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class StudentDAO {
public List<StudentBean> searchByName(String keyword) {
List<StudentBean> list = new ArrayList<>();
// LIKE search — % wildcard on both sides for partial match
String sql = "SELECT id, name, class FROM student WHERE name LIKE ? ORDER BY name";
try (Connection con = DBConnection.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
ps.setString(1, "%" + keyword + "%"); // safe — no SQL injection
ResultSet rs = ps.executeQuery();
while (rs.next()) {
StudentBean s = new StudentBean();
s.setId(rs.getLong("id"));
s.setName(rs.getString("name"));
s.setStudentClass(rs.getString("class"));
list.add(s);
}
} catch (SQLException e) {
e.printStackTrace();
}
return list;
}
}
StudentSearchAction.java
package com.java9r.action;
import com.java9r.dao.StudentDAO;
import com.java9r.model.StudentBean;
import com.opensymphony.xwork2.ActionSupport;
import java.util.Collections;
import java.util.List;
public class StudentSearchAction extends ActionSupport {
private static final long serialVersionUID = 1L;
private String keyword; // bound from ?keyword=... request param
private List<StudentBean> students = Collections.emptyList();
private final StudentDAO dao = new StudentDAO();
@Override
public String execute() {
if (keyword != null && !keyword.trim().isEmpty()) {
students = dao.searchByName(keyword.trim());
}
return SUCCESS;
}
public String getKeyword() { return keyword; }
public void setKeyword(String keyword) { this.keyword = keyword; }
public List<StudentBean> getStudents() { return students; }
}
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.devMode" value="false"/>
<package name="student" namespace="/" extends="struts-default">
<!-- Main search page -->
<action name="index"
class="com.java9r.action.StudentSearchAction">
<result name="success">/index.jsp</result>
</action>
<!-- AJAX endpoint — returns an HTML fragment, not a full page -->
<action name="searchStudent"
class="com.java9r.action.StudentSearchAction">
<result name="success">/studentResults.jsp</result>
</action>
</package>
</struts>
index.jsp – Search Page with jQuery AJAX
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Student Search</title>
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
<h3>Student Search (no page reload)</h3>
<hr>
<div class="col-md-4 mb-3">
<label for="searchBox">Search by Name:</label>
<input type="text" id="searchBox" class="form-control"
placeholder="Type a name...">
</div>
<!-- AJAX results are injected here -->
<div id="results"></div>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
// Fire AJAX on every keystroke (debounced to avoid hammering the server)
var searchTimer;
$('#searchBox').on('input', function () {
clearTimeout(searchTimer);
var keyword = $(this).val().trim();
if (keyword.length === 0) {
$('#results').empty();
return;
}
// Wait 300ms after the user stops typing before sending the request
searchTimer = setTimeout(function () {
$.get('searchStudent', { keyword: keyword })
.done(function (html) {
$('#results').html(html);
})
.fail(function () {
$('#results').html('<p class="text-danger">Error loading results.</p>');
});
}, 300);
});
</script>
</body>
</html>
studentResults.jsp – HTML Fragment (no full page tags)
This JSP is returned as the AJAX response body. It must NOT include <html>, <head>, or <body> tags — jQuery will insert it directly as inner HTML of the #results div.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:choose>
<c:when test="${empty students}">
<p class="text-muted">No students found.</p>
</c:when>
<c:otherwise>
<table class="table table-bordered table-sm">
<thead class="table-primary">
<tr>
<th>ID</th>
<th>Name</th>
<th>Class</th>
</tr>
</thead>
<tbody>
<c:forEach var="s" items="${students}">
<tr>
<td>${s.id}</td>
<td>${s.name}</td>
<td>${s.studentClass}</td>
</tr>
</c:forEach>
</tbody>
</table>
<small class="text-muted">${students.size()} result(s) found.</small>
</c:otherwise>
</c:choose>
How the AJAX Flow Works Step by Step
| Step | What happens |
|---|---|
| 1. User types "Ali" | jQuery input event fires after 300ms debounce |
2. $.get('searchStudent', {keyword:'Ali'}) |
GET request to /searchStudent?keyword=Ali |
3. Struts2 routes to StudentSearchAction.execute() |
Struts2 binds keyword="Ali" via setKeyword() |
4. DAO runs SELECT ... WHERE name LIKE '%Ali%' |
Returns list of matching students |
| 5. Action returns SUCCESS → studentResults.jsp | JSP renders an HTML table fragment |
| 6. jQuery receives HTML string | $('#results').html(html) inserts it into the page |
Original Code Issues Fixed in This Rewrite
| Original | Fixed |
|---|---|
SQL injection: "WHERE name='" + id + "'" |
PreparedStatement with ps.setString(1, "%" + keyword + "%") |
| Raw XMLHttpRequest (verbose, IE-era code) | jQuery $.get() with debounce — cleaner and cross-browser |
Scriptlet code in JSP (<% %>) |
JSTL <c:forEach> and EL ${s.name} |
Exact match only (WHERE name='...') |
Partial match with LIKE %keyword% |
| No debounce — request on every character | 300ms debounce — waits for user to pause typing |
Summary
Struts2 AJAX works by creating a separate action mapping that returns an HTML fragment JSP instead of a full page. jQuery's $.get() calls that URL, receives the HTML string in the callback, and injects it into the DOM with .html(). The key rule: the response JSP must be a fragment (no <html>/<body> tags), since it is embedded inside the already-loaded page. A debounce timeout prevents a database query on every single keystroke, making the search responsive without overloading the server.
Comments