Dao模式、单例模式和前端页面连接数据库实现登录功能
Dao
封装JDBC
上篇博客中说到使用JDBC连接数据库,其中存在几个弊端。
业务代码和数据访问代码耦合:
(1)可读性差
(2)不利于后期修改和维护
(3)不利于代码复用
解决办法:
(1)将相似功能的代码抽取封装成方法,减少代码冗余
(2)因为不同的数据库会有不同的实现,对数据库的操作一般抽取成接口,在以后的开发中可以降低耦合
隔离业务逻辑代码和数据访问代码。
隔离不同数据库的实现。
实现JDBC封装
将所有增删改查操作抽取成接口
定义实体类传输数据
将通用的操作(打开、关闭连接等)封装到工具类
数据库工具类BaseDao:增、删、改、查的通用方法
什么是Dao
Data Access Object(数据存取对象)
位于业务逻辑和持久化数据之间
实现对持久化数据的访问
Dao模式的组成部分
DAO接口
DAO实现类
实体类
数据库连接和关闭工具类
优势:
隔离了数据访问代码和业务逻辑代码
隔离了不同数据库实现
配置数据库访问参数
新闻信息存储在MySQL数据库中,但在开发和部署时有可能使用不同的数据库,也可能因为客户的需求而更换数据库产品。此时刚才读取新闻信息的做法有何弊端呢?
弊端
数据库发生改变时,要重新修改代码,重新编译和部署
解决
将数据库信息写在配置文件当中,让程序通过读取配置文件来获得这些信息
属性文件
后缀为.properties
数据格式为“键=值”
使用“#”来注释
Java中提供了Properties类来读取配置文件
示例一:Dao模式实现对新闻信息表的增删改查
(1)创建kgcnews数据库,并创建表,插入数据
CREATE DATABASE `kgcnews`;
USE `kgcnews`;
CREATE TABLE `news_category` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`createDate` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
)
insert into `news_category`(`id`,`name`,`createDate`) values
(1,'国内','2016-09-16 14:41:24'),
(2,'国际','2016-09-16 14:42:58'),
(3,'娱乐','2016-09-16 14:42:58'),
(4,'军事','2016-09-16 14:42:58'),
(5,'财经','2016-09-16 14:42:58'),
(6,'天气','2016-09-16 14:42:58');
CREATE TABLE `news_comment` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT 'id',
`newsId` bigint(10) DEFAULT NULL COMMENT '评论新闻id',
`content` varchar(2000) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '评论内容',
`author` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '评论者',
`ip` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '评论ip',
`createDate` datetime DEFAULT NULL COMMENT '发表时间',
PRIMARY KEY (`id`)
)
CREATE TABLE `news_detail` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT 'id',
`categoryId` bigint(10) DEFAULT NULL COMMENT '新闻类别id',
`title` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '新闻标题',
`summary` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '新闻摘要',
`content` text COLLATE utf8_unicode_ci COMMENT '新闻内容',
`picPath` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '新闻图片路径',
`author` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '发表者',
`createDate` datetime DEFAULT NULL COMMENT '创建时间',
`modifyDate` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
)
insert into `news_detail`(`id`,`categoryId`,`title`,`summary`,`content`,`picPath`,`author`,`createDate`,`modifyDate`) values
(1,1,'Java Web开课啦','Java Web课程重磅开课,学员福利','璇女神主讲,课工场倾力出品,Java Web课程开课了,等靠谱的你来报名!','','admin','2016-05-16 14:43:53','2015-05-16 14:43:53'),
(2,1,' 520课工场Java狂欢节','课工场准备了一大波福利:Java大赛、折扣课程,免费线下福利……你准备好了吗?','在这个五月,课工场Java学员突破100万人。为感谢所有学员的支持,我们特将5月20日定为【课工场Java狂欢节】。课工场准备了一大波福利:Java大赛、折扣课程,免费线下福利……你准备好了吗?',NULL,'admin','2016-05-16 14:43:53','2016-05-16 14:43:53');
CREATE TABLE `news_user` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`userName` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用户名',
`password` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '密码',
`email` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'email',
`userType` int(5) DEFAULT NULL COMMENT '用户类型 0:管理员 1:普通用户',
PRIMARY KEY (`id`)
)
insert into `news_user`(`id`,`userName`,`password`,`email`,`userType`) values
(1,'admin','admin','admin@kgc.cn',0),
(2,'user','user','user@kgc.cn',1),
(3,'test','test','test@kgc.cn',1);
(2)导入jar包和Tomcat:
hamcrest-core-1.3.jar
junit-4.12.jar
mysql-connector-java-5.1.38.jar
Tomcat 8.5.45
导入jar包及Tomcat相关配置设置:
1.Tomcat目录地址:
D:\1\apache-tomcat-8.5.45
2、在idea中导入jar包:
File->Project Structure->Modules->Dependencies->±>JARs or directories
导入所需要的jar包,点击左下角的Apply
3、在idea中配置tomcat:
File->Project Structure->Modules->+选择web->apply
点击右下角create artifact->apply
然后回到Modules->选择项目名->选择Dependencies添加依赖->Tomcat8.5.45 ok
4、配置项目Configurations:
run->Edit Configurations->
On update action:当发现更新时的操作 选择Update classes and resources
On frame deactivation:当IDEA 切换时的操作 (比如缩下去、打开网页等) 选择Update classes and resources
选择Deployment->选择+ Artifact ->
(3)在主目录下创建一个directory文件名称叫:rescoures
在rescourse文件下创建一个File文件名称叫:db.properties
(4)在db.properties中输入:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.136.30:3306/kgcnews
user=root
pwd=ok
注意:192.136.136.30是你的IP地址,每个人都是不一样的。
3306是接口。
kgcnews是你使用数据库的名称。
user是你的数据库用户名。
ok是你的mysql的登录密码。
(5)在src下创建一个包,名称为util。
在util下创建一个java程序,名称为Prop。
(6)在Prop.java中输入:
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
//配置文件
//为了要修改为修改
public class Prop {
private static Properties p = new Properties();
public static String getP(String param){
try {
p.load(new FileInputStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
return p.getProperty(param);
}
// public static void main(String[] args) {
// System.out.println(Prop.getP("driver"));
// }
}
注意:这里db.properties是相对路径,也可以换为绝对路径:“D:/ideashuju/20200821-web/resources/db.properties”
每个人的绝对路径是不一样的,你要根据你的db.properties文件所在路径输入正确的绝对路径。
(7)创建一个包,名称为Dao。
在Dao包下创建一个java程序,名称为NewsDao
(8)在NewsDao.java中输入:
import cn.kgc.kb09.util.Prop;
import java.sql.*;
public class NewsDao {//加载驱动,获取连接
private static String driver = Prop.getP("driver");
private static String url = Prop.getP("url");
private static String user = Prop.getP("user");
private static String pwd = Prop.getP("pwd");
public static Connection getCon(){
try {
Class.forName(driver);
return DriverManager.getConnection(url, user, pwd);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static ResultSet query(String sql, Object... params) {//查询方法
Connection conn = getCon();
PreparedStatement pstmt =null;
ResultSet rs=null;
try {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1,params[i]);
}
rs= pstmt.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
public static int update(String sql,Object...params) {//增删改方法
Connection con = getCon();
PreparedStatement pstmt=null;
try {
pstmt = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
return pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return -1;
}
}
(9)按下Ctrl+Shift+T创建测试,就会出现一个绿色的test包,里面有一个NewsDaoTest的java程序
(10)在NewsDaoTest.java中输入:
import org.junit.Test;
import java.sql.ResultSet;
import java.sql.SQLException;
public class NewsDaoTest {//查询
@Test
public void query() throws SQLException {
NewsDao newsDao = new NewsDao();
String sql = "SELECT summary FROM news_detail WHERE id=? OR id=?";
ResultSet rs = NewsDao.query(sql, 1, 2);
while (rs.next()) {
System.out.println(rs.getString("summary"));
}
}
@Test
public void testInsert() {//增加
NewsDao newsDao = new NewsDao();
String sql = "insert into news_detail(id,categoryId,title,summary,content,picPath,author,createDate,modifyDate)values(?,?,?,?,?,?,?,?,?)";
int a = NewsDao.update(sql, 3, 2, "特朗普下台了", "特朗普在从参议院的联名投诉下滚下台了", "特朗普世界头号恐怖分子", "null", "admin", "2020-08-21 21:10:10", "2020-08-22 08:00:00");
System.out.println(a>0 ? "增加成功" : "增加失败");
}
@Test
public void testDelete() {删除
NewsDao newsDao = new NewsDao();
String sql = "delete from news_detail where id=?";
int b = NewsDao.update(sql, 3);
System.out.println(b>0 ? "删除成功" : "删除失败");
}
@Test
public void testUpdate() {//改
NewsDao newsDao = new NewsDao();
String sql = "update news_detail set title=? where id=?";
int c = NewsDao.update(sql, "大数据开课了,明星老师罗鑫手把手面对面一对一教导,晚上深夜辅导赶紧加入吧,口碑走一波", 1);
System.out.println(c>0 ? "更改成功" : "更改失败");
}
}
输入结果:
注意:我这边在还没有增加之前,就执行了删除的操作,所以会出现删除失败的结果。如果是一步一步执行下来,就不会出现这样的问题。
单例模式
为何需要单例模式
BaseDao:操作数据库的基类
每个线程对系统操作都需new一个BaseDao实例
初始化时的I/O操作消耗系统资源,影响系统性能
对于每个线程,可共享一个实例
什么是单例模式
系统运行期间,有且仅有一个实例
一个类只有一个实例——最基本的要求
只提供私有构造器
它必须自行创建这个实例
定义了静态的该类私有对象
它必须自行向整个系统提供这个实例
提供一个静态的公有方法,返回创建或者获取本身的静态私有对象
在并发环境下上述的单例模式实现是否存在弊端,线程是否安全?是否会出现多个configManager实例?
懒汉模式
饿汉模式
懒汉模式
类加载时不创建实例,采用延迟加载的方式,在运行调用时创建实例
特点
线程不安全
延迟加载(lazy loading)
如何解决线程安全问题?
同步(synchronized)
恶汉模式
在类加载的时候,就完成初始化
特点:
线程安全
不具备延迟加载特性
注意:在整个程序运行期间,有且仅有一个实例。
若违背这一点,所设计的类就不是单例类
单例模式 | 懒汉模式 | 饿汉模式 |
---|---|---|
概念 | 在类加载器时不创建实例,采用 延迟加载的方式,在运行调用时创建实例 | 在类加载的时候,就完成初始化 |
特点 | 类加载速度快,但是运行时获取对象的速度较慢。——时间换空间 | 类加载较慢,但获取对象速度快。——空间换时间 |
延迟加载(lazy loa ding ) | 具备 | 不具备 |
线程安全 | 线程不安全 | 线程安全 |
前端页面连接数据库实现登录功能
实现登录功能:
登录页面(用户名和密码要能传到后台)index.jsp
Servlet接收数据–>数据传递给service–>service经过合法性判断–>调用Dao的查询方法–>Dao查询返回数据给service–>service进过业务逻辑判断–>把结果传回给servlet–>传回给页面
(1)配置web.xml文件
<?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">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>cn.kgc.kb09.controller.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login.html</url-pattern>
</servlet-mapping>
</web-app>
(2)配置初始页面index.jsp
<%--
Created by IntelliJ IDEA.
User: dongsijai
Date: 2020/8/21
Time: 10:49
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<h1>欢迎来到吃货联盟</h1>
<form action="login.html" method="get">
<p><input type="text" name="username"></p>
<p><input type="password" name="password"></p>
<p><input type="submit" value="点我登录"></p>
</form>
</body>
</html>
(3)在src文件下创建一个包,名称为controller
在下面再创建一个java程序,名称为LoginServlet
(4)在LoginServlet.java中输入:
import cn.kgc.kb09.service.LoginService;
import cn.kgc.kb09.service.LoginServiceImpl;
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 LoginService service;
//也需要调用service方法
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//先获取到前台传回的数据
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println(password);
System.out.println(username);
service = new LoginServiceImpl();
boolean canLogin = service.login(username, password);
// super.doGet(req, resp);
if (canLogin) {//可以登陆
req.setAttribute("rst","恭喜"+username+",登录成功!");
} else {//不可以登录
req.setAttribute("rst", "对不起,输入有误,登录不成功!");
}
//转发请求到新的页面
req.getRequestDispatcher("result.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
}
(5)在src文件下创建一个包,名称为service
在下面再创建一个接口,名称为LoginService
再创建一个java程序实现类,名称为LoginServicelmpl
(6)在接口LoginService中输入:
public interface LoginService {
boolean login(String username, String password);
}
(7)在实现类LoginServicelmpl.java中输入:
import cn.kgc.kb09.dao.LoginDao;
import cn.kgc.kb09.dao.LoginDaoImpl;
import cn.kgc.kb09.entity.User;
import com.mysql.jdbc.StringUtils;
//主要负责业务逻辑判断
public class LoginServiceImpl implements LoginService {
LoginDao dao;
@Override
public boolean login(String username, String password) {
dao = new LoginDaoImpl();
//合法性判断
if (username == null || password == null || username.trim().equals("")
|| password.trim().equals("")) {
return false;
}
//调用dao层的数据查询
User user = dao.queryUserByNameAndPwd(username, password);
//如果有返回的数据,则可以登录,否则不可以直接登录
if (user != null) {
return true;
}
return false;
}
}
(8)在src文件下创建一个包,名称为Dao
在下面再创建一个接口,名称为LoginDao
再创建一个java程序实现类,名称为LoginDaolmpl
再创建一个java程序,名称为PstDao
(9)在接口LoginDao中输入:
// An highlighted block
import cn.kgc.kb09.entity.User;
public interface LoginDao {
User queryUserByNameAndPwd(String username, String password);
}
(10)在实现类LoginDaolmpl.java中输入:
import cn.kgc.kb09.entity.User;
import java.sql.ResultSet;
import java.sql.SQLException;
public class LoginDaoImpl implements LoginDao {
@Override
public User queryUserByNameAndPwd(String username, String password) {
String sql = "select * from user_info where uname=? and password=?";
ResultSet rs = PstDao.query(sql, username, password);
User user=null;
try {
if (rs.next()) {
user = new User();
user.setUsername(rs.getString("uname"));
user.setPassword(rs.getString("password"));
}
}catch (SQLException e){
e.printStackTrace();
}
return user;
}
}
(11)在PstDao实现连接数据库查询,输入:
import cn.kgc.kb09.util.Prop;
import java.sql.*;
import java.time.Period;
public class PstDao {
private static String driver= Prop.getP("driver");
private static String url = Prop.getP("url");
private static String user=Prop.getP("user");
private static String pwd=Prop.getP("pwd");
public static Connection getConn(){
try {
Class.forName(driver);
return DriverManager.getConnection(url, user, pwd);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public static void close(Connection conn, PreparedStatement pst, ResultSet rs) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pst != null) {
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static ResultSet query(String sql,Object... params) {
Connection conn = getConn();
PreparedStatement pst=null;
ResultSet rs=null;
try {
pst = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pst.setObject(i + 1, params[i]);
}
rs=pst.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
public static int update(String sql, Object... params) {
Connection conn = getConn();
PreparedStatement pst=null;
try {
pst = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
pst.setObject(i + 1, params[i]);
}
return pst.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if (pst != null) {
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return -1;
}
}
(12)重新配置db.properties,输入:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.136.30:3306/userControl
user=root
pwd=ok
(13)重新配置Prop.java,输入:
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
//配置文件
//为了要修改为修改
public class Prop {
private static Properties p = new Properties();
public static String getP(String param){
try {
p.load(new FileInputStream("D:/ideashuju/20200821-web/resources/db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
return p.getProperty(param);
}
注意:在Java Web项目中要使用绝对路径。
(14)在src文件在下创建一个包,名称为entity,
在下面创建一个java程序,名称为User
(15)在User.java中输入:
//实体类
public class User {
private String username;
private String password;
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;
}
}
(16)在跟index.jsp同级目录下创建一个result.jsp,输入:
<%--
Created by IntelliJ IDEA.
User: dongsijai
Date: 2020/8/22
Time: 11:47
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录结果页面</title>
</head>
<body>
<%
Object rst = request.getAttribute("rst");
%>
<h1><%=rst%></h1>
</body>
</html>
(17)右击index.jsp,选择run执行这个程序,就会自动跳转出来一个界面
(18)输入用户名和密码,点击点我登录
到这一步,才是完整的实现了页面的登录。