1、JavaEE三层架构

客户端提交发送到后端的数据,会先在Web层获取并封装成Bean对象,然后调用Service层的方法处理业务,再调用Dao层的方法与数据库交互实现数据的持久化存储
分层的目的是为了解耦,所谓的解耦就是降低代码的耦合度,方便项目后期的维护与升级。

在BookS这个项目中,src文件夹用来存放源代码,web用来存放页面资源
在src这个文件夹中有一个总包“com.chang”存在,以及存放连接数据库所需的配置文件“jdbc.properties”。
在“com.chang”包中又存在6个包,其各自功能如下:
| 包名 | 作用 |
|---|---|
| dao | 服务于service层,实现业务数据的增删改查。 |
| pojo | 存放业务所需要的对象,例如User对象 |
| service | 存放针对某一对象所要做的操作,例如增删改查 |
| test | 针对一些工具类、或者对象方法进行测试 |
| utils | 工具类,用来存放一些工具,例如连接数据库 |
| web | 视图层,通过servlet获取前端的数据 |
针对具体使用流程,可将上述表格中的内容稍作调整,(代码编写过程中可将第2到第5顺序调换 1->5->4->3->2->6),6可以穿插进行
| 包名 | 作用 |
|---|---|
| pojo | 存放User对象 |
| web | 获取前端数据,例如名称、密码、验证码,然后可构建一个User对象,针对次对象,要进行业务处理,则需要用到service包中UserService类,“private UserService userService = new UserServiceImpl();”然后使用相关方法“userService.registUser(User);” |
| service | 为了更好的解耦,首先编写UserService的接口,然后在impl包中编写UserServiceImpl实现类,在此类中因为要实现与数据库的交互,所以要在dao包中实现UserDao类,“private UserDao userDao = new UserDaoImpl();”,“userDao.saveUser(user);” |
| dao | 在此包中除了要编写实现某类对象数据库操作的接口以及实现类,还要编写基础类,具体实现类要继承实现类并实现接口方法,例如UserDaoImpl extends BaseDao implements UserDao。在BaseDao类中要实现具体的增删改查,则要使用utils包中的JdbcUtils类,以及一些其他的官方包“private QueryRunner queryRunner=new QueryRunner();” |
| utils | 在这个包中编写JdbcUtils方法读取src目录下的配置文件,实现与数据库的连接、关闭操作 |
| test | 在这个包中对实现的方法进行测试,例如JdbcUtils、UserService、UserDao |

2、创建项目需要的数据库和表
在终端的MySQL中输入如下命令:
drop database if exists book;create database book;use book;create table t_user(`id` int primary key auto_increment,`username` varchar(20) not null unique,`password` varchar(32) not null,`email` varchar(200));insert into t_user(`username`,`password`,`email`) values('admin','admin','admin@atguigu.com');select * from t_user;

3、编写数据库表对应的JavaBean对象
User
package com.chang.pojo;public class User {private Integer id;private String username;private String password;private String email;public User() {}public User(Integer id, String username, String password, String email) {this.id = id;this.username = username;this.password = password;this.email = email;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", email='" + email + '\'' +'}';}}
4、编写工具类 JdbcUtils
4.1、导入jar包(数据库和连接池需要)
放在WEB-INF文件夹的lib中
Druid首先是一个数据库连接池。Druid是目前最好的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。

