原文:http://zetcode.com/java/tomcatderby/

在本教程中,我们将使用 Tomcat 和 Derby。 该应用分为四个层,它具有一个控制器,并使用 DAO 访问数据。 对于项目创建,我们使用 NetBeans IDE。 源代码可从作者的 Github TomcatDerby 仓库中获得。

现代 Java Web 应用主要通过使用诸如 Spring 或 Vaadin 之类的框架来创建。 但是有必要了解基础。

我们使用pure.css库来帮助创建用户界面。

Apache Derby 是完全用 Java 实现的开源关系数据库。 它占地面积小,易于部署和安装。 它支持嵌入式和客户端/服务器模式。

Apache Tomcat 是 Java Servlet,JavaServer Pages,Java Expression Language 和 Java WebSocket 技术的开源实现。

数据访问对象(DAO) 是为数据库或其他持久性机制提供抽象接口的对象。 DAO 完全向其客户端隐藏了数据源实现细节。 它充当组件和数据源之间的适配器。

Java Web 应用

我们在 NetBeans 中创建一个新的 Web 应用。 该应用管理一个简单的Cars表。 它将创建新的汽车,检索一辆汽车和所有汽车。

该应用分为四个层:表示层,模型层,服务层和持久层。 Web 应用的多层设置是重要的软件开发模式。

  1. $ tree
  2. .
  3. ├── nb-configuration.xml
  4. ├── pom.xml
  5. ├── README.md
  6. └── src
  7. ├── main
  8. ├── java
  9. └── com
  10. └── zetcode
  11. ├── bean
  12. └── Car.java
  13. ├── persistence
  14. ├── CarDAO.java
  15. ├── Executable.java
  16. └── JdbcDAO.java
  17. ├── service
  18. ├── CarsService.java
  19. └── ICarsService.java
  20. ├── util
  21. ├── DBUtils.java
  22. ├── ServiceLocator.java
  23. └── ValidateParameter.java
  24. └── web
  25. └── Controller.java
  26. └── webapp
  27. ├── allCars.jsp
  28. ├── carSaved.jsp
  29. ├── index.jsp
  30. ├── META-INF
  31. └── context.xml
  32. ├── readCarId.jsp
  33. ├── readCar.jsp
  34. ├── showCar.jsp
  35. ├── unknown.jsp
  36. ├── WEB-INF
  37. └── wrongParams.jsp
  38. └── test
  39. └── java

这是项目结构。

pom.xml

  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
  5. http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6. <modelVersion>4.0.0</modelVersion>
  7. <groupId>com.mycompany</groupId>
  8. <artifactId>DerbyTomcat</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10. <packaging>war</packaging>
  11. <name>DerbyTomcat</name>
  12. <properties>
  13. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  14. <maven.compiler.source>1.8</maven.compiler.source>
  15. <maven.compiler.target>1.8</maven.compiler.target>
  16. </properties>
  17. <dependencies>
  18. <dependency>
  19. <groupId>javax.servlet</groupId>
  20. <artifactId>javax.servlet-api</artifactId>
  21. <version>4.0.0</version>
  22. <scope>provided</scope>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.apache.derby</groupId>
  26. <artifactId>derbyclient</artifactId>
  27. <version>10.14.1.0</version>
  28. </dependency>
  29. <dependency>
  30. <groupId>javax.servlet</groupId>
  31. <artifactId>jstl</artifactId>
  32. <version>1.2</version>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.apache.commons</groupId>
  36. <artifactId>commons-lang3</artifactId>
  37. <version>3.7</version>
  38. </dependency>
  39. </dependencies>
  40. <build>
  41. <plugins>
  42. <plugin>
  43. <groupId>org.apache.maven.plugins</groupId>
  44. <artifactId>maven-war-plugin</artifactId>
  45. <version>3.2.0</version>
  46. <configuration>
  47. <failOnMissingWebXml>false</failOnMissingWebXml>
  48. </configuration>
  49. </plugin>
  50. </plugins>
  51. </build>
  52. </project>

