javaweb——书城项目——20200701-

    技术2022-07-11  118

    文章目录

    JavaEE项目三层架构分层1. 先创建书城需要的数据库和表2. 编写数据库表对应的javabean对象3. 编写工具类JdbcUtils4. 编写BaseDao5. 编写UserDao和测试*测试接口方法 6. 编写UserService和测试7. 注册页面中地址写法修改 MVC概念图书模块Dao(略)Service(略)Web(略) 前后台分离*注意表达重复提交情况一:刷新页面情况二:用户重复操作情况三:用户回退 动态判断action 谷歌Kaptcha验证码购物车技术选择实现功能

    JavaEE项目三层架构

    JavaEE三层架构:

    Web层/视图展现层——Servlet程序、Webwork、Structs、SpringMVC 获取请求参数,封装成为Bean对象调用service层处理业务响应数据给客户端,请求转发,重定向 Service业务层 ——Spring框架 处理业务逻辑调用持久层保存到数据库 Dao持久层,只负责跟数据库交互——JDBC DbUtils JdbcTemplate Mybatis Hibernate JPA CRUD操作

    分层

    分层的目的是为了解耦。解耦就是为了降低代码耦合度,方便后期维护和升级

    层包packageWeb层com.bookstore.web.servlet.controllerService业务层Service接口包com.bookstore.serviceService业务层Service接口实现类com.bookstore.service.implDao持久层Dao接口com.bookstore.daoDao持久层Dao接口实现类com.bookstore.dao.impl实体bean对象JavaBean类com.bookstore.pojo/entity/domain/bean测试包com.bookstore.test/junit工具类com.bookstore.utils

    1. 先创建书城需要的数据库和表

    drop database if exists book_store; create database book_store; use book_store; create table t_user ( id int auto_increment, username varchar(20) not null unique , password varchar(32) not null, email varchar(200) null, constraint t_user_pk primary key (id) ); insert into t_user(username,password,email) value('admin1','admin','admin@123.com'); select * from t_user;

    ps:在insert的时候,username、password等加了单引号,报语法错误。 原因未知。

    2. 编写数据库表对应的javabean对象

    3. 编写工具类JdbcUtils

    用的druid的jar包

    package com.bookstore.utils; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class JdbcUtils { private static DruidDataSource dataSource; static { Properties p = new Properties(); try (InputStream resourceAsStream = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"); ){ p.load(resourceAsStream); dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(p); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection(){ Connection connection = null; try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } return connection; } public static void close(Connection conn){ if (conn!=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }

    连接失败:

    严重: init datasource error, url: jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Client does not support authentication protocol requested by server; consider upgrading MySQL client at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:488) …… 严重: create connection SQLException, url: jdbc:mysql://localhost:3306?serverTimezone=Asia/Shanghai, errorCode 1251, state 08004

    从这一条状态码搜索,原因是jdbc版本5.1.7,低于Mysql版本8.0.19。 注意高版本配置properties时,连接的驱动地址要改成com.mysql.cj.jdbc.Driver (不该可以运行,但会警告Loading class ‘com.mysql.jdbc.Driver'. This is deprecated. The new driver class is ’com.mysql.cj.jdbc.Driver'.)

    参照:JDBC连接Mysql 8.0.12版本的几个注意事项 (链接)

    4. 编写BaseDao

    package com.bookstore.dao.impl; import com.bookstore.utils.JdbcUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import java.sql.Connection; import java.sql.SQLException; import java.util.List; public abstract class BaseDao { // 使用DbUtils导入操作 private QueryRunner queryRunner = new QueryRunner(); // insert/update/delete // return -1 means Failure public int update(String sql,Object...args){ try(Connection connection = JdbcUtils.getConnection();) { return queryRunner.update(connection,sql,args); } catch (SQLException e) { e.printStackTrace(); } return -1; } /* 查询返回一个JavaBean的sql语句 * @param type 返回的对象类型 @param sql 执行的sql语句 @param args sql 对应的参数值 @param <T> 返回的类型的泛型 */ public <T> Object queryForOne(Class<T> type,String sql,Object...args){ try(Connection con = JdbcUtils.getConnection();) { return queryRunner.query(con,sql,new BeanHandler<T>(type),args); } catch (SQLException e) { e.printStackTrace(); } return null; } /* 查询返回多个JavaBean的sql语句 * @param type 返回的对象类型 @param sql 执行的sql语句 @param args sql 对应的参数值 @param <T> 返回的类型的泛型 */ public <T> List<T> queryForList(Class<T> type, String sql, Object...args){ try(Connection con = JdbcUtils.getConnection();) { return (List<T>) queryRunner.query(con,sql,new BeanHandler<T>(type),args); } catch (SQLException e) { e.printStackTrace(); } return null; } /* 查询返回一行一列的sql语句 @param sql 执行的sql语句 @param args sql 对应的参数值 */ public Object queryForSingle(String sql, Object...args){ Connection connection = JdbcUtils.getConnection(); try { return queryRunner.query(connection,sql, new ScalarHandler(),args); } catch (Exception e) { e.printStackTrace(); }finally { JdbcUtils.close(connection); } return null; } } 在这里插入代码片

    中途java.sql.SQLException: No database selected Query:还是因为query里的单引号。

    5. 编写UserDao和测试

    public interface UserDao { /* * 根据用户名查询用户信息 */ public User queryUserByUsername(String username); /* * 根据用户名和密码查询用户信息 */ public User queryUserByUsernameAndPassword(String username,String password); /* * 保存用户信息 */ public int saveUser(User user); } package com.bookstore.dao.impl; import com.bookstore.dao.UserDao; import com.bookstore.pojo.User; public class UserDaoImpl extends BaseDao implements UserDao { @Override public User queryUserByUsername(String username) { String sql = "select 'id',username,password,email from t_user where username = ?"; return queryForOne(User.class,sql,username); } @Override public User queryUserByUsernameAndPassword(String username, String password) { String sql = "select id,username,password,email from t_user where username = ? and password = ?"; return queryForOne(User.class,sql,username,password); } @Override public int saveUser(User user) { String sql = "insert into t_user(username,password,email) value(?,?,?)"; return update(sql,user.getUsername(),user.getPassword(),user.getEmail()); } }

    *测试接口方法

    ctrl+shift+T -> new Test,自动生成类,再编写方法

    package com.bookstore.test; import com.bookstore.dao.UserDao; import com.bookstore.dao.impl.UserDaoImpl; import com.bookstore.pojo.User; import org.junit.Test; import static org.junit.Assert.*; public class UserDaoTest { @Test public void queryUserByUsername() { UserDao userDao = new UserDaoImpl(); System.out.println(userDao.queryUserByUsername("admin")); } @Test public void queryUserByUsernameAndPassword() { System.out.println(new UserDaoImpl().queryUserByUsernameAndPassword("admin", "admin")); } @Test public void saveUser() { final User user = new User(1, "ad", "ad", "dafa@com"); System.out.println(new UserDaoImpl().saveUser(user)); } }

    6. 编写UserService和测试

    package com.bookstore.service; import com.bookstore.pojo.User; public interface UserService { /* 注册用户 */ public void registUser(User user); public void login(User user); public boolean existsUsername(String name); }

    以上是接口,实现和测试略。

    7. 注册

    RegistServlet程序:

    获取请求参数检查,验证码是否正确 若不正确,返回注册页面。若正确,下一步。 检查用户名是否已存在 若不存在,则调用Service程序保存到数据库,再跳转到注册成功页面。若已存在,跳回注册页面。

    页面中地址写法

    建议:

    javaweb阶段:base+相对路径框架阶段:绝对路径

    修改

    注入参数: BeanUtils.populate(user,req.getParameter());改成jsp页面,显示回调参数等

    MVC概念

    Model 模型:将与业务逻辑相关的数据封装成具体的JavaBean类View 视图:只负责数据和界面的显示Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色。MVC是一种思想,主要目的是解耦合。

    图书模块

    Dao(略)

    Service(略)

    Web(略)

    html展示多个项目(遍历),需要导入jar包

    <c:forEach items = "${requestScope.books}" var="book"> <tr> <td>${book.name}</td> <td>${book.price}</td> <td>${book.author}</td> <td>${book.sales}</td> <td>${book.price}</td> <td><a href="/pages/manager/book_edit.jsp">修改</a></td> <td><a href="#">删除</a></td> </tr> </c:forEach>

    前后台分离

    前台:一般不需要权限检查,就可以访问的资源。后台:一般需要权限检查

    *注意表达重复提交

    情况一:刷新页面

    当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户刷新,就会发起浏览器记录的最后一次请求。原因:当使用请求转发时,两个操作是一个请求,那么当到达新页面(譬如添加图书操作之后,跳转到展示全部图书页面),此时若进行刷新,将重复添加图书操作。解决办法:使用重定向

    情况二:用户重复操作

    用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户认为提交失败,重复点击“提交”操作,也会造成表单重复提交若使用刷新的验证码,那么第二次提交的时候,验证码错误,则不会提交成功解决办法:验证码实现: 当用户第一次访问表单的时候生成一个随机的验证码字符串,显示在表单中;将验证码保存到session域中;验证表单项中的验证码和session域中的是否相等,且删除表单域中的验证码

    情况三:用户回退

    用户正常提交服务器,服务器也没有延迟,但是提交完成后,用户回退浏览器,重新点击“提交”操作,也会造成表单重复提交解决办法:验证码

    动态判断action

    修改图书界面既要用来添加图书,又要用来修改。

    方案一:可以发请求发起时,附带上当前要操作的值(add还是update),并注入到隐藏域中。方案二:判断是否有id参数,有就说明是修改。${ empty param.id?“add”:“update”}方案三:判断,request域中是否包含有修改的图书信息,如果有说明是修改操作。

    谷歌Kaptcha验证码

    导入mykaptcha-2.3.2.jar包在web.xml中配置 <servlet> <servlet-name>KaptchaServlet</servlet-name> <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>KaptchaServlet</servlet-name> <url-pattern>/kaptcha.jpg</url-pattern> </servlet-mapping> 在表单中使用img标签去显示验证码图片 <img alt="" src="${pageContext.request.contextPath}/kaptcha.jpg" style="float: right; margin-right: 50px;width: 100px;height: 40px;"> 比对验证码 //获取session中的验证码 String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY); //删除 req.getSession().removeAttribute(KAPTCHA_SESSION_KEY); if (token!=null&&token.equalsIgnoreCase(code)){ //略 }else { req.setAttribute("msg","验证码错误"); //略 } 点击图片刷新验证码 // 验证码刷新 $("#code_img").click(function () { this.src = "${basePath}/kaptcha.jpg?d="+new Date() ; });

    购物车

    技术选择

    session版本(把购物车信息保存到session域)数据库版本(保存到数据库)redis+数据库+Cookie

    实现功能

    商品加入/移除购物车清空购物车修改商品数量(结账非购物车功能)

    实现:

    CartServlet程序: addItem()deleteItem()clear()updateCount() Cart 购物车: addItem(CartItem)deleteItem(id)clear()updateCount(id,count)

    在HTTP协议中有一个请求头,Reference:它可把请求发起时,浏览器地址栏中的地址发送给服务器。

    Processed: 0.013, SQL: 9