4.2、在src源码目录下编写配置文件
jdbc.properties
username=rootpassword=mysqlurl=jdbc:mysql://localhost:3306/bookdriverClassName=com.mysql.jdbc.DriverinitialSize=5maxActive=10
4.3、在utils文件夹中编写JdbcUtils
实现与数据库的连接以及断开
JdbcUtils
调用DruidDataSources生成一个dataSource,将读取好的配置文件放到dataSource中,之后使用getConnection()进行连接,使用close()进行关闭。
package com.chang.utils;import com.alibaba.druid.pool.DruidDataSource;import com.alibaba.druid.pool.DruidDataSourceFactory;import java.io.IOException;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 prop=new Properties();// 读取 jdbc.properties属性配置文件InputStream input=JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");try {// 从流中加载数据prop.load(input);} catch (IOException e) {e.printStackTrace();}// 创建 数据库连接 池try {dataSource=(DruidDataSource) DruidDataSourceFactory.createDataSource(prop);} catch (Exception e) {e.printStackTrace();}}/*** 获取数据库连接池中的连接* @return 如果返回null,说明获取连接失败<br/>有值就是获取连接成功*/public static Connection getConnection(){Connection conn=null;try {conn=dataSource.getConnection();} catch (SQLException e) {e.printStackTrace();}return conn;}/*** 关闭连接,放回数据库连接池* @param conn*/public static void close(Connection conn){if(conn!=null){try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}}
4.4、对JdbcUtils进行测试
在测试之前先将所需的包进行导入
- hamcrest-core-1.3.jar
- junit-4.12.jar
在test包中生曾JdbcUtilsTest类:IDEA中的快捷键是ctrl + shift + T
package com.chang.test;import com.chang.utils.JdbcUtils;import org.junit.Test;import java.sql.Connection;import static org.junit.Assert.*;public class JdbcUtilsTest {@Testpublic void testJdbcUtils() {for(int i=0;i<100;i++){Connection conn= JdbcUtils.getConnection();System.out.println(conn);JdbcUtils.close(conn);}}}
5、编写BaseDao实现数据库的增删改查
5.1、导入DBUtils的jar包
commons-dbutils-1.3.jar
5.2、编写BaseDao
package com.chang.dao.impl;import com.chang.utils.JdbcUtils;import org.apache.commons.dbutils.QueryRunner;import org.apache.commons.dbutils.handlers.BeanHandler;import org.apache.commons.dbutils.handlers.BeanListHandler;import org.apache.commons.dbutils.handlers.ScalarHandler;import java.sql.Connection;import java.sql.SQLException;import java.util.List;public class BaseDao {//使用DBUtils操作数据库private QueryRunner queryRunner=new QueryRunner();/*** update() 方法用来执行:Insert\Update\Delete语句** @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数*/public int update(String sql,Object... args){Connection conn= JdbcUtils.getConnection();try {return queryRunner.update(conn,sql,args);} catch (SQLException e) {e.printStackTrace();}finally {JdbcUtils.close(conn);}return -1;}/*** 查询返回一个javaBean的sql语句** @param type 返回的对象类型* @param sql 执行的sql语句* @param args sql对应的参数值* @param <T> 返回的类型的泛型* @return 返回查找到的对象,若有多个,只返回第一个*/public <T> T queryForOne(Class<T> type,String sql,Object... args){Connection conn=JdbcUtils.getConnection();try {return queryRunner.query(conn,sql,new BeanHandler<T>(type),args);} catch (SQLException e) {e.printStackTrace();}finally {JdbcUtils.close(conn);}return null;}/*** 查询返回多个javaBean的sql语句** @param type 返回的对象类型* @param sql 执行的sql语句* @param args sql对应的参数值* @param <T> 返回的类型的泛型* @return 返回查找到的对象列表*/public <T>List<T> queryForList(Class<T> type,String sql,Object...args){Connection conn=JdbcUtils.getConnection();try {return queryRunner.query(conn,sql,new BeanListHandler<T>(type),args);} catch (SQLException e) {e.printStackTrace();}finally {JdbcUtils.close(conn);}return null;}/*** 执行返回一行一列的sql语句* @param sql 执行的sql语句* @param args sql对应的参数值* @return 返回查找到的索引行(id),若有多个,只返回第一个*/public Object queryForStringValue(String sql,Object...args){Connection conn=JdbcUtils.getConnection();try {return queryRunner.query(conn,sql,new ScalarHandler(),args);} catch (SQLException e) {e.printStackTrace();}finally {JdbcUtils.close(conn);}return null;}}
5.3、对BaseDao类进行测试
BaseDaoTest
package com.chang.test;import com.chang.dao.impl.BaseDao;import org.junit.Test;import com.chang.pojo.User;import java.util.List;import static org.junit.Assert.*;public class BaseDaoTest {private BaseDao baseDao=new BaseDao();@Testpublic void update() {String sql = "insert into t_user(`username`,`password`,`email`) values(?,?,?)";int getLine = baseDao.update(sql, "xiaoming", "123456", "1234@qq.com");System.out.println(getLine);int getLine2 = baseDao.update(sql, "xiaohong", "123456", "4321@qq.com");System.out.println(getLine2);/*11*/}@Testpublic void queryForOne() {String sql="select * from t_user where password=?";User user = baseDao.queryForOne(User.class, sql, "123456");System.out.println(user);/*User{id=7, username='xiaoming', password='123456', email='1234@qq.com'}*/}@Testpublic void queryForList() {String sql="select * from t_user where password=?";List<User> users = baseDao.queryForList(User.class, sql, "123456");System.out.println(users);/*[User{id=7, username='xiaoming', password='123456', email='1234@qq.com'},User{id=8, username='xiaohong', password='123456', email='4321@qq.com'}]*/}@Testpublic void queryForStringValue() {String sql="select * from t_user where password=?";Object admin = baseDao.queryForStringValue(sql, "123456");System.out.println(admin);/*7*/}}

6、编写UserDao(对BaseDao封装)
6.1、接口:UserDao
package com.chang.dao;import com.chang.pojo.User;public interface UserDao {/*** 根据用户名查询用户信息* @param username 用户名* @return 如果返回 null,说明没有这个用户。反之亦然*/public User queryUserByUsername(String username);/*** 根据 用户名和密码查询用户信息* @param username* @param password* @return 如果返回 null,说明用户名或密码错误,反之亦然*/public User queryUserByUsernameAndPassword(String username,String password);/*** 保存用户信息* @param user* @return 返回-1 表示操作失败,其他是 sql 语句影响的行数*/public int saveUser(User user);}
6.2、实现类:UserDaoImpl
package com.chang.dao.impl;import com.chang.dao.UserDao;import com.chang.pojo.User;public class UserDaoImpl extends BaseDao implements UserDao {@Overridepublic User queryUserByUsername(String username) {String sql="select * from t_user where username=?";return queryForOne(User.class,sql,username);}@Overridepublic User queryUserByUsernameAndPassword(String username, String password) {String sql="select * from t_user where username=? and password =?";return queryForOne(User.class,sql,username,password);}@Overridepublic int saveUser(User user) {String sql="insert into t_user(`username`,`password`,`email`) values(?,?,?)";return update(sql,user.getUsername(),user.getPassword(),user.getEmail());}}
6.3、测试:UserDaoTest
package com.chang.test;import com.chang.dao.UserDao;import com.chang.dao.impl.UserDaoImpl;import com.chang.pojo.User;import org.junit.Test;import static org.junit.Assert.*;public class UserDaoTest {UserDao userDao=new UserDaoImpl();@Testpublic void queryUserByUsername() {User user = userDao.queryUserByUsername("xiaoming");System.out.println(user);if (userDao.queryUserByUsername("admin1234") == null ){System.out.println("用户名可用!");} else {System.out.println("用户名已存在!");}/*User{id=7, username='xiaoming', password='123456', email='1234@qq.com'}用户名可用!*/}@Testpublic void queryUserByUsernameAndPassword() {if ( userDao.queryUserByUsernameAndPassword("admin","admin1234") == null) {System.out.println("用户名或密码错误,登录失败");} else {System.out.println("查询成功");}/*用户名或密码错误,登录失败*/}@Testpublic void saveUser() {System.out.println( userDao.saveUser(new User(null,"wzg168", "123456", "wzg168@qq.com")) );/*1*/}}
7、编写UserService
7.1、接口:UserService
package com.chang.service;import com.chang.pojo.User;public interface UserService {/*** 注册用户* @param user*/public void registUser(User user);/*** 登录* @param user* @return 如果返回 null,说明登录失败,返回有值,是登录成功*/public User login(User user);/*** 检查 用户名是否可用* @param username* @return 返回 true 表示用户名已存在,返回 false 表示用户名可用*/public boolean existsUsername(String username);}
7.2、实现类:UserServiceImpl
package com.chang.service.impl;import com.chang.dao.UserDao;import com.chang.dao.impl.UserDaoImpl;import com.chang.pojo.User;import com.chang.service.UserService;public class UserServiceImpl implements UserService {private UserDao userDao=new UserDaoImpl();@Overridepublic void registUser(User user) {userDao.saveUser(user);}@Overridepublic User login(User user) {return userDao.queryUserByUsernameAndPassword(user.getUsername(), user.getPassword());}@Overridepublic boolean existsUsername(String username) {if (userDao.queryUserByUsername(username) == null) {// 等于null,说明没查到,没查到表示可用return false;}return true;}}
7.3、测试:UserSerciceTest
package com.chang.test;import com.chang.pojo.User;import com.chang.service.UserService;import com.chang.service.impl.UserServiceImpl;import org.junit.Test;import static org.junit.Assert.*;public class UserServiceTest {UserService userService = new UserServiceImpl();@Testpublic void registUser() {userService.registUser(new User(null, "bbj168", "666666", "bbj168@qq.com"));userService.registUser(new User(null, "abc168", "666666", "abc168@qq.com"));}@Testpublic void login() {User wzg168 = userService.login(new User(null, "wzg168", "123456", null));System.out.println(wzg168);/*User{id=9, username='wzg168', password='123456', email='wzg168@qq.com'}*/}@Testpublic void existsUsername() {if (userService.existsUsername("wzg16888")) {System.out.println("用户名已存在!");} else {System.out.println("用户名可用!");}/*用户名可用!*/}}
8、编写Web层
8.1、实现用户注册的功能
8.1.1、图解用户注册的流程:

8.1.2、修改 regist.html 和 regist_success.html 页面
1、添加 base 标签
<!--写 base 标签,永远固定相对路径跳转的结果--><base href="http://localhost:8080/book/">
2、修改 base 标签对页面中所有相对路径的影响(浏览器 F12,哪个报红,改哪个)
以下是几个修改的示例:
<link type="text/css" rel="stylesheet" href="static/css/style.css" ><script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
3、修改注册表单的提交地址和请求方式

8.1.3、编写 RegistServlet 程序
package com.chang.web;import com.chang.pojo.User;import com.chang.service.UserService;import com.chang.service.impl.UserServiceImpl;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class RegistServlet extends HttpServlet {private UserService userService=new UserServiceImpl();@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1、获取请求的参数String username=req.getParameter("username");String password=req.getParameter("password");String email=req.getParameter("email");String code=req.getParameter("code");//2、检查 验证码是否正确 === 写死,要求验证码为:abcdeif("abcde".equalsIgnoreCase(code)){//3、检查用户名是否可用if(userService.existsUsername(username)){System.out.println("用户名[" + username + "]已存在!");//跳回注册页面req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);}else {//可用//调用Sservice保存到数据库userService.registUser(new User(null,username,password,email));//跳到注册成功页面 regist_success.htmlreq.getRequestDispatcher("/pages/user/regist_success.html").forward(req,resp);}}else{System.out.println("验证码[" + code + "]错误");req.getRequestDispatcher("/pages/user/regist.html").forward(req, resp);}}}
8.1.4、在web.xml配置servlet
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet><servlet-name>RegistServlet</servlet-name><servlet-class>com.chang.web.RegistServlet</servlet-class></servlet><servlet-mapping><servlet-name>RegistServlet</servlet-name><url-pattern>/registServlet</url-pattern></servlet-mapping></web-app>
8.2、实现用户登录的功能
8.2.1、图解用户登录

8.2.2、修改 login.html 页面和 login_success.html 页面
1、添加 base 标签
<!--写 base 标签,永远固定相对路径跳转的结果--><base href="http://localhost:8080/book/">
2、修改 base 标签对页面中所有相对路径的影响(浏览器 F12,哪个报红,改哪个)
以下是几个修改的示例:
<link type="text/css" rel="stylesheet" href="static/css/style.css" ><script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
3、修改 login.html 表单的提交地址和请求方式

8.2.3、编写LoginServlet程序
package com.chang.web;import com.chang.pojo.User;import com.chang.service.UserService;import com.chang.service.impl.UserServiceImpl;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class LoginServlet extends HttpServlet {private UserService userService = new UserServiceImpl();@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 1、获取请求的参数String username = req.getParameter("username");String password = req.getParameter("password");// 调用 userService.login()登录处理业务User loginUser = userService.login(new User(null, username, password, null));// 如果等于null,说明登录 失败!if (loginUser == null) {// 跳回登录页面req.getRequestDispatcher("/pages/user/login.html").forward(req, resp);} else {// 登录 成功//跳到成功页面login_success.htmlreq.getRequestDispatcher("/pages/user/login_success.html").forward(req, resp);}}}
8.2.4、在web.xml配置servlet
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><servlet><servlet-name>RegistServlet</servlet-name><servlet-class>com.chang.web.RegistServlet</servlet-class></servlet><servlet-mapping><servlet-name>RegistServlet</servlet-name><url-pattern>/registServlet</url-pattern></servlet-mapping><servlet><servlet-name>LoginServlet</servlet-name><servlet-class>com.chang.web.LoginServlet</servlet-class></servlet><servlet-mapping><servlet-name>LoginServlet</servlet-name><url-pattern>/loginServlet</url-pattern></servlet-mapping></web-app>