pom.xml包含项目依赖项:servlet 的 JAR,JSP 页面,Derby 数据库驱动程序,JSTL 库以及某些帮助程序类的 Apache Commons Lang JAR。

Tomcat Derby 教程 - 图1

图:数据库创建

在“服务”选项卡中,我们右键单击 Java DB 节点,然后选择“创建数据库”选项。 我们给它命名为testdb。 该数据库位于用户主目录的.netbeans_derby目录中。

cars.sql

  1. CREATE TABLE CARS(ID BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY
  2. (START WITH 1, INCREMENT BY 1), NAME VARCHAR(30), PRICE INT);
  3. INSERT INTO CARS(Name, Price) VALUES('Audi', 52642);
  4. INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127);
  5. INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000);
  6. INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000);
  7. INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000);
  8. INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000);
  9. INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400);
  10. INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600);

这是创建Cars表的 SQL。 汽车对象的 ID 会自动增加。 我们可以使用 NetBeans 工具创建Cars表。 我们右键单击“数据库”节点,然后选择“新建连接”选项。

Tomcat Derby 教程 - 图2

图:连接向导

我们在连接向导中填写必要的详细信息。 我们使用 Derby 网络驱动程序; Derby 的端口是 1527。

Tomcat Derby 教程 - 图3

图:连接

创建一个新的连接对象; 它由橙色图标表示。 其上下文菜单提供了用于连接到指定数据库并执行命令的选项。 “执行命令”选项显示了执行 SQL 命令的工具。 在此窗口中,我们可以使用上面的 SQL 创建Cars表。

Tomcat Derby 教程 - 图4

图:NetBeans Derby 工具

NetBeans 具有有用的 Derby 工具,可用于管理 Derby 数据库。

context.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <Context path="/DerbyTomcat">
  3. <Resource name="jdbc/testdb"
  4. auth="Container"
  5. type="javax.sql.DataSource"
  6. driverClassName="org.apache.derby.jdbc.ClientDriver"
  7. username="app"
  8. password="app"
  9. url="jdbc:derby://localhost:1527/testdb"
  10. maxActive="10" maxIdle="4"
  11. />
  12. </Context>

META-INF目录中的context.xml文件中,我们提供了数据源。 使用 JNDI API 查找资源。

模型层

模型层具有Car类。

Car.java

  1. package com.zetcode.bean;
  2. import java.util.Objects;
  3. public class Car {
  4. private Long id;
  5. private String name;
  6. private int price;
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public int getPrice() {
  14. return price;
  15. }
  16. public void setPrice(int price) {
  17. this.price = price;
  18. }
  19. public Long getId() {
  20. return id;
  21. }
  22. public void setId(Long id) {
  23. this.id = id;
  24. }
  25. @Override
  26. public int hashCode() {
  27. int hash = 7;
  28. hash = 61 * hash + Objects.hashCode(this.id);
  29. hash = 61 * hash + Objects.hashCode(this.name);
  30. hash = 61 * hash + this.price;
  31. return hash;
  32. }
  33. @Override
  34. public boolean equals(Object obj) {
  35. if (this == obj) {
  36. return true;
  37. }
  38. if (obj == null) {
  39. return false;
  40. }
  41. if (getClass() != obj.getClass()) {
  42. return false;
  43. }
  44. final Car other = (Car) obj;
  45. if (this.price != other.price) {
  46. return false;
  47. }
  48. if (!Objects.equals(this.name, other.name)) {
  49. return false;
  50. }
  51. return Objects.equals(this.id, other.id);
  52. }
  53. }

Car类具有三个属性以及相应的获取器和设置器方法。

表示层

应用的表示层包含 JSP 页面,这些页面构建了应用的用户界面。

index.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Home page</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. </head>
  11. <body>
  12. <h2>Home page</h2>
  13. <p>Available actions:</p>
  14. <ul>
  15. <li><a href="controller?action=listcars">Show all</a></li>
  16. <li><a href="controller?action=readbyid">Show car by ID</a></li>
  17. <li><a href="controller?action=readcar">Create a new car</a></li>
  18. </ul>
  19. <a href="<%= request.getContextPath() %>">Home</a>
  20. </body>
  21. </html>

