文章目录
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操作
分层
分层的目的是为了解耦。解耦就是为了降低代码耦合度,方便后期维护和升级
层包package
Web层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 {
private QueryRunner queryRunner
= new QueryRunner();
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;
}
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
;
}
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
;
}
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;">
比对验证码
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:它可把请求发起时,浏览器地址栏中的地址发送给服务器。