目录
一 前言
上一篇通过小白新手web开发简单总结(七)-数据库HSQLDB学习了什么是HSQLDB数据库,这一篇就是通过一个实例再去看下web应用是怎么读取HSQLDB数据库的。虽然里面里面用到的代码不一定是最精确的,但是通过这个实例自己对web应用开发又多了一些知识点的积累。
二 实例
1.创建工程
目前还不太清楚具体一个web应用应该创建什么工程,一开始自己一直在创建一个webapp工程,但是发现不知道为什么创建的Sevelet打包之后无法访问,所以经过自己多次探索,我觉得创建按照下面创建出来的工程,是最适合的。
- (1)通过File -> new -> project...弹出如下对话框,选择Java Enterprise,点击Next
- (2)设置工程所在的目录和名称,设置需要的Artiface相关的信息
经过上面的几步就可以看到一个基本的项目结构,点击Run之后,就可以按照配置好Tomcat(当然这里需要IDEA是专业版,社区版是没有这个选项的)运行项目了。通过这种方式默认的会浏览器显示的主页的url为 http://localhost:8080/sql-web-war-exploded/,这样就导致在后面添加新的Servlet的时候,必须添加该字符串才可以访问成功 ,后来想到这个去掉这两个地方的固定字符串,没想到成功了。这里以后还得在研究下为什么是这种情况。下面是去掉sql-web-war-exploded/的步骤:
- (1)通过下面的图示打开Edit Configurations
- (2)主要修改的地方如图所示的两个地方
这样就可以直接通过http://localhost:8080/访问到设置的Servlet
在Run的过程中,还遇到一个问题:就是在终端界面显示如下报错信息,看到这个报错信息一开始是头大的,因为在当时运行公司的项目的时候,也是抛出这个错误,没有通过IDEA的Tomcat运行成功项目,但是这次硬着头皮仔细分析这个错误,突然发现了里面的关键报错信息“Caused by: java.net.BindException: Address already in use”(因为之前为了能够创建一个合适的工程,运行过多个项目),然后突然醒悟是不是8080端口被占用了??
26-Feb-2021 10:43:59.686 信息 [main] org.apache.catalina.core.AprLifecycleListener.lifecycleEvent 在java.library.path:[/Users/j1/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]上找不到基于APR的Apache Tomcat本机库,该库允许在生产环境中获得最佳性能
26-Feb-2021 10:43:59.949 信息 [main] org.apache.coyote.AbstractProtocol.init 初始化协议处理器 ["http-nio-8080"]
26-Feb-2021 10:43:59.971 严重 [main] org.apache.catalina.util.LifecycleBase.handleSubClassException 初始化组件[Connector[HTTP/1.1-8080]]失败。
org.apache.catalina.LifecycleException: 协议处理程序初始化失败
at org.apache.catalina.connector.Connector.initInternal(Connector.java:1042)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
at org.apache.catalina.core.StandardService.initInternal(StandardService.java:558)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
at org.apache.catalina.core.StandardServer.initInternal(StandardServer.java:1057)
at org.apache.catalina.util.LifecycleBase.init(LifecycleBase.java:136)
at org.apache.catalina.startup.Catalina.load(Catalina.java:724)
at org.apache.catalina.startup.Catalina.load(Catalina.java:746)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:302)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:472)
/其实这里是报错的主要原因,太开心了,终于找到问题了!!!!!!!!
Caused by: java.net.BindException: Address already in use
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:433)
at sun.nio.ch.Net.bind(Net.java:425)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:74)
at org.apache.tomcat.util.net.NioEndpoint.initServerSocket(NioEndpoint.java:228)
at org.apache.tomcat.util.net.NioEndpoint.bind(NioEndpoint.java:211)
at org.apache.tomcat.util.net.AbstractEndpoint.bindWithCleanup(AbstractEndpoint.java:1159)
at org.apache.tomcat.util.net.AbstractEndpoint.init(AbstractEndpoint.java:1172)
at org.apache.coyote.AbstractProtocol.init(AbstractProtocol.java:592)
at org.apache.coyote.http11.AbstractHttp11Protocol.init(AbstractHttp11Protocol.java:80)
at org.apache.catalina.connector.Connector.initInternal(Connector.java:1039)
... 13 more
26-Feb-2021 10:43:59.972 信息 [main] org.apache.catalina.startup.Catalina.load 服务器在[477]毫秒内初始化
26-Feb-2021 10:44:00.002 信息 [main] org.apache.catalina.core.StandardService.startInternal 正在启动服务[Catalina]
26-Feb-2021 10:44:00.002 信息 [main] org.apache.catalina.core.StandardEngine.startInternal 正在启动 Servlet 引擎:[Apache Tomcat/9.0.41]
26-Feb-2021 10:44:00.011 信息 [main] org.apache.catalina.startup.Catalina.start [39]毫秒后服务器启动
Connected to server
[2021-02-26 10:44:00,145] Artifact sql-web:war exploded: Artifact is being deployed, please wait...
[2021-02-26 10:44:00,454] Artifact sql-web:war exploded: Artifact is deployed successfully
[2021-02-26 10:44:00,454] Artifact sql-web:war exploded: Deploy took 310 milliseconds
26-Feb-2021 10:44:10.015 信息 [Catalina-utility-2] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/Users/j1/Documents/java/apache-tomcat-9.0.41/webapps/manager]
26-Feb-2021 10:44:10.047 信息 [Catalina-utility-2] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[/Users/j1/Documents/java/apache-tomcat-9.0.41/webapps/manager]的部署已在[32]毫秒内完成
马上验证自己的想法,通过"lsof -i:8080"查找到所有8080端口的进程,发现果然里面的好几个进程都占用着8080端口,最后通过"kill -9 进程pid"杀掉每一个进程,最后发现没有任何占用8080端口的进程,再次通过run tomcat来运行项目,发现项目运行成功,当运行完毕之后,就会自动在浏览器中加载首页的内容。
MacBook-Pro:bin j1$ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
?\x81? 375 j1 19u IPv4 0xbf83bb36d15dbe79 0t0 TCP 172.16.20.11:49239->157.255.245.177:http-alt (ESTABLISHED)
?\x81? 375 j1 32u IPv4 0xbf83bb36d15ddc19 0t0 TCP 172.16.20.11:49241->reverse.gdsz.cncnet.net:http-alt (ESTABLISHED)
?\x81? 375 j1 131u IPv4 0xbf83bb36d2ccd0d9 0t0 TCP 172.16.20.11:50159->157.255.245.177:http-alt (ESTABLISHED)
java 2632 j1 37u IPv6 0xbf83bb36d7b03069 0t0 TCP *:http-alt (LISTEN)
MacBook-Pro:bin j1$ kill -9 375
MacBook-Pro:bin j1$ kill -9 2632
MacBook-Pro:bin j1$ lsof -i:8080
MacBook-Pro:bin j1$
2.部分实例代码
一个图书管理系统:主要有两个界面:一个是录入图书信息的界面;一个是列举所有录入的图书列表的界面。因为我觉得这两个操作,正好可以来实操下代码修改数据库。
- (1)添加项目依赖
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!--IoC容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--使用里面的一些帮助类如SpringBeanAutowiringSupport -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--通过注解标记不同类的功能,让IoC容器管理对应的对象-->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<!--jdbc接口-->
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-jdbc</artifactId>-->
<!-- <version>5.2.5.RELEASE</version>-->
<!-- </dependency>-->
<!--hsqldb数据库-->
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.0</version>
</dependency>
<!-- 与jsp中的C标签相关的依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
具体对应的依赖相关作用见代码注释。
- (2)创建Book对象,很简单就是id、图书名、价格以及上架时间四个属性
这里的代码比较简单,不在添加到这里,具体会将所有的代码上传到github
- (3)创建操作数据库配置类JdbcConfiguration,用来读取数据库的相关配置信息
@Configuration
@PropertySource("classpath:/config/jdbc.properties")
public class JdbcConfiguration {
@Value("${jdbc.url}")
public String jdbcUrl ; //= "jdbc:hsqldb:file:db/hsqldb/xbook";
@Value("${jdbc.user}")
public String jdbcUser;// = "SA";
@Value("${jdbc.password}")
public String jdbcPassword;// = "";
@Value("${jdbc.table}")
public String jdbcTable;// = "book";
public Connection connection;
@PostConstruct
public void init(){
System.out.println("创建Jdbc 。。。。。。 ");
}
public Statement createStatement() {
try {
Class.forName("org.hsqldb.jdbcDriver");
} catch (ClassNotFoundException e) {
System.out.println("jdbcDriver not found!!!");
return null;
}
try {
//JDBC不支持自增,创建或连接数据库
connection = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
if (connection == null) {
return null;
}
//创建里面的表
Statement statement = connection.createStatement();
return statement;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public void closeConnection(){
if (connection == null){
return;
}
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
这里实操了小白新手web开发简单总结(七)-数据库HSQLDB中一个进程模式的数据库的创建方式和小白新手web开发简单总结(六)-Spring的IoC容器 的通过@PropertySource注解来读取resources的配置文件的相关内容,并且后面对于所有的类的实例化也都是通过@Component的方式标示类,通过Spring来加载所有的JavaBean。同时需要在resources/config下增加jdbc.properties来配置HSQLDB数据库的基本信息,配置内容如下:
#hsqldb的相关配置
#最后会在apache/bin的目录下生成该数据库
jdbc.url = jdbc:hsqldb:file:db/hsqldb/xbook
jdbc.user = SA
jdbc.password =
jdbc.table = book
- (4)数据库的增删改查操作类DbOperation
@Component
public class DbOperation {
@Autowired
public JdbcConfiguration jdbcConfiguration;
/**
* 创建表
*
* @param sql
* @return
*/
public int createTable(String sql) {
Statement statement = jdbcConfiguration.createStatement();
try {
int result = statement.executeUpdate(sql);
System.out.println(String.format("创建表的结果为 %d", result));
return result;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
System.out.println("创建表失败了!!!");
return -1;
}
/**
* 查询
*
* @param sql
* @return
*/
public ResultSet query(String sql) {
Statement statement = jdbcConfiguration.createStatement();
try {
ResultSet set = statement.executeQuery(sql);
return set;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
/**
* 插入
*
* @param sql
* @return
*/
public int insert(String sql) {
Statement statement = jdbcConfiguration.createStatement();
try {
int set = statement.executeUpdate(sql);
return set;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return -1;
}
public void closeTable() {
jdbcConfiguration.closeConnection();
}
}
我觉得像spring-jdbc等是可以直接代替这部分操作,在项目中引入的一些框架性的东西,都是为了让这些处理更高效、更优。我这里是主要是为了学习一个基础的操作数据库的过程,所以先按照自己的想法来验证这些内容。
- (5)业务逻辑相关的录入一个Book以及获取数据库中的所有Book
@Component
public class BookManagerService {
@Autowired
public DbOperation dbOperation;
@Value("#{jdbcConfiguration.jdbcTable}")
private String jdbcTable;
@PostConstruct
public void createBookTable() {
System.out.println("BookManagerService 初始化 自动创建表!!!");
String sql = String.format("CREATE TABLE IF NOT EXISTS %s " +
"(id INTEGER PRIMARY KEY , name VARCHAR(50) NOT NULL, price DOUBLE, online DATE)", jdbcTable);
int result = dbOperation.createTable(sql);
System.out.println(String.format("BookManagerService 创建 %s 表的结果为 %d", jdbcTable, result));
}
public int addBook(Book book) {
// 因为暂时不知道让id自加1,所以用这种笨方法先记录下
String sql = String.format("SELECT * FROM %s ", jdbcTable);
int count = 0;
try {
ResultSet set = dbOperation.query(sql);
while (set.next()) {
count++;
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//在总行数的基础上在加1,得到新数据的id
int id = count + 1;
// 插入新的数据
String insert = String.format("INSERT INTO %s \n" +
"(id, name, price, online)\n" +
"VALUES (%d , '%s', %f,'%s' )", jdbcTable, id, book.name, book.price, book.online);
int result = dbOperation.insert(insert);
System.out.println(String.format("%s已经成功加入数据库,目前数据库总共有%d条数据 ", book.name, result));
return result;
}
public List<Book> getBook() {
List<Book> books = new ArrayList<>();
String sql = String.format("SELECT * FROM %s ", jdbcTable);
ResultSet set = dbOperation.query(sql);
if (set == null) {
return books;
}
try {
while (set.next()) {
Book book = new Book();
book.id = set.getInt(1);
book.name = set.getString(2);
book.price = set.getFloat(3);
book.online = set.getDate(4).toString();
System.out.println("查询的数据为:" + book.toString());
books.add(book);
}
return books;
} catch (SQLException e) {
e.printStackTrace();
}
return books;
}
@PreDestroy
public void closeBookTable() {
System.out.println("BookManagerService初始化自动关闭表!!!");
dbOperation.closeTable();
}
}
这里的代码逻辑很简单,就是通过上面的DbOperation类来进行读写数据库。
- (6)HttpServlet和jsp配合做页面渲染
BookListController继承HttpServlet,用来实现接收浏览器的发送的请求以及处理该请求返回给浏览器。
@WebServlet(name = "BookListServlet", value = "/booklist")
public class BookListController extends BaseHttpServlet {
@Autowired
public BookManagerService bookManagerService;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<Book> books = bookManagerService.getBook();
System.out.println("book size = " + books.size());
req.setAttribute("book", books);
req.getRequestDispatcher("/book/booklist.jsp").forward(req, resp);
}
}
该booklist.jsp主要渲染图书列表页面。
<%@ page import="java.util.List" %>
<%@ page import="com.wj.hsqldb.model.Book" %><%--
Created by IntelliJ IDEA.
User: j1
Date: 2021/2/25
Time: 16:33
To change this template use File | Settings | File Templates.
--%>
<%--需要导入C标签该包--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>图书列表</title>
</head>
<body>
<table width="50%" border="1px" style="margin-top: 50px;" title="${display}">
<tr> <!--加粗-->
<th>Book id</th>
<th>Book name</th>
<th>Book price</th>
<th>Book online</th>
</tr>
<%-- 使用el的条件:如果用的servlet-api.jar包低于javax.servlet-api-3.0.1版本,web版本必须是3.0之前的。--%>
<c:forEach items="${book}" var="it">
<tr style="color: red;alignment: center">
<td>${it.id}</td>
<td>${it.name}</td>
<td>${it.price}</td>
<td>${it.online}</td>
</tr>
</c:forEach>
</table>
<input type="button" value="添加图书"
style="color: aqua; width: 10%;height: 5%;font-size: larger;margin-top: 50px;background: yellow;"
onclick="window.location.href='/book/addbook.jsp'">
</body>
</html>
这里仅单独列举一个图书列表页的一个逻辑,就是去展示了一个图书列表的table,具体的其他代码可直接去git地址下载下来去看。github地址为:https://github.com/wenjing-bonnie/sql-web.git。
- (7)配置web.xml
这个步骤非常重要,也是后面在解决一些出现的问题的关键点。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<display-name>Sql-Web</display-name>
<!--这种处理方式是错误的-->
<!-- <listener>-->
<!-- <listener-class>com.wj.hsqldb.SpringContextListener</listener-class>-->
<!-- </listener>-->
<!--用来配置Spring的配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/application-context.xml</param-value>
</context-param>
<!--用来创建Spring的IoC容器,并完成与ServletContext的绑定-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
经过上面的几个过程,通过IDEA的tomcat启动项目,会自动在浏览器中加载http://localhost:8080,看到自己想要的效果。里面创建的数据库会在apache本地默认的bin的目录下生成该数据库。
代码已经上传到github地址为:https://github.com/wenjing-bonnie/sql-web.git。因为代码一直在更新,所以此处的代码的tag标记为example8(因为项目在一直更新,所以通过打tag的方式来标记这次代码),即
需要通过将代码切换到该tag下,在这tag之前的代码为后面继续学习优化之后的代码。
三 总结
上面的例子的代码是我在反复解决各种问题之后可以成功运行项目的最终代码,这些代码并不是我想要总结的重点。因为本身就是Android开发,对写这些Java代码太简单,主要想总结下在这个过程中遇到的一些问题,也让我对web开发有了更深的印象,当然也产生一些疑问,需要自己后面去一一解决。
后面单独会有一篇总结,小白新手web开发简单总结(十)-数据库HSQLDB实例问题总结。