index.jsp页面包含三个可用操作的链接:显示所有汽车,显示通过其 ID 找到的汽车以及创建新汽车。

  1. <a href="<%= request.getContextPath() %>">Home</a>

使用getContextPath()方法,我们可以获得主页路径。

allCars.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Cars</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
  11. integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
  12. crossorigin="anonymous">
  13. <style>
  14. body { padding:1em }
  15. nav { margin-top: 2em }
  16. </style>
  17. </head>
  18. <body>
  19. <h2>All cars</h2>
  20. <table class="pure-table pure-table-horizontal">
  21. <thead>
  22. <tr>
  23. <th>Id</th>
  24. <th>Name</th>
  25. <th>Price</th>
  26. </tr>
  27. </thead>
  28. <c:forEach items="${carList}" var='car'>
  29. <tr>
  30. <td>
  31. <c:out value="${car.id}"/>
  32. </td>
  33. <td>
  34. <c:out value="${car.name}"/>
  35. </td>
  36. <td>
  37. <c:out value="${car.price}"/>
  38. </td>
  39. </tr>
  40. </c:forEach>
  41. </table>
  42. <a href="<%= request.getContextPath() %>">Home</a>
  43. </body>
  44. </html>

allCars.jsp页面中,JSTL 的<c:forEach><c:out>标签用于打印每个返回的汽车对象的属性。

  1. <table class="pure-table pure-table-horizontal">

我们使用pure.css类来设计表。

readCar.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true" %>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Car details</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
  11. integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
  12. crossorigin="anonymous">
  13. <style>
  14. body { padding:1em }
  15. nav { margin-top: 2em }
  16. </style>
  17. </head>
  18. <body>
  19. <form class="pure-form pure-form-stacked" action="controller?action=savecar" method="post">
  20. <legend>Enter car details:</legend>
  21. <label for="carName">Name:</label>
  22. <input id="carName" type="text" name="carName">
  23. <label for="carPrice">Price:</label>
  24. <input id ="carPrice" type="text" name="carPrice">
  25. <button class="pure-button pure-button-primary" type="submit">Submit</button>
  26. </form>
  27. <a href="<%= request.getContextPath() %>">Home</a>
  28. </body>
  29. </html>

readCar.jsp页面上,我们有一个表格来输入新车的详细信息。

readCarId.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Enter car ID</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. <link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
  11. integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
  12. crossorigin="anonymous">
  13. <style>
  14. body { padding:1em }
  15. nav { margin-top:2em }
  16. </style>
  17. </head>
  18. <body>
  19. <form class="pure-form pure-form-stacked" action="controller">
  20. <legend>Enter car Id</legend>
  21. <input type="hidden" name="action" value="viewcar">
  22. <label for="carId">Id:</label>
  23. <input id="carId" type="text" name="carId">
  24. <button class="pure-button pure-button-primary" type="submit">Submit</button>
  25. </form>
  26. <a href="<%= request.getContextPath() %>">Home</a>
  27. </body>
  28. </html>

readCarId.jsp文件中,我们有一个表格来输入我们要检索的汽车 ID。

carSaved.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Car saved</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. </head>
  11. <body>
  12. <p>
  13. Successfully saved <c:out value="${sessionScope.carName}"/>
  14. car priced <c:out value="${sessionScope.carPrice}"/>
  15. </p>
  16. <a href="<%= request.getContextPath() %>">Home</a>
  17. </body>
  18. </html>

carSaved.jsp页面中,我们仅通知您已保存具有给定名称和价格的汽车。 我们使用 JSTL 库中的<c:out>标签。

showCar.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Returned car</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. </head>
  11. <body>
  12. <h2>Car details</h2>
  13. <ul>
  14. <li>ID: <c:out value="${returnedCar.id}"/></li>
  15. <li>Name: <c:out value="${returnedCar.name}"/></li>
  16. <li>Price: <c:out value="${returnedCar.price}"/></li>
  17. </ul>
  18. <a href="<%= request.getContextPath() %>">Home</a>
  19. </body>
  20. </html>

