之前scrapy框架爬取豆瓣电影top250信息,爬虫数据存储到mysql数据库写了如何构建一个简单的scrapy项目以及如何将爬取到的数据存储到数据库中,接下来记录一下tomcat服务器端通过j2ee项目实现jdbc(数据库连接)访问这些数据以及通过json实现服务端与android客户端的数据交流。
运行环境
1. win7-64bit
2. Apache Tomcat/8.5.12
3. mysql 5.7.17
创建服务端项目
使用eclipse新建Dynamic web project项目,命名为doubanApp
首先整理一下思路:我们需要从数据库中读取关于电影的各种信息,之后将信息存放到对应的对象变量中,将对象变量转化为json格式然后与客户端进行通信。
那么我们共需要四部分:
1. 数据库部分:数据库的连接,增删查改。
2. 对象变量:根据从数据库取得的信息初始化变量,以便转化为json通信。
3. 对象变量的服务:调用编写的数据库增删查改接口,取得对象变量以及转化为json格式。
4. servlet:客户端访问部分。
根据这几部分创建不同的包,结果如下图
服务端
首先编写需要帮助我们处理数据的对象类,在model包中创建java类Movie,其具有的属性与数据库中属性一致,分别为
private String name;
private String info;
private double rating;
private int num;
private String quote;
private String img_url;
对各个属性编写setter与getter,然后重载toString()
方法
@Override
public String toString()
{
return "{\"name\":\"" + this.name + "\", \"info\":\"" + this.info
+ "\", \"rating\":\""+ this.rating + "\", \"num\":\"" + this.num
+ "\", \"quote\":\""+ this.quote + "\", \"img_url\":\""
+ this.img_url + "\"}";
}
使其能够方便的转化为json格式。
接下来编写数据库部分。
首先配置数据源,在对于项目目录:D:\jee-neon\workstation\doubanApp\WebContent\META-INF中找到context.xml文件(如果没有自行创建即可),修改里面内容,如下
之后在database包中创建文件InitDatabase.java文件,用于初始化数据库连接。在构造方法中获得数据源,之后编写getConnection()
方法及各种 close 方法。
package database;
import java.sql.*;
import javax.naming.*;
import javax.sql.*;
public class InitDatabase {
private DataSource ds = null;
//初始化数据源
public InitDatabase() throws Exception
{
Context ctx = new InitialContext();
ds = (DataSource)ctx.lookup("java:comp/env/jdbc/douban");
}
//获得与数据库的连接
public Connection getConnection()throws Exception
{
return ds.getConnection();
}
//关闭连接
public void closeConnection(Connection con)
{
try
{
if(con!=null)
{
con.close();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
//关闭准备语句
public void closePrepStmt(PreparedStatement prepStmt)
{
try
{
if(prepStmt!=null)
{
prepStmt.close();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
//关闭查询结果集
public void closeResultSet(ResultSet rs)
{
try
{
if(rs!=null)
{
rs.close();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
其中PreparedStatement用于准备待执行的sql语句,ResultSet用于存放查询结果。
继续在database包中创建文件MovieDB.java,用于实现增删查改。
首先我们想到在客户端我们会通过电影名查询获得电影的全部信息,以及将250条电影信息分页显示等,暂时先编写这两个方法。
方法编写大同小异,首先初始化连接,执行sql语句,将返回结果存在对象中后返回给调用者。
package database;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import model.Movie;
public class MovieDB extends InitDatabase{
public MovieDB() throws Exception
{
super();
}
//根据电影名获得电影信息
public Movie getMovie(String name)
{
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
Movie movie = null;
try
{
//初始化连接
con = getConnection();
//构造sql语句
String query = "select * from doubanmovie where name = ?";
prepStmt = con.prepareStatement(query);
prepStmt.setString(1, name);
//执行查询
rs = prepStmt.executeQuery();
//将查询结果存储在对象中
while(rs.next())
{
movie = new Movie(rs.getString(1),
rs.getString(2),
rs.getDouble(3),
rs.getInt(4),
rs.getString(5),
rs.getString(6));
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
closeConnection(con);
closePrepStmt(prepStmt);
closeResultSet(rs);
}
return movie;
}
//获得某一页电影信息
public List<Movie> getMovies(int page)
{
Connection con = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
Movie movie = null;
ArrayList<Movie> movies = new ArrayList<>();
try
{
//初始化连接
con = getConnection();
//构造sql语句
int lastPage = page-1;
String query = "select * from doubanmovie limit ?, 10";
prepStmt = con.prepareStatement(query);
prepStmt.setInt(2, page*10);
//执行查询
rs = prepStmt.executeQuery();
while(rs.next())
{
//将查询结果存储在对象中
movie = new Movie(rs.getString(1),
rs.getString(2),
rs.getDouble(3),
rs.getInt(4),
rs.getString(5),
rs.getString(6));
movies.add(movie);
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
closeConnection(con);
closePrepStmt(prepStmt);
closeResultSet(rs);
}
return movies;
}
}
实现了从数据库取得数据后,我们还有编写服务方法来由servlet调用访问数据库,在service包中创建文件JsonService.java,编写方法getMovieString()
,getMovieArray()
分别取得对应电影信息。
package service;
import java.awt.List;
import java.util.ArrayList;
import org.json.JSONArray;
import org.json.JSONObject;
import database.MovieDB;
import model.Movie;
public class JsonService {
//由电影名获得电影描述字符串
public String getMovieString(String name) throws Exception
{
//从数据库中取得电影
Movie movie = new MovieDB().getMovie(name);
//转化为json格式
JSONObject jsonObject = new JSONObject();
jsonObject.put(name, movie);
return jsonObject.toString();
}
//获得某一页电影信息字符串
public String getMovieArray(int page) throws Exception
{
//从数据库中取得电影
ArrayList<Movie> movies = (ArrayList<Movie>) new MovieDB().getMovies(page);
JSONArray jsonMovies = new JSONArray();
//转化为json格式
for( int i = 0 ; i < movies.size() ; ++i)
{
JSONObject jsonMovie = new JSONObject(movies.get(i).toString());
jsonMovies.put(i+1, jsonMovie);
}
return jsonMovies.toString();
}
}
最后编写servlet,在servlet包中创建文件JsonServlet.java,类JsonServlet继承自HttpServlet,同时重载方法doGet(HttpServletRequest request, HttpServletResponse response)
,doPost(HttpServletRequest request, HttpServletResponse response)
这里使GET与POST方法执行相同的操作。首先规定编码方式,在http请求中获得请求参数,根据参数执行不同操作,然后将结果返回给请求者。
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import service.JsonService;
public class JsonServlet extends HttpServlet{
public JsonServlet()
{
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
//设置文档格式及编码方式
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
//获得输出文档writer
PrintWriter out = response.getWriter();
//获得请求参数
String name = request.getParameter("name");
int pageNum = Integer.parseInt(request.getParameter("page"));
//没有参数时打印对应错误
String movieString = "Error";
String movieArray = "Error2";
try
{
//这里规定一次访问只能有一个参数
if(name == null)
{
//获取某一页电影信息
movieArray = new JsonService().getMovieArray(pageNum);
out.println(movieArray);
}
else
{
//获取某一个电影信息
movieString = new JsonService().getMovieString(name);
out.println(movieString);
}
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//写出缓冲区
out.flush();
out.close();
}
}
最后,在目录:D:\jee-neon\workstation\doubanApp\WebContent\WEB-INF下编辑web.xml文件(如果没有自行创建即可),在<web-app>
元素内添加访问url
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/douban</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<servlet>
<servlet-name>JsonServlet</servlet-name>
<servlet-class>servlet.JsonServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JsonServlet</servlet-name>
<url-pattern>/JsonServlet</url-pattern>
</servlet-mapping>
到此,服务端代码就以及编写完毕。
客户端
客户端重要说明如何实现网络访问以及json解析。
首先创建项目后,在MainActivity中初始化变量private String baseURL = "http://localhost:8080/doubanApp/JsonServlet";
访问网络的步骤为首先根据url开启网络连接,设置网络访问方法,超时时间等属性,然后接受从服务端返回的内容,将其进行处理。android规定,在主线程中不能进行网络访问,所以我们在主线程中只做一些ui的更改,费时的网络操作开启子线程来完成。
public void sendRequest()
{
//开启线程访问网络
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try
{
//获得访问url
URL url = new URL(baseURL + "?page=" + pageNum);
//开启网络连接
connection = (HttpURLConnection)url.openConnection();
//设置请求方法
connection.setRequestMethod("GET");
//设置超时时间
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
//读入服务端返回内容
InputStream in = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while((line = reader.readLine()) != null)
{
response.append(line);
}
//解析json数据
parseJson(response.toString());
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if(reader != null)
{
try
{
reader.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
if(connection != null)
{
connection.disconnect();
}
}
}
}).start();
}
解析Json数据也比较简单,这里解析从服务端获得的一页电影字符串信息。
private void parseJson(final String response)
{
Movie movie;
try
{
//从服务端返回字符串初始化JSONArray
JSONArray movies = new JSONArray(response);
//movieList.clear();
//从JSONArray中取得JSONObject初始化movie对象
for( int i = 0 ; i < movies.length()-1 ; ++i)
{
JSONObject movieJson = movies.getJSONObject(i+1);
movie = new Movie(movieJson.getString("name"), movieJson.getString("info"),
movieJson.getDouble("rating"), movieJson.getInt("num"),
movieJson.getString("quote"), movieJson.getString("img_url"));
//Log.w(TAG, movie.getName());
//movieList.add(movie);
}
showResponse();
}
catch (Exception e)
{
e.printStackTrace();
}
}
这里我将获得的movie对象添加到movieList这样一个列表中,然后在showResponse()
方法中进行处理。
这里我使用了ListView来显示信息,然后对每一项电影名称编写了点击事件
总结
通过这么一个小demo尝试了一下简单的网络项目,学习了爬虫,数据库的使用,服务端客户端网络通信,客户端简单界面编写及信息显示,有这么一个小项目过程作为参考,相信之后在写其他项目时逻辑步骤应该会更加清晰吧