8,案例
需求:完成品牌数据的增删改查操作
这个功能我们之前一直在做,而这个案例是将今天学习的所有的内容(包含 MVC模式 和 三层架构)进行应用,并将整个流程贯穿起来。
8.1 环境准备
环境准备工作,我们分以下步骤实现:
创建新的模块 brand_demo,引入坐标
创建三层架构的包结构
数据库表 tb_brand
实体类 Brand
MyBatis 基础环境
Mybatis-config.xml
BrandMapper.xml
BrandMapper接口
8.1.1 创建工程
创建新的模块 brand_demo,引入坐标。我们只要分析出要用到哪儿些技术,那么需要哪儿些坐标也就明确了
需要操作数据库。mysql的驱动包
要使用mybatis框架。mybaits的依赖包
web项目需要用到servlet和jsp。servlet和jsp的依赖包
需要使用 jstl 进行数据展示。jstl的依赖包
pom.xml 内容如下:
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>brand-demo</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!--mybatis--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.5</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.34</version></dependency><!--servlet--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope></dependency><!--jsp--><dependency><groupId>javax.servlet.jsp</groupId><artifactId>jsp-api</artifactId><version>2.2</version><scope>provided</scope></dependency><!--jstl--><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version></plugin></plugins></build></project>
8.1.2 创建包
创建不同的包结构,用来存储不同的类。包结构如下
8.1.3 创建表
--删除tb_brand表droptableifexiststb_brand; --创建tb_brand表createtabletb_brand( --id主键idintprimarykeyauto_increment, --品牌名称brand_namevarchar(20), --企业名称company_namevarchar(20), --排序字段orderedint, --描述信息descriptionvarchar(100), --状态:0:禁用1:启用statusint); --添加数据insertintotb_brand (brand_name, company_name, ordered, description, status) values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0), ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1), ('小米', '小米科技有限公司', 50, 'are you ok', 1);
8.1.4 创建实体类
在 pojo 包下创建名为 Brand 的类。
publicclassBrand { // id 主键privateIntegerid; // 品牌名称privateStringbrandName; // 企业名称privateStringcompanyName; // 排序字段privateIntegerordered; // 描述信息privateStringdescription; // 状态:0:禁用 1:启用privateIntegerstatus; publicBrand() { } publicBrand(Integerid, StringbrandName, StringcompanyName, Stringdescription) { this.id=id; this.brandName=brandName; this.companyName=companyName; this.description=description; } publicBrand(Integerid, StringbrandName, StringcompanyName, Integerordered, Stringdescription, Integerstatus) { this.id=id; this.brandName=brandName; this.companyName=companyName; this.ordered=ordered; this.description=description; this.status=status; } publicIntegergetId() { returnid; } publicvoidsetId(Integerid) { this.id=id; } publicStringgetBrandName() { returnbrandName; } publicvoidsetBrandName(StringbrandName) { this.brandName=brandName; } publicStringgetCompanyName() { returncompanyName; } publicvoidsetCompanyName(StringcompanyName) { this.companyName=companyName; } publicIntegergetOrdered() { returnordered; } publicvoidsetOrdered(Integerordered) { this.ordered=ordered; } publicStringgetDescription() { returndescription; } publicvoidsetDescription(Stringdescription) { this.description=description; } publicIntegergetStatus() { returnstatus; } publicvoidsetStatus(Integerstatus) { this.status=status; } publicStringtoString() { return"Brand{"+"id="+id+", brandName='"+brandName+'\''+", companyName='"+companyName+'\''+", ordered="+ordered+", description='"+description+'\''+", status="+status+'}'; } }
8.1.5 准备mybatis环境
定义核心配置文件 Mybatis-config.xml ,并将该文件放置在 resources 下
<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!--起别名--><typeAliases><packagename="com.itheima.pojo"/></typeAliases><environmentsdefault="development"><environmentid="development"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="com.mysql.jdbc.Driver"/><propertyname="url"value="jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true"/><propertyname="username"value="root"/><propertyname="password"value="1234"/></dataSource></environment></environments><mappers><!--扫描mapper--><packagename="com.itheima.mapper"/></mappers></configuration>
在 resources 下创建放置映射配置文件的目录结构 com/itheima/mapper,并在该目录下创建映射配置文件 BrandMapper.xml
<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.itheima.mapper.BrandMapper"></mapper>
8.2 查询所有
当我们点击 index.html 页面中的 查询所有 这个超链接时,就能查询到上图右半部分的数据。
对于上述的功能,点击 查询所有 超链接是需要先请后端的 servlet ,由 servlet 跳转到对应的页面进行数据的动态展示。而整个流程如下图:
8.2.1 编写BrandMapper
在 mapper 包下创建创建 BrandMapper 接口,在接口中定义 selectAll() 方法
/*** 查询所有* @return*/"select * from tb_brand") (List<Brand>selectAll();
8.2.2 编写工具类
在 com.itheima 包下创建 utils 包,并在该包下创建名为 SqlSessionFactoryUtils 工具类
publicclassSqlSessionFactoryUtils { privatestaticSqlSessionFactorysqlSessionFactory; static { //静态代码块会随着类的加载而自动执行,且只执行一次try { Stringresource="mybatis-config.xml"; InputStreaminputStream=Resources.getResourceAsStream(resource); sqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream); } catch (IOExceptione) { e.printStackTrace(); } } publicstaticSqlSessionFactorygetSqlSessionFactory(){ returnsqlSessionFactory; } }
8.2.3 编写BrandService
在 service 包下创建 BrandService 类
publicclassBrandService { SqlSessionFactoryfactory=SqlSessionFactoryUtils.getSqlSessionFactory(); /*** 查询所有* @return*/publicList<Brand>selectAll(){ //调用BrandMapper.selectAll()//2. 获取SqlSessionSqlSessionsqlSession=factory.openSession(); //3. 获取BrandMapperBrandMappermapper=sqlSession.getMapper(BrandMapper.class); //4. 调用方法List<Brand>brands=mapper.selectAll(); sqlSession.close(); returnbrands; } }
8.2.4 编写Servlet
在 web 包下创建名为 SelectAllServlet 的 servlet,该 servlet 的逻辑如下:
调用 BrandService 的 selectAll() 方法进行业务逻辑处理,并接收返回的结果
将上一步返回的结果存储到 request 域对象中
跳转到 brand.jsp 页面进行数据的展示
具体的代码如下:
"/selectAllServlet") (publicclassSelectAllServletextendsHttpServlet { privateBrandServiceservice=newBrandService(); protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { //1. 调用BrandService完成查询List<Brand>brands=service.selectAll(); //2. 存入request域中request.setAttribute("brands",brands); //3. 转发到brand.jsprequest.getRequestDispatcher("/brand.jsp").forward(request,response); } protectedvoiddoPost(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { this.doGet(request, response); } }
8.2.5 编写brand.jsp页面
将资料 资料\2. 品牌增删改查案例\静态页面 下的 brand.html 页面拷贝到项目的 webapp 目录下,并将该页面改成 brand.jsp 页面,而 brand.jsp 页面在表格中使用 JSTL 和 EL表达式 从request域对象中获取名为 brands 的集合数据并展示出来。页面内容如下:
<%pagecontentType="text/html;charset=UTF-8"language="java"%><%taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title></head><body><hr><tableborder="1"cellspacing="0"width="80%"><tr><th>序号</th><th>品牌名称</th><th>企业名称</th><th>排序</th><th>品牌介绍</th><th>状态</th><th>操作</th></tr><c:forEachitems="${brands}"var="brand"varStatus="status"><tralign="center"><%--<td>${brand.id}</td>--%><td>${status.count}</td><td>${brand.brandName}</td><td>${brand.companyName}</td><td>${brand.ordered}</td><td>${brand.description}</td><c:iftest="${brand.status == 1}"><td>启用</td></c:if><c:iftest="${brand.status != 1}"><td>禁用</td></c:if><td><ahref="/brand-demo/selectByIdServlet?id=${brand.id}">修改</a><ahref="#">删除</a></td></tr></c:forEach></table></body></html>
8.2.6 测试
启动服务器,并在浏览器输入 http://localhost:8080/brand-demo/index.html,看到如下 查询所有 的超链接,点击该链接就可以查询出所有的品牌数据
为什么出现这个问题呢?是因为查询到的字段名和实体类对象的属性名没有一一对应。相比看到这大家一定会解决了,就是在映射配置文件中使用 resultMap 标签定义映射关系。映射配置文件内容如下:
<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEmapperPUBLIC"-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mappernamespace="com.itheima.mapper.BrandMapper"><resultMapid="brandResultMap"type="brand"><resultcolumn="brand_name"property="brandName"></result><resultcolumn="company_name"property="companyName"></result></resultMap></mapper>
并且在 BrandMapper 接口中的 selectAll() 上使用 @ResuleMap 注解指定使用该映射
/*** 查询所有* @return*/"select * from tb_brand") ("brandResultMap") (List<Brand>selectAll();
重启服务器,再次访问就能看到我们想要的数据了
8.3 添加
上图是做 添加 功能流程。点击 新增 按钮后,会先跳转到 addBrand.jsp 新增页面,在该页面输入要添加的数据,输入完毕后点击 提交 按钮,需要将数据提交到后端,而后端进行数据添加操作,并重新将所有的数据查询出来。整个流程如下:
接下来我们根据流程来实现功能:
8.3.1 编写BrandMapper方法
在 BrandMapper 接口,在接口中定义 add(Brand brand) 方法
"insert into tb_brand values(null,#{brandName},#{companyName},#{ordered},#{description},#{status})") (voidadd(Brandbrand);
8.3.2 编写BrandService方法
在 BrandService 类中定义添加品牌数据方法 add(Brand brand)
/*** 添加* @param brand*/publicvoidadd(Brandbrand){ //2. 获取SqlSessionSqlSessionsqlSession=factory.openSession(); //3. 获取BrandMapperBrandMappermapper=sqlSession.getMapper(BrandMapper.class); //4. 调用方法mapper.add(brand); //提交事务sqlSession.commit(); //释放资源sqlSession.close(); }
8.3.3 改进brand.jsp页面
我们需要在该页面表格的上面添加 新增 按钮
<inputtype="button"value="新增"id="add"><br>
并给该按钮绑定单击事件,当点击了该按钮需要跳转到 brand.jsp 添加品牌数据的页面
<script>document.getElementById("add").onclick=function (){ location.href="/brand-demo/addBrand.jsp"; } </script>
==注意:==该 script 标签建议放在 body 结束标签前面。
8.3.4 编写addBrand.jsp页面
从资料 资料\2. 品牌增删改查案例\静态页面 中将 addBrand.html 页面拷贝到项目的 webapp 下,并改成 addBrand.jsp 动态页面
<%pagecontentType="text/html;charset=UTF-8"language="java"%><!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>添加品牌</title></head><body><h3>添加品牌</h3><formaction="/brand-demo/addServlet"method="post">品牌名称:<inputname="brandName"><br>企业名称:<inputname="companyName"><br>排序:<inputname="ordered"><br>描述信息:<textarearows="5"cols="20"name="description"></textarea><br>状态:<inputtype="radio"name="status"value="0">禁用<inputtype="radio"name="status"value="1">启用<br><inputtype="submit"value="提交"></form></body></html>
8.3.5 编写servlet
在 web 包下创建 AddServlet 的 servlet,该 servlet 的逻辑如下:
设置处理post请求乱码的字符集
接收客户端提交的数据
将接收到的数据封装到 Brand 对象中
调用 BrandService 的add() 方法进行添加的业务逻辑处理
跳转到 selectAllServlet 资源重新查询数据
具体的代码如下:
"/addServlet") (publicclassAddServletextendsHttpServlet { privateBrandServiceservice=newBrandService(); protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { //处理POST请求的乱码问题request.setCharacterEncoding("utf-8"); //1. 接收表单提交的数据,封装为一个Brand对象StringbrandName=request.getParameter("brandName"); StringcompanyName=request.getParameter("companyName"); Stringordered=request.getParameter("ordered"); Stringdescription=request.getParameter("description"); Stringstatus=request.getParameter("status"); //封装为一个Brand对象Brandbrand=newBrand(); brand.setBrandName(brandName); brand.setCompanyName(companyName); brand.setOrdered(Integer.parseInt(ordered)); brand.setDescription(description); brand.setStatus(Integer.parseInt(status)); //2. 调用service 完成添加service.add(brand); //3. 转发到查询所有Servletrequest.getRequestDispatcher("/selectAllServlet").forward(request,response); } protectedvoiddoPost(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { this.doGet(request, response); } }
8.3.6 测试
点击 brand.jsp 页面的 新增 按钮,会跳转到 addBrand.jsp页面
点击 提交 按钮,就能看到如下页面,里面就包含我们刚添加的数据
8.4 修改
点击每条数据后面的 编辑 按钮会跳转到修改页面,如下图:
在该修改页面我们可以看到将 编辑 按钮所在行的数据 回显 到表单,然后需要修改那个数据在表单中进行修改,然后点击 提交 的按钮将数据提交到后端,后端再将数据存储到数据库中。
从上面的例子我们知道 修改 功能需要从两方面进行实现,数据回显和修改操作。
8.4.1 回显数据
上图就是回显数据的效果。要实现这个效果,那当点击 修改 按钮时不能直接跳转到 update.jsp 页面,而是需要先带着当前行数据的 id 请求后端程序,后端程序根据 id 查询数据,将数据存储到域对象中跳转到 update.jsp 页面进行数据展示。整体流程如下
8.4.1.1 编写BrandMapper方法
在 BrandMapper 接口,在接口中定义 selectById(int id) 方法
/*** 根据id查询* @param id* @return*/"select * from tb_brand where id = #{id}") ("brandResultMap") (BrandselectById(intid);
8.4.1.2 编写BrandService方法
在 BrandService 类中定义根据id查询数据方法 selectById(int id)
/*** 根据id查询* @return*/publicBrandselectById(intid){ //调用BrandMapper.selectAll()//2. 获取SqlSessionSqlSessionsqlSession=factory.openSession(); //3. 获取BrandMapperBrandMappermapper=sqlSession.getMapper(BrandMapper.class); //4. 调用方法Brandbrand=mapper.selectById(id); sqlSession.close(); returnbrand; }
8.4.1.3 编写servlet
在 web 包下创建 SelectByIdServlet 的 servlet,该 servlet 的逻辑如下:
获取请求数据 id
调用 BrandService 的 selectById() 方法进行数据查询的业务逻辑
将查询到的数据存储到 request 域对象中
跳转到 update.jsp 页面进行数据真实
具体代码如下:
"/selectByIdServlet") (publicclassSelectByIdServletextendsHttpServlet { privateBrandServiceservice=newBrandService(); protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { //1. 接收idStringid=request.getParameter("id"); //2. 调用service查询Brandbrand=service.selectById(Integer.parseInt(id)); //3. 存储到request中request.setAttribute("brand",brand); //4. 转发到update.jsprequest.getRequestDispatcher("/update.jsp").forward(request,response); } protectedvoiddoPost(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { this.doGet(request, response); } }
8.4.1.4 编写update.jsp页面
拷贝 addBrand.jsp 页面,改名为 update.jsp 并做出以下修改:
title 标签内容改为 修改品牌
form 标签的 action 属性值改为 /brand-demo/updateServlet
input 标签要进行数据回显,需要设置 value 属性
品牌名称:<inputname="brandName"value="${brand.brandName}"><br>企业名称:<inputname="companyName"value="${brand.companyName}"><br>排序:<inputname="ordered"value="${brand.ordered}"><br>
textarea 标签要进行数据回显,需要在标签体中使用 EL表达式
描述信息:<textarearows="5"cols="20"name="description">${brand.description} </textarea><br>
单选框使用 if 标签需要判断 brand.status 的值是 1 还是 0 在指定的单选框上使用 checked 属性,表示被选中状态
状态:<c:iftest="${brand.status == 0}"><inputtype="radio"name="status"value="0"checked>禁用<inputtype="radio"name="status"value="1">启用<br></c:if><c:iftest="${brand.status == 1}"><inputtype="radio"name="status"value="0">禁用<inputtype="radio"name="status"value="1"checked>启用<br></c:if>
综上,update.jsp 代码如下:
<%pagecontentType="text/html;charset=UTF-8"language="java"%><%taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>修改品牌</title></head><body><h3>修改品牌</h3><formaction="/brand-demo/updateServlet"method="post">品牌名称:<inputname="brandName"value="${brand.brandName}"><br>企业名称:<inputname="companyName"value="${brand.companyName}"><br>排序:<inputname="ordered"value="${brand.ordered}"><br>描述信息:<textarearows="5"cols="20"name="description">${brand.description} </textarea><br>状态:<c:iftest="${brand.status == 0}"><inputtype="radio"name="status"value="0"checked>禁用<inputtype="radio"name="status"value="1">启用<br></c:if><c:iftest="${brand.status == 1}"><inputtype="radio"name="status"value="0">禁用<inputtype="radio"name="status"value="1"checked>启用<br></c:if><inputtype="submit"value="提交"></form></body></html>
8.4.2 修改数据
做完回显数据后,接下来我们要做修改数据了,而下图是修改数据的效果:
在修改页面进行数据修改,点击 提交 按钮,会将数据提交到后端程序,后端程序会对表中的数据进行修改操作,然后重新进行数据的查询操作。整体流程如下:
8.4.2.1 编写BrandMapper方法
在 BrandMapper 接口,在接口中定义 update(Brand brand) 方法
/*** 修改* @param brand*/"update tb_brand set brand_name = #{brandName},company_name = #{companyName},ordered = #{ordered},description = #{description},status = #{status} where id = #{id}") (voidupdate(Brandbrand);
8.4.2.2 编写BrandService方法
在 BrandService 类中定义根据id查询数据方法 update(Brand brand)
/*** 修改* @param brand*/publicvoidupdate(Brandbrand){ //2. 获取SqlSessionSqlSessionsqlSession=factory.openSession(); //3. 获取BrandMapperBrandMappermapper=sqlSession.getMapper(BrandMapper.class); //4. 调用方法mapper.update(brand); //提交事务sqlSession.commit(); //释放资源sqlSession.close(); }
8.4.2.3 编写servlet
在 web 包下创建 AddServlet 的 servlet,该 servlet 的逻辑如下:
设置处理post请求乱码的字符集
接收客户端提交的数据
将接收到的数据封装到 Brand 对象中
调用 BrandService 的update() 方法进行添加的业务逻辑处理
跳转到 selectAllServlet 资源重新查询数据
具体的代码如下:
"/updateServlet") (publicclassUpdateServletextendsHttpServlet { privateBrandServiceservice=newBrandService(); protectedvoiddoGet(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { //处理POST请求的乱码问题request.setCharacterEncoding("utf-8"); //1. 接收表单提交的数据,封装为一个Brand对象Stringid=request.getParameter("id"); StringbrandName=request.getParameter("brandName"); StringcompanyName=request.getParameter("companyName"); Stringordered=request.getParameter("ordered"); Stringdescription=request.getParameter("description"); Stringstatus=request.getParameter("status"); //封装为一个Brand对象Brandbrand=newBrand(); brand.setId(Integer.parseInt(id)); brand.setBrandName(brandName); brand.setCompanyName(companyName); brand.setOrdered(Integer.parseInt(ordered)); brand.setDescription(description); brand.setStatus(Integer.parseInt(status)); //2. 调用service 完成修改service.update(brand); //3. 转发到查询所有Servletrequest.getRequestDispatcher("/selectAllServlet").forward(request,response); } protectedvoiddoPost(HttpServletRequestrequest, HttpServletResponseresponse) throwsServletException, IOException { this.doGet(request, response); } }
存在问题:update.jsp 页面提交数据时是没有携带主键数据的,而后台修改数据需要根据主键进行修改。
针对这个问题,我们不希望页面将主键id展示给用户看,但是又希望在提交数据时能将主键id提交到后端。此时我们就想到了在学习 HTML 时学习的隐藏域,在 update.jsp 页面的表单中添加如下代码:
<%--隐藏域,提交id--%><inputtype="hidden"name="id"value="${brand.id}">12update.jsp页面的最终代码如下:<%pagecontentType="text/html;charset=UTF-8"language="java"%><%taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>修改品牌</title></head><body><h3>修改品牌</h3><formaction="/brand-demo/updateServlet"method="post"><%--隐藏域,提交id--%><inputtype="hidden"name="id"value="${brand.id}">品牌名称:<inputname="brandName"value="${brand.brandName}"><br>企业名称:<inputname="companyName"value="${brand.companyName}"><br>排序:<inputname="ordered"value="${brand.ordered}"><br>描述信息:<textarearows="5"cols="20"name="description">${brand.description} </textarea><br>状态:<c:iftest="${brand.status == 0}"><inputtype="radio"name="status"value="0"checked>禁用<inputtype="radio"name="status"value="1">启用<br></c:if><c:iftest="${brand.status == 1}"><inputtype="radio"name="status"value="0">禁用<inputtype="radio"name="status"value="1"checked>启用<br></c:if><inputtype="submit"value="提交"></form></body></html>