showCar.jsp页面中,我们显示检索到的汽车的属性。

unknown.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Unknown action</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. </head>
  11. <body>
  12. <h2>Unknown action</h2>
  13. <a href="<%= request.getContextPath() %>">Home</a>
  14. </body>
  15. </html>

当控制器收到未定义的动作时,将显示unknown.jsp页面。

wrongParams.jsp

  1. <%@page contentType="text/html" pageEncoding="UTF-8"%>
  2. <%@page trimDirectiveWhitespaces="true"%>
  3. <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
  4. <!DOCTYPE html>
  5. <html>
  6. <head>
  7. <title>Wrong parameters</title>
  8. <meta charset="UTF-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1">
  10. </head>
  11. <body>
  12. <h2>Wrong parameters specified</h2>
  13. <a href="<%= request.getContextPath() %>">Home</a>
  14. </body>
  15. </html>

请求参数无效时,显示wrongParams.jsp页面。 创建名为ValidateParameter的工具类以确保请求参数的有效性。

服务层

服务层提供逻辑以对发送到 DAO 和从 DAO 发送的数据进行操作。

ICarsService.java

  1. package com.zetcode.service;
  2. import com.zetcode.bean.Car;
  3. import java.util.List;
  4. public interface ICarsService {
  5. public List<Car> findAllCars();
  6. public Car findCar(Long id);
  7. public void saveCar(Car car);
  8. }

ICarsService为汽车服务提供了三种合同方式。 我们提供了一种检索所有汽车,查找特定汽车并保存新汽车的方法。

CarsService.java

  1. package com.zetcode.service;
  2. import com.zetcode.bean.Car;
  3. import com.zetcode.persistence.CarDAO;
  4. import com.zetcode.persistence.JdbcDAO;
  5. import java.util.List;
  6. public class CarsService implements ICarsService {
  7. @Override
  8. public List<Car> findAllCars() {
  9. CarDAO carDAO = new JdbcDAO();
  10. return carDAO.findAll();
  11. }
  12. @Override
  13. public Car findCar(Long id) {
  14. CarDAO carDAO = new JdbcDAO();
  15. return carDAO.findCar(id);
  16. }
  17. @Override
  18. public void saveCar(Car car) {
  19. CarDAO carDAO = new JdbcDAO();
  20. carDAO.saveCar(car);
  21. }
  22. }

CarsService提供合同方法的实现。

  1. @Override
  2. public List<Car> findAllCars() {
  3. CarDAO carDAO = new JdbcDAO();
  4. return carDAO.findAll();
  5. }

findAllCars()方法创建一个JdbcDAO并调用其findAll()方法。

持久层

在持久层中,我们应用 DAO 模式。 DAO 在定义的 API 中隐藏了数据库编程的复杂性。

CarDAO.java

  1. package com.zetcode.persistence;
  2. import com.zetcode.bean.Car;
  3. import java.util.List;
  4. public interface CarDAO {
  5. public void saveCar(Car car);
  6. public Car findCar(Long id);
  7. public List<Car> findAll();
  8. }

这是CarDAO接口,显示用于访问我们的数据库的方法签名。

Executable.java

  1. package com.zetcode.persistence;
  2. import java.sql.SQLException;
  3. import javax.naming.NamingException;
  4. public interface Executable {
  5. void exec() throws SQLException, NamingException;
  6. }

Executable接口是将try/catch/finally样板放入exec()方法的合约。

