
JSTL(Coreタグライブラリ)入門 – JSP開発の効率化
JSTLとは何か JavaServer Pages Standard Tag Library(JSTL)は、JSPでよく使用される機能をタグ形式で提供する標準ラ […]
以下に、Servlet、JSP、JDBC、フロントコントローラーパターンを活用した3つの課題を提案します。
1. データベース接続用クラス (DatabaseConnector.java)
public class DatabaseConnector {
private static final String URL = "jdbc:mysql://localhost:3306/taskdb";
private static final String USER = "root";
private static final String PASS = "password";
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASS);
}
}
2. モデルクラス (Task.java)
public class Task {
private int id;
private String title;
private String content;
private String priority;
private LocalDate deadline;
// コンストラクタ、ゲッター、セッター省略
}
3. DAOクラス (TaskDAO.java)
public class TaskDAO {
public List findAll() throws SQLException {
List tasks = new ArrayList<>();
String sql = "SELECT * FROM tasks ORDER BY deadline ASC";
try (Connection conn = DatabaseConnector.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Task task = new Task();
task.setId(rs.getInt("id"));
task.setTitle(rs.getString("title"));
task.setContent(rs.getString("content"));
task.setPriority(rs.getString("priority"));
task.setDeadline(rs.getDate("deadline").toLocalDate());
tasks.add(task);
}
}
return tasks;
}
// 他のCRUDメソッドも同様に実装
}
4. フロントコントローラー (FrontController.java)
@WebServlet("*.do")
public class FrontController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String path = request.getServletPath();
switch(path) {
case "/list.do":
new ListTaskController().execute(request, response);
break;
case "/add.do":
new AddTaskController().execute(request, response);
break;
// 他のケースも追加
default:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
5. コントローラーの例 (ListTaskController.java)
public class ListTaskController implements Controller {
@Override
public void execute(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
TaskDAO dao = new TaskDAO();
List tasks = dao.findAll();
request.setAttribute("tasks", tasks);
request.getRequestDispatcher("/WEB-INF/view/list.jsp").forward(request, response);
} catch (SQLException e) {
throw new ServletException(e);
}
}
}
6. JSP (list.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>タスク一覧</title>
</head>
<body>
<h1>タスク一覧</h1>
<table border="1">
<tr>
<th>タイトル</th>
<th>優先度</th>
<th>期限</th>
<th>操作</th>
</tr>
<c:forEach items="${tasks}" var="task">
<tr>
<td>${task.title}</td>
<td>${task.priority}</td>
<td>${task.deadline}</td>
<td>
<a href="edit.do?id=${task.id}">編集</a>
<a href="delete.do?id=${task.id}">削除</a>
</td>
</tr>
</c:forEach>
</table>
<a href="add.do">新規追加</a>
</body>
</html>
1. ユーザーモ型 (User.java)
public class User {
private int id;
private String username;
private String passwordHash;
private String email;
// コンストラクタ、ゲッター、セッター省略
}
2. 認証フィルター (AuthFilter.java)
@WebFilter("/*")
public class AuthFilter implements Filter {
private static final List ALLOWED_PATHS = Arrays.asList(
"/login.do", "/register.do", "/css/", "/js/");
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String path = request.getRequestURI().substring(request.getContextPath().length());
boolean allowedPath = ALLOWED_PATHS.stream().anyMatch(path::startsWith);
if (allowedPath || request.getSession().getAttribute("user") != null) {
chain.doFilter(request, response);
} else {
response.sendRedirect(request.getContextPath() + "/login.do");
}
}
// init, destroyメソッド省略
}
3. ログインコントローラー (LoginController.java)
public class LoginController implements Controller {
@Override
public void execute(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if ("GET".equals(request.getMethod())) {
request.getRequestDispatcher("/WEB-INF/view/login.jsp").forward(request, response);
return;
}
String username = request.getParameter("username");
String password = request.getParameter("password");
try {
UserDAO userDAO = new UserDAO();
User user = userDAO.findByUsername(username);
if (user != null && PasswordUtil.verify(password, user.getPasswordHash())) {
request.getSession().setAttribute("user", user);
response.sendRedirect(request.getContextPath() + "/home.do");
} else {
request.setAttribute("error", "ユーザー名またはパスワードが不正です");
request.getRequestDispatcher("/WEB-INF/view/login.jsp").forward(request, response);
}
} catch (SQLException e) {
throw new ServletException(e);
}
}
}
4. パスワードユーティリティ (PasswordUtil.java)
public class PasswordUtil {
private static final int ITERATIONS = 10000;
private static final int KEY_LENGTH = 256;
private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
public static String hash(String password) {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
byte[] hash = factory.generateSecret(spec).getEncoded();
return ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static boolean verify(String password, String storedHash) {
String[] parts = storedHash.split(":");
int iterations = Integer.parseInt(parts[0]);
byte[] salt = fromHex(parts[1]);
byte[] hash = fromHex(parts[2]);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, hash.length * 8);
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
byte[] testHash = factory.generateSecret(spec).getEncoded();
return Arrays.equals(hash, testHash);
} catch (Exception e) {
return false;
}
}
private static String toHex(byte[] array) {
BigInteger bi = new BigInteger(1, array);
String hex = bi.toString(16);
int paddingLength = (array.length * 2) - hex.length();
if (paddingLength > 0) {
return String.format("%0" + paddingLength + "d", 0) + hex;
} else {
return hex;
}
}
private static byte[] fromHex(String hex) {
byte[] bytes = new byte[hex.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return bytes;
}
}
1. 商品DAO (ProductDAO.java)
public class ProductDAO {
public List<Product> findByCriteria(ProductSearchCriteria criteria) throws SQLException {
List<Product> products = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT * FROM products WHERE 1=1 ");
List<Object> params = new ArrayList<>();
if (criteria.getName() != null && !criteria.getName().isEmpty()) {
sql.append("AND name LIKE ? ");
params.add("%" + criteria.getName() + "%");
}
if (criteria.getCategoryId() != null) {
sql.append("AND category_id = ? ");
params.add(criteria.getCategoryId());
}
if (criteria.getMinPrice() != null) {
sql.append("AND price >= ? ");
params.add(criteria.getMinPrice());
}
if (criteria.getMaxPrice() != null) {
sql.append("AND price <= ? ");
params.add(criteria.getMaxPrice());
}
sql.append("ORDER BY ").append(criteria.getSortField())
.append(" ").append(criteria.getSortDirection())
.append(" LIMIT ? OFFSET ?");
params.add(criteria.getLimit());
params.add(criteria.getOffset());
try (Connection conn = DatabaseConnector.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql.toString())) {
for (int i = 0; i < params.size(); i++) {
stmt.setObject(i + 1, params.get(i));
}
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
Product product = new Product();
product.setId(rs.getInt("id"));
product.setName(rs.getString("name"));
product.setPrice(rs.getInt("price"));
product.setStock(rs.getInt("stock"));
product.setCategoryId(rs.getInt("category_id"));
products.add(product);
}
}
}
return products;
}
public int countByCriteria(ProductSearchCriteria criteria) throws SQLException {
// 同様の検索条件でCOUNTを取得
}
}
2. 検索条件クラス (ProductSearchCriteria.java)
public class ProductSearchCriteria {
private String name;
private Integer categoryId;
private Integer minPrice;
private Integer maxPrice;
private String sortField = "id";
private String sortDirection = "ASC";
private int page = 1;
private int limit = 10;
public int getOffset() {
return (page - 1) * limit;
}
// ゲッター、セッター省略
}
3. 商品リストコントローラー (ListProductController.java)
public class ListProductController implements Controller {
@Override
public void execute(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ProductSearchCriteria criteria = new ProductSearchCriteria();
try {
// リクエストパラメータから検索条件を設定
if (request.getParameter("name") != null) {
criteria.setName(request.getParameter("name"));
}
if (request.getParameter("categoryId") != null) {
criteria.setCategoryId(Integer.parseInt(request.getParameter("categoryId")));
}
if (request.getParameter("minPrice") != null) {
criteria.setMinPrice(Integer.parseInt(request.getParameter("minPrice")));
}
if (request.getParameter("maxPrice") != null) {
criteria.setMaxPrice(Integer.parseInt(request.getParameter("maxPrice")));
}
if (request.getParameter("page") != null) {
criteria.setPage(Integer.parseInt(request.getParameter("page")));
}
ProductDAO productDAO = new ProductDAO();
List products = productDAO.findByCriteria(criteria);
int totalCount = productDAO.countByCriteria(criteria);
request.setAttribute("products", products);
request.setAttribute("criteria", criteria);
request.setAttribute("totalPages", (int) Math.ceil((double) totalCount / criteria.getLimit()));
request.getRequestDispatcher("/WEB-INF/view/product/list.jsp").forward(request, response);
} catch (SQLException | NumberFormatException e) {
throw new ServletException(e);
}
}
}
4. 商品リストJSP (list.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>商品一覧</title>
</head>
<body>
<h1>商品一覧</h1>
<form method="get" action="list.do">
<input type="text" name="name" placeholder="商品名" value="${criteria.name}">
<select name="categoryId">
<option value="">すべてのカテゴリ</option>
<c:forEach items="${categories}" var="category">
<option value="${category.id}"
${criteria.categoryId == category.id ? 'selected' : ''}>
${category.name}
</option>
</c:forEach>
</select>
<input type="number" name="minPrice" placeholder="最低価格" value="${criteria.minPrice}">
<input type="number" name="maxPrice" placeholder="最高価格" value="${criteria.maxPrice}">
<button type="submit">検索</button>
</form>
<table border="1">
<tr>
<th>ID</th>
<th>商品名</th>
<th>価格</th>
<th>在庫</th>
<th>操作</th>
</tr>
<c:forEach items="${products}" var="product">
<tr>
<td>${product.id}</td>
<td>${product.name}</td>
<td>${product.price}</td>
<td>${product.stock}</td>
<td>
<a href="edit.do?id=${product.id}">編集</a>
<a href="delete.do?id=${product.id}">削除</a>
</td>
</tr>
</c:forEach>
</table>
<div class="pagination">
<c:if test="${criteria.page > 1}">
<a href="list.do?page=${criteria.page - 1}&name=${criteria.name}&categoryId=${criteria.categoryId}">前へ</a>
</c:if>
<c:forEach begin="1" end="${totalPages}" var="i">
<c:choose>
<c:when test="${i == criteria.page}">
<strong>${i}</strong>
</c:when>
<c:otherwise>
<a href="list.do?page=${i}&name=${criteria.name}&categoryId=${criteria.categoryId}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${criteria.page < totalPages}">
<a href="list.do?page=${criteria.page + 1}&name=${criteria.name}&categoryId=${criteria.categoryId}">次へ</a>
</c:if>
</div>
<a href="add.do">新規追加</a>
</body>
</html>
これらの課題を通じて、JSPのMVCモデルにおける各コンポーネントの役割と連携を実践的に学ぶことができます。データベース設計やエラーハンドリングなど、さらに拡張できるポイントも多くありますので、必要に応じて機能を追加してみてください。