我们填写资料选择住址时经常看到这样选择省、市、县/区的;这种我们称为三级联动,即只有选择了上一级,与上一级对应的下拉选项才会显示;我们来看一下这个原理是怎么实现的;
这个实现原理相对于刚入门Java的萌新来说是一个相当麻烦的过程,因此,我会将我用到的代码尽量的全放出来,供大家参考;先看这个项目的结构:
我们从下往上看:lib中的 gson-2.8.4.jar、mysql-connector-java-5.1.9.jar、servlet-api.jar 和js 中的 jquery-3.3.1.js 四个jar包是我们使用的工具包;
index.jsp是我们主界面,下文会细说;然后是app.properties文件;db包中的两个文件夹是我们定义的需要用到的方法和接口;代码如下:
DBUtil.java
package db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.jd.tool.PropertiesUtil;
/**
* 数据库连接工具类
*/
public class DBUtil {
//加载连接对象
public static Connection getConnection() throws Exception {
String driver=PropertiesUtil.getValue("db.driver");
String url=PropertiesUtil.getValue("db.url");
String userName=PropertiesUtil.getValue("db.user_name");
String password=PropertiesUtil.getValue("db.password");
Class.forName(driver);
return DriverManager.getConnection(url, userName,password);
}
//事务处理
public static boolean batchUpdate(String ...sqls) {
Connection connection = null;
Statement statement = null;
try {
connection = getConnection();
connection.setAutoCommit(false);
statement = connection.createStatement();
for(String data:sqls) {
statement.addBatch(data);
}
statement.executeBatch();
connection.commit();
return true;
} catch (Exception e) {
if (connection!=null) {
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
close(statement,connection);
}
return false;
}
//查询是否存在
public static boolean exist(String sql) {
class RowMapper implements IRowMapper{
boolean flag;
public void rowMapper(ResultSet resultSet) {
try {
if(resultSet.next()) {
flag=true;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
RowMapper rowMapper=new RowMapper();
select(sql,rowMapper);
return rowMapper.flag;
}
//查询是否存在,防注入
public static boolean exist(String sql,Object ...params) {
class RowMapper implements IRowMapper{
boolean flag;
public void rowMapper(ResultSet resultSet) {
try {
if(resultSet.next()) {
flag=true;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
RowMapper rowMapper=new RowMapper();
select(sql,rowMapper,params);
return rowMapper.flag;
}
//数据修改
public static boolean update(String sql) {
Connection connection=null;
Statement statement=null;
try {
connection=getConnection();
statement=connection.createStatement();
int affect=statement.executeUpdate(sql);
return affect>0;
} catch (Exception e) {
e.printStackTrace();
}finally {
close(statement,connection);
}
return false;
}
//数据修改,防SQL注入
public static boolean update(String sql,Object...params) {
Connection connection=null;
PreparedStatement preparedStatement = null;
try {
connection=getConnection();
preparedStatement = connection.prepareStatement(sql);
for(int i=0;i<params.length;i++) {
preparedStatement.setObject(i+1, params[i]);
}
int affect=preparedStatement.executeUpdate();
return affect>0;
} catch (Exception e) {
e.printStackTrace();
}finally {
close(preparedStatement,connection);
}
return false;
}
//数据查询
public static void select(String sql,IRowMapper irowmapper) {
ResultSet resultSet=null;
Statement statement=null;
Connection connection=null;
try {
connection=getConnection();
statement=connection.createStatement();
resultSet= statement.executeQuery(sql);
irowmapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
}finally {
close(resultSet,statement,connection);
}
}
//数据查询,防SQL注入 我们项目中用到的方法就是这个
public static void select(String sql,IRowMapper irowMapper,Object...params) {
ResultSet resultSet=null;
PreparedStatement preparedStatement = null;
Connection connection=null;
try {
connection=getConnection();
preparedStatement = connection.prepareStatement(sql);
for(int i=0;i<params.length;i++) {
preparedStatement.setObject(i+1, params[i]);
}
resultSet= preparedStatement.executeQuery();
irowMapper.rowMapper(resultSet);
} catch (Exception e) {
e.printStackTrace();
}finally {
close(resultSet,preparedStatement,connection);
}
}
//释放资源
public static void close(Statement statement,Connection connection) {
if (statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
if (connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//释放资源
public static void close(ResultSet resultSet,Statement statement,Connection connection) {
if (resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(statement,connection);
}
}
IRowMapper.java
package db;
import java.sql.ResultSet;
public interface IRowMapper {
void rowMapper(ResultSet resultset);
}
然后是我们用来方便处理存储数据的类 Area.java
package com.jd.vo;
public class Area {
private String id;
private String name;
private String code;
private String parentCode;
public Area(String id, String name, String code, String parentCode) {
super();
this.id = id;
this.name = name;
this.code = code;
this.parentCode = parentCode;
}
public String toString() {
return "Area [id=" + id + ", name=" + name + ", code=" + code + ", parentCode=" + parentCode + "]";
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getParentCode() {
return parentCode;
}
public void setParentCode(String parentCode) {
this.parentCode = parentCode;
}
}
紧接着一个工具类代码; PropertiesUtil.java
package com.jd.tool;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class PropertiesUtil {
private static Properties properties = new Properties();
static{
InputStream inputstream = PropertiesUtil.class.getClassLoader().getResourceAsStream("app.properties");
try {
properties.load(inputstream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getValue(String key) {
return properties.getProperty(key);
}
}
DataServlet.java 之前的 index.jsp 一样下文会重点说明;因此跳过;联动是连接的数据库,因此我们还缺一个数据库表就大功告成了;
https://download.csdn.net/download/qq_43705275/12016860 这是全国省市区的地区编号,我们下过来之后再复制粘贴在数据库中建表就OK了;
然后我们看 index.jsp 和 DataServlet.java ;
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script src="./js/jquery-3.3.1.js"></script>
</head>
<body>
<select id="province" onchange="getCity()">
<option value="">--请选择省份--</option>>
</select>
<select id="city" onchange="getCounty()">
<option value="">--请选择市区--</option>>
</select>
<select id="county">
<option value="">--请选择县区--</option>>
</select>
<script type="text/javascript">
var object={
url:"./DataServlet?code="+"",
type:"get",
success:function(data){
var provinces=JSON.parse(data); //将返回的数据赋值给provinces
var options="<option value=\"\">--请选择省份--</option>"
for(var i=0;i<provinces.length;i++){
options=options+"<option value=\"" + provinces[i].code+"\">"+provinces[i].name+"</option>";
}
$("#province").empty(); //清空原下拉框内的内容
$("#province").append(options); //将得到的options增添进去
}
}
$.ajax(object);
function getCity(){
var code=$("#province").val(); //获取上一个下拉框用户的选择项的value值
if(code==""){ //判断是否为空,为空说明用户回拉到默认选项,我们需要重置其余的下拉框
$("#city").empty(); //重置city下拉框
$("#city").append("<option value=\"\">--请选择市区--</option>");
$("#county").empty(); //重置county下拉框
$("#county").append("<option value=\"\">--请选择县区--</option>");
return;
}
var object={
url:"./DataServlet?code="+code,
type:"get",
success:function(data){
var city=JSON.parse(data);
var options="<option value=\"\">--请选择市区--</option>"
for(var i=0;i<city.length;i++){
options=options+"<option value=\"" + city[i].code+"\">"+city[i].name+"</option>";
}
$("#city").empty();
$("#city").append(options);
$("#county").empty();
$("#county").append("<option value=\"\">--请选择县区--</option>");
}
}
$.ajax(object);
}
function getCounty(){
var code=$("#city").val();
if(code==""){
$("#county").empty();
$("#county").append("<option value=\"\">--请选择市区--</option>");
return;
}
var object={
url:"./DataServlet?code="+$("#city").val(),
type:"get",
success:function(data){
var county=JSON.parse(data);
var options="<option value=\"\">--请选择县区--</option>"
for(var i=0;i<county.length;i++){
options=options+"<option value=\"" + county[i].code+"\">"+county[i].name+"</option>";
}
$("#county").empty();
$("#county").append(options);
}
}
$.ajax(object);
}
</script>
</body>
</html>
- 先创建三个下拉框;分别对应省、市、县区;
- 创建对象 object 对象并赋值:url:路径,?后可跟参数,这里是 code=""(DataServlet 的作用在下面,因此可先看下面再回头继续看这里);type使用方法的类型,默认为get,因此这里可以省略不写;success:方法 是对返回数据进行处理;
- 将返回的数据赋值给provinces;定义字符类型options,然后对返回的数据进行遍历,并把每个值都赋值给options;我们就会得到这样的options(下面是将options输出的结果):
- 之后我们将原select下拉框测内容清空;再将新得到的options添加到select下拉框内,我们就可以得到下图我们想要的效果:
- 到此第一个下拉框功能结束;以下两个方法的原理与该方法完全相同;过程不再详说;
注意:
- 当第一个下拉框改变时(即用户选择了数据),那么第二个下拉框就需要工作并返回对应选项了;因此这里我们使用onchange事件触发方法对用户的选择进行处理并返回处理结果;
- 当我们选择好省市县之后,我们再点击省下拉框选择第一个“--请选择省份--”时,因为此时的value值为 "" (即为空)时;getCity查询会误认为传入的value值为 "" ,因此 我们需要添加一个判断条件,判断getCity() 和 getCounty() 获取的值是否为空,若为空则说明用户选择了第一个默认的“请选择”项;因此我们需要对其余的下拉框进行重置;
- getCity方法中在对象里面对第三个下拉框重置的作用是:在用户已经查询完一组数据后,需要查询另外一组数据时,返回第二个下拉框内容的同时重置第三个下拉框到初始值,以防出现上次查询的县区还在本次查询中显示;
DataServlet.java
package com.jd.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
import com.jd.vo.Area;
import db.DBUtil;
import db.IRowMapper;
public class DataServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setCharacterEncoding("UTF-8"); //定义编码类型,防止Gson返回值乱码
String code=request.getParameter("code");
class RowMapper implements IRowMapper { //需要用到查询方法,因此这里定义一个继承 IRowMapper 接口的内部类
List<Area> list=new ArrayList<Area> (); //创建列表,用于存储数据
public void rowMapper(ResultSet resultSet) { //重写抽样放法
try {
while(resultSet.next()) { //遍历resultSet
list.add(new Area(resultSet.getString("id"),resultSet.getString("name"),resultSet.getString("code"),resultSet.getString("parent_code"))); //将遍历的结果创建成Area对象并赋值给list列表
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
String sql="select * from area where parent_code=?";
RowMapper rowMapper = new RowMapper();
DBUtil.select(sql, rowMapper, code); //调用方法查询(DBUtil中已备注的方法)
List<Area> list=rowMapper.list; //将list列表从内部类中提升
for(Area area:list) {
System.out.println(area);
}
PrintWriter out=response.getWriter(); //调用 PrintWriter 用于返回需要的数据
out.write(new Gson().toJson(list)); //将Java对象转换成js对象
out.flush(); //防止数据过小导致的无法输出的问题
out.close(); //关闭资源
}
}
DataServlet的用作就是根据用户端返回的查询条件对数据库进行查找并返回查询结果;其中,因为DataServlet中的对象是Java对象,而用户端则是js对象,因此,因此我们使用Gson将Java对象转换成js对象(转换的jar包有很多,这里我使用的是Gson)。