JdbcDAO.java

  1. package com.zetcode.persistence;
  2. import com.zetcode.bean.Car;
  3. import com.zetcode.util.DBUtils;
  4. import com.zetcode.util.ServiceLocator;
  5. import java.sql.Connection;
  6. import java.sql.PreparedStatement;
  7. import java.sql.ResultSet;
  8. import java.util.ArrayList;
  9. import java.util.List;
  10. import java.util.logging.Level;
  11. import java.util.logging.Logger;
  12. import javax.naming.NamingException;
  13. import java.sql.SQLException;
  14. import javax.sql.DataSource;
  15. public class JdbcDAO implements CarDAO {
  16. private static final String DATA_SOURCE = "java:comp/env/jdbc/testdb";
  17. private Connection con;
  18. private ResultSet rs;
  19. private PreparedStatement pst;
  20. @Override
  21. public void saveCar(Car car) {
  22. execute(() -> {
  23. DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
  24. con = ds.getConnection();
  25. pst = con.prepareStatement("INSERT INTO CARS(Name, Price) VALUES(?, ?)");
  26. pst.setString(1, car.getName());
  27. pst.setInt(2, car.getPrice());
  28. pst.executeUpdate();
  29. });
  30. }
  31. @Override
  32. public Car findCar(Long id) {
  33. Car car = new Car();
  34. execute(() -> {
  35. DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
  36. con = ds.getConnection();
  37. pst = con.prepareStatement("SELECT * FROM CARS WHERE Id = (?)");
  38. pst.setLong(1, id);
  39. rs = pst.executeQuery();
  40. if (rs.next()) {
  41. car.setId(rs.getLong(1));
  42. car.setName(rs.getString(2));
  43. car.setPrice(rs.getInt(3));
  44. }
  45. });
  46. return car;
  47. }
  48. @Override
  49. public List<Car> findAll() {
  50. List<Car> carList = new ArrayList<>();
  51. execute(() -> {
  52. DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
  53. con = ds.getConnection();
  54. pst = con.prepareStatement("SELECT * FROM CARS");
  55. rs = pst.executeQuery();
  56. while (rs.next()) {
  57. Car car = new Car();
  58. car.setId(rs.getLong(1));
  59. car.setName(rs.getString(2));
  60. car.setPrice(rs.getInt(3));
  61. carList.add(car);
  62. }
  63. });
  64. return carList;
  65. }
  66. private void execute(Executable executable) {
  67. try {
  68. executable.exec();
  69. } catch (NamingException | SQLException e) {
  70. Logger lgr = Logger.getLogger(JdbcDAO.class.getName());
  71. lgr.log(Level.SEVERE, e.getMessage(), e);
  72. } finally {
  73. DBUtils.closeResultSet(rs);
  74. DBUtils.closeStatement(pst);
  75. DBUtils.closeConnection(con);
  76. }
  77. }
  78. }

JdbcDAOCarDAO接口的具体实现。 它使用 JDBC 从Cars表中插入和检索数据。

  1. private static final String DATA_SOURCE = "java:comp/env/jdbc/testdb";

这是用于定位testdb数据库的 JNDI 资源名称。

  1. @Override
  2. public void saveCar(Car car) {
  3. execute(() -> {
  4. DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
  5. con = ds.getConnection();
  6. pst = con.prepareStatement("INSERT INTO CARS(Name, Price) VALUES(?, ?)");
  7. pst.setString(1, car.getName());
  8. pst.setInt(2, car.getPrice());
  9. pst.executeUpdate();
  10. });
  11. }

saveCar()方法保存一个新的汽车对象。 ServiceLocator.getDataSource()方法查找并返回数据源。 该代码已插入execute()方法中,该方法将处理try/catch/finally样板。

  1. @Override
  2. public Car findCar(Long id) {
  3. Car car = new Car();
  4. execute(() -> {
  5. DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
  6. con = ds.getConnection();
  7. pst = con.prepareStatement("SELECT * FROM CARS WHERE Id = (?)");
  8. pst.setLong(1, id);
  9. rs = pst.executeQuery();
  10. if (rs.next()) {
  11. car.setId(rs.getLong(1));
  12. car.setName(rs.getString(2));
  13. car.setPrice(rs.getInt(3));
  14. }
  15. });
  16. return car;
  17. }

findCar()方法从Cars表中检索新车。 它执行准备好的语句,该语句接收汽车的 ID。 一个新的car bean 填充了返回的数据。

  1. private void execute(Executable executable) {
  2. try {
  3. executable.exec();
  4. } catch (NamingException | SQLException e) {
  5. Logger lgr = Logger.getLogger(JdbcDAO.class.getName());
  6. lgr.log(Level.SEVERE, e.getMessage(), e);
  7. } finally {
  8. DBUtils.closeResultSet(rs);
  9. DBUtils.closeStatement(pst);
  10. DBUtils.closeConnection(con);
  11. }
  12. }

处理异常的重复代码位于execute()方法中。

工具类

我们创建了三个工具类:ServiceLocatorValidateParameterDBUtils。 这些类位于com.zetcode.util包中。

ServiceLocator.java

  1. package com.zetcode.util;
  2. import javax.naming.Context;
  3. import javax.naming.InitialContext;
  4. import javax.naming.NamingException;
  5. import javax.sql.DataSource;
  6. public class ServiceLocator {
  7. public static DataSource getDataSource(String jndiName) throws NamingException {
  8. Context ctx = new InitialContext();
  9. DataSource ds = (DataSource) ctx.lookup(jndiName);
  10. return ds;
  11. }
  12. }

ServiceLocator查找并返回数据源。 从JdbcDAO类调用它。 数据源的详细信息在context.xml文件中指定。

ValidateParameter.java

  1. package com.zetcode.util;
  2. import org.apache.commons.lang3.math.NumberUtils;
  3. public class ValidateParameter {
  4. private static final int MAX_PRICE_CAR = 10_000_000;
  5. public static boolean validateName(String param) {
  6. return !(null == param || "".equals(param));
  7. }
  8. public static boolean validateId(String param) {
  9. return !(null == param || "".equals(param) ||
  10. !NumberUtils.isCreatable(param));
  11. }
  12. public static boolean validatePrice(String param) {
  13. if (null == param || "".equals(param) || !NumberUtils.isCreatable(param)) {
  14. return false;
  15. }
  16. int price = Integer.valueOf(param);
  17. return !(price < 0 || price > MAX_PRICE_CAR);
  18. }
  19. }

ValidateParameter具有用于验证请求参数的静态方法。 例如,ID 必须为数字,且价格不得为负。 我们使用 Apache Commons Lang 库中的NumberUtils.isCreatable()方法来确保参数为数字。

DBUtils.java

  1. package com.zetcode.util;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.SQLException;
  5. import java.sql.Statement;
  6. import java.util.logging.Level;
  7. import java.util.logging.Logger;
  8. public class DBUtils {
  9. private static final Logger logger = Logger.getLogger(DBUtils.class.getName());
  10. public static void closeResultSet(ResultSet rs) {
  11. if (rs != null) {
  12. try {
  13. rs.close();
  14. } catch (SQLException ex) {
  15. logger.log(Level.FINEST, "Could not close JDBC ResultSet", ex);
  16. } catch (Throwable ex) {
  17. // We don't trust the JDBC driver: It might throw RuntimeException or Error.
  18. logger.log(Level.FINEST, "Unexpected exception on closing JDBC ResultSet", ex);
  19. }
  20. }
  21. }
  22. public static void closeStatement(Statement stmt) {
  23. if (stmt != null) {
  24. try {
  25. stmt.close();
  26. } catch (SQLException ex) {
  27. logger.log(Level.FINEST, "Could not close JDBC Statement", ex);
  28. } catch (Throwable ex) {
  29. // We don't trust the JDBC driver: It might throw RuntimeException or Error.
  30. logger.log(Level.FINEST, "Unexpected exception on closing JDBC Statement", ex);
  31. }
  32. }
  33. }
  34. public static void closeConnection(Connection con) {
  35. if (con != null) {
  36. try {
  37. con.close();
  38. } catch (SQLException ex) {
  39. logger.log(Level.FINEST, "Could not close JDBC Connection", ex);
  40. } catch (Throwable ex) {
  41. // We don't trust the JDBC driver: It might throw RuntimeException or Error.
  42. logger.log(Level.FINEST, "Unexpected exception on closing JDBC Connection", ex);
  43. }
  44. }
  45. }
  46. }

DBUtils包含释放数据库资源和处理异常的方法。

控制器

Controller是一个 Servlet,它接收传入的请求,调用服务方法并发送响应。

Controller.java

  1. package com.zetcode.web;
  2. import com.zetcode.bean.Car;
  3. import com.zetcode.persistence.CarDAO;
  4. import com.zetcode.persistence.JdbcDAO;
  5. import com.zetcode.service.CarsService;
  6. import com.zetcode.service.ICarsService;
  7. import com.zetcode.util.ValidateParameter;
  8. import java.io.IOException;
  9. import javax.servlet.RequestDispatcher;
  10. import javax.servlet.ServletException;
  11. import javax.servlet.annotation.WebServlet;
  12. import javax.servlet.http.HttpServlet;
  13. import javax.servlet.http.HttpServletRequest;
  14. import javax.servlet.http.HttpServletResponse;
  15. @WebServlet(name = "Controller", urlPatterns = {"/controller"})
  16. public class Controller extends HttpServlet {
  17. private static final String ACTION_KEY = "action";
  18. private static final String READ_CAR_BY_ID_VIEW = "readCarId.jsp";
  19. private static final String SHOW_CAR_VIEW = "showCar.jsp";
  20. private static final String READ_CAR_VIEW = "readCar.jsp";
  21. private static final String CAR_SAVED_VIEW = "carSaved.jsp";
  22. private static final String ALL_CARS_VIEW = "allCars.jsp";
  23. private static final String UNKNOWN_VIEW = "unknown.jsp";
  24. private static final String WRONG_PARAMS_VIEW = "wrongParams.jsp";
  25. private static final String LIST_CARS_ACTION = "listcars";
  26. private static final String READ_CAR_BY_ID_ACTION = "readbyid";
  27. private static final String READ_CAR_ACTION = "readcar";
  28. private static final String VIEW_CAR_ACTION = "viewcar";
  29. private static final String SAVE_CAR_ACTION = "savecar";
  30. @Override
  31. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  32. throws ServletException, IOException {
  33. response.setContentType("text/html;charset=UTF-8");
  34. String actionName = request.getParameter(ACTION_KEY);
  35. String page = UNKNOWN_VIEW;
  36. if (LIST_CARS_ACTION.equals(actionName)) {
  37. ICarsService service = new CarsService();
  38. request.setAttribute("carList", service.findAllCars());
  39. page = ALL_CARS_VIEW;
  40. }
  41. if (READ_CAR_BY_ID_ACTION.equals(actionName)) {
  42. page = READ_CAR_BY_ID_VIEW;
  43. }
  44. if (READ_CAR_ACTION.equals(actionName)) {
  45. page = READ_CAR_VIEW;
  46. }
  47. if (VIEW_CAR_ACTION.equals(actionName)) {
  48. String sid = request.getParameter("carId");
  49. if (ValidateParameter.validateId(sid)) {
  50. ICarsService service = new CarsService();
  51. Long carId = Long.valueOf(sid);
  52. request.setAttribute("returnedCar", service.findCar(carId));
  53. page = SHOW_CAR_VIEW;
  54. } else {
  55. page = WRONG_PARAMS_VIEW;
  56. }
  57. }
  58. RequestDispatcher disp = getServletContext().getRequestDispatcher("/" + page);
  59. disp.forward(request, response);
  60. }
  61. @Override
  62. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  63. throws ServletException, IOException {
  64. response.setContentType("text/html;charset=UTF-8");
  65. String actionName = request.getParameter(ACTION_KEY);
  66. String page = UNKNOWN_VIEW;
  67. if (SAVE_CAR_ACTION.equals(actionName)) {
  68. String sname = request.getParameter("carName");
  69. String sprice = request.getParameter("carPrice");
  70. if (ValidateParameter.validateName(sname)
  71. && ValidateParameter.validatePrice(sprice)) {
  72. Car car = new Car();
  73. car.setName(sname);
  74. car.setPrice(Integer.valueOf(sprice));
  75. ICarsService service = new CarsService();
  76. service.saveCar(car);
  77. request.getSession().setAttribute("carName", sname);
  78. request.getSession().setAttribute("carPrice", sprice);
  79. page = CAR_SAVED_VIEW;
  80. } else {
  81. page = WRONG_PARAMS_VIEW;
  82. }
  83. }
  84. response.sendRedirect(page);
  85. }
  86. }

该 servlet 位于com.zetcode.web包中。

  1. private static final String READ_CAR_BY_ID_VIEW = "readCarId.jsp";
  2. private static final String SHOW_CAR_VIEW = "showCar.jsp";
  3. private static final String READ_CAR_VIEW = "readCar.jsp";
  4. ...

这些是我们的应用中使用的各种视图。

  1. private static final String LIST_CARS_ACTION = "listcars";
  2. private static final String READ_CAR_BY_ID_ACTION = "readbyid";
  3. private static final String READ_CAR_ACTION = "readcar";
  4. ...

这些是应用的各种动作。 例如,READ_CAR_ACTION显示的视图包含一个表单,用户可以在其中输入新车的详细信息。

  1. if (LIST_CARS_ACTION.equals(actionName)) {
  2. ICarsService service = new CarsService();
  3. request.setAttribute("carList", service.findAllCars());
  4. page = ALL_CARS_VIEW;
  5. }

对于LIST_CARS_ACTION,我们创建一个CarsService对象。 我们调用findAllCars()服务方法,并将结果设置为carList属性。 然后,控制器 Servlet 指向ALL_CARS_VIEW

  1. if (VIEW_CAR_ACTION.equals(actionName)) {
  2. String sid = request.getParameter("carId");
  3. if (ValidateParameter.validateId(sid)) {
  4. ICarsService service = new CarsService();
  5. Long carId = Long.valueOf(sid);
  6. request.setAttribute("returnedCar", service.findCar(carId));
  7. page = SHOW_CAR_VIEW;
  8. } else {
  9. page = WRONG_PARAMS_VIEW;
  10. }
  11. }

为了查看一辆汽车,我们从request参数中获得了汽车的 ID。 该值使用ValidateParameter.validateId()工具方法进行验证。 (该值不能为null,为空,并且必须为数字。)如果参数无效,则控制器导航至WRONG_PARAMS_VIEW

findCar()尝试从数据库中检索汽车。 返回的汽车将插入returnedCar属性,该属性随后会在showCar.jsp页面中获取。

  1. if (ValidateParameter.validateName(sname)
  2. && ValidateParameter.validatePrice(sprice)) {
  3. Car car = new Car();
  4. car.setName(sname);
  5. car.setPrice(Integer.valueOf(sprice));
  6. ICarsService service = new CarsService();
  7. service.saveCar(car);
  8. request.getSession().setAttribute("carName", sname);
  9. request.getSession().setAttribute("carPrice", sprice);
  10. page = CAR_SAVED_VIEW;
  11. } else {
  12. page = WRONG_PARAMS_VIEW;
  13. }

该代码位于doPost()方法中。 保存新车时,我们有两个参数:汽车的名称和价格。 该 ID 由 Derby 自动创建。 验证参数,并创建一个新的Car对象并填充参数。 saveCar()将汽车对象保存到数据库中。 汽车的名称将传递到CAR_SAVED_VIEW,以便向用户创建消息。 由于我们在doPost()方法中使用了重定向,因此我们将汽车的名称及其价格放入会话对象; 进行重定向操作后,我们会从原始请求中删除数据。

  1. response.sendRedirect(page);

遵循发布/重定向/获取模式,我们将重定向到doPost()方法中的视图。 这样可以防止提交多个表单。 (例如,我们可能不小心多次添加了汽车)。

Tomcat Derby 教程 - 图5

图:显示所有汽车

在本教程中,我们创建了一个简单的 Web 应用框架,用于管理汽车对象。 数据已保存在 Derby 数据库中。

该应用分为四层。 我们已经使用 DAO 模式进行数据访问。 您可以在 ZetCode 的 Derby 教程中找到有关 Derby 的更多信息。 在中显示数据网格中的数据教程中,我们展示了如何在 EasyUI datagrid 控件中显示来自 Derby 数据库的数据。