狂神说Java【SMBMS】——SMBMS超市订单管理系统(九) ——项目感悟
∞、项目亮点/技巧/学习点
1.在前端使用EL表达式取值提示用户账号/密码错误
在登陆界面使用的EL表达式(
- 由于在用户登陆之前,这个div中的EL表达式取不到值,所以它不会显示,而一旦用户输入错误,后端就会向前端响应参数(req.setAttribute("error","用户名或密码错误")😉 + 重定向前端视图,这就使用div中的EL表达式取到了error的值,从而在登陆界面上进行显示
2.使用session存储用于的全部信息
在用户登陆上的时候,将从数据库中将用户的全部信息封装的pojo中,并将pojo存到session中,这就便于后面我们需要使用这个用户的某些信息的时候,不用再去查询数据库,而直接从session中获取对应的pojo对象的属性即可
- 比如修改密码的时候使用Ajax与session中User对象的passwor做对比
- 比如我们使用过滤器,在每一次用户发起请求的时候都去查看用户是否登陆,保证数据安全
- 比如我们用session控制用户最长在线时间,只要长时间没操作将自动注销登陆
- 比如注销功能,直接将session中用户对象属性删除即可配合过滤器将用户注销
- ...
3.SQL语句的动态拼接
- 在实现用户管理功能的模块中,首次用到了StringBuffer+集合List的组合来实现动态拼接SQL语句和存储pstmt对象所需要的参数数组的功能
- StringBuffer提供动态拼接SQL语句的功能,使得我们可以在基础的SQL语句上加上独特的筛选条件
- 集合List通过存储pstmt对象所需要的参数数组的功能,使得不管我们要在后面添加多少筛选条件都只是用一个集合List存储,在最后传输的时候调用List.toArray()即可将其转为数组
4.某一功能的开发流程
-
要在前端实现一个功能,我们需要按照Dao开始-->service-->servlet-->JSP的顺序来开发
并在进行写代码之前分析实现步骤/模块功能划分(很重要)
只有我们先想好了怎么做,然后再去编写代码才会快,且有条不紊,切忌看完要求之后马上开始写代码
5.在项目中多复用,少创建
-
本项目中,我们复用了servlet
- 通过前端传递的指定参数名称的参数的值,调用同一个servlet中不同的方法(将实现多个功能的servlet独立封装为一个个的方法)
- 通过前端传递的指定参数名称的参数的值,调用同一个servlet中不同的方法(将实现多个功能的servlet独立封装为一个个的方法)
在本项目中我们复用了SQL
- 前面第3点我们提到了使用"StringBuffer+集合List的组合来实现动态拼接SQL语句和存储pstmt对象所需要的参数数组的功能",这个功能给我们带来的效果就是通过判断传递的参数是否为NULL,我们可以动态的决定要在基础的SQL语句后面添加什么筛选条件,这些条件中的参数又是什么
- 复用的效果就是:使用这一条基本SQL语句+StringBuffer+集合List我们可以实现前端按照用户名查询、按照职位名称查询和整表查询
6.合理使用隐藏域
在本项目中隐藏域的使用是配合servlet复用使用的,我们就是通过隐藏域来让前端向后端提交method参数的值,然后通过判断method的值决定到底调用哪一个封装在复用servlet中的方法
7.合理使用过滤器
通过合理的适合于过滤器我们可以为后端服务器程序拦截许多的非法请求、垃圾请求、无效请求,也可以解决中文乱码问题
注意:在使用过滤器的时候,正常情况下我们只需要实现doFilter(),并且最重要的就是在方法的最后放行,否则这个请求是不能到达服务器程序并被响应的
chain.doFilter(req,resp);//过滤器放行
8.合理的编写公用的Dao
在本项目中,在最开始我们就编写了一个BaseDao类,这个类不针对某一张表进行CRUD,内部没有绑定任何的SQL语句
这个类我们一共只定义了1个static代码块+4个方法
- 静态代码块用于初始化JDBC4大参数
- 4个方法分别为
- 获取数据库连接对象
- 查询数据
- 修改数据
- 关闭资源
- 注意,这些方法之间不要出现相互调用的情况,我们应该在具体的对某一张表进行操作的时候再来调用这个类中的某些方法;且这些方法使用的数据库对象全部都需要从外部传递
package com.thhh.dao;
/**
* 注意理解这个类中的方法之所以要传入这些数据库操纵对象是因为为了统一的关闭资源
* 而传入的对象中可以都是null,具体的对象获取在方法里面进行;也可以只有conn实例化,其他对象的实例化同样放在具体的方法里进行
*/
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//操作数据库的公共类
public class BaseDao {
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;
//静态代码块用于初始化JDBC4大参数,且静态代码块只会在第一次调用这个类的时候执行一次
static {//静态代码块,在调用这个类的地方优先自动执行
//读取配置文件
//1、创建properties对象
Properties properties = new Properties();
//2、通过类加载器加载资源文件为字节输入流
InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
try {
properties.load(in);
} catch (IOException e) {
e.printStackTrace();
}
DRIVER = properties.getProperty("DRIVER");
URL = properties.getProperty("URL");
USERNAME = properties.getProperty("USERNAME");
PASSWORD = properties.getProperty("PASSWORD");
}
//1、编写获取数据库的连接对象的公共方法
public static Connection getConnection(){
Connection conn= null;
try {
//1、加载驱动类
Class.forName(DRIVER);
//2、获取连接对象
conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//2、编写查询公共方法 —— 注意查询的结果返回为ResultSet结果集
/**
* 用于查询数据的公共方法,注意:使用发送SQL语句的对象为PreparedStatement
* @param sql:查询的sql语句,由前端传递
* @param params:sql语句中占位符的值
*
*===============这下面的3个参数之所以在调用的时候传递原因就在于这3个都是资源,我们需要关闭,如果我们直接在这个方法里获取资源对象的话,那么我们就应该在这个方法中关闭资源===============
*===============但是调用处还在等待这个方法返回结果集,所以我们不应该在这个地方获取这3个对象,而应该由调用出传递,这样可以统一管理和关闭资源===============
*
* @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
* @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
* @param rs:返回的结果集,和pstmt只是传入null的引用。这个对象真正的实例化放在这个方法里面
*
* @return:返回查询到的结果集
*/
public static ResultSet executeQuery(String sql,Object[] params,Connection conn,PreparedStatement pstmt,ResultSet rs){
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循环遍历参数数组,并将参数设入SQL中
pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
}
rs = pstmt.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return rs;
}
//3、编写修改公共方法
/**
* 用于修改数据的公共方法,注意:使用发送SQL语句的对象为PreparedStatement
* @param sql:修改数据的sql语句模板
* @param params:模板中占位符对应的值
*
* =====下面两个对象需要调用出传入也是为了统一管理和关闭资源=====
* @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
* @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
*
* @return 返回受影响的行数
*/
public static int executeUpdate(String sql,Object[] params,Connection conn,PreparedStatement pstmt){
int result = 0;
try {
pstmt = conn.prepareStatement(sql);
for (int i=1;i<= params.length;i++){//循环遍历参数数组,并将参数设入SQL中
pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
}
result = pstmt.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return result;
}
//4、编写关闭资源公共方法
/**
* 关闭资源
* @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
* @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
* @param rs:返回的结果集,和pstmt只是传入null的引用。这个对象真正的实例化放在这个方法里面
* @return:返回关闭资源的结果
*
* 注意:关闭资源的时候要倒着关
*/
public static boolean close(Connection conn,PreparedStatement pstmt,ResultSet rs){
boolean flag = true;
if (rs!=null){
try {
rs.close();
rs = null;//让这个变量为null,gc就会自动对其进行回收
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;//关闭失败就将flag设置false
}
}
if (pstmt!=null){
try {
pstmt.close();
pstmt = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
if (conn!=null){
try {
conn.close();
conn = null;
} catch (SQLException throwables) {
throwables.printStackTrace();
flag = false;
}
}
return flag;//返回关闭结果
}
}
9.各层功能单一,使得项目维护更高效
这一点其实可以和第4点写在一起,但是为了加深印象就分开写了
第4点里面说了:项目开步骤应该按照Dao开始-->service-->servlet-->JSP的顺序来开发
这里补充说明:在开发的时候我们应该保证开发的每一层只是在完成自己这一层应该有的功能,应该其他层完成的功能,坚决不要混到其他层来开发
各层的功能遵循SpringMVC的划分:
-
DAO只专注于与数据库交互
- service专注于完成业务逻辑编写
-
servlet专注获取前端请求、调用service、和跳转视图
-
JSP专注于页面展示
各层之间的功能不要混用,各层之间的联系通过项目调用来构建
10. 多利用"面向接口编程"的思想
在本项目中,不管是开发Dao层还是开发service层,我们都使用了面向接口编程的思想
首先编写接口定义,再去实现接口定义;这样我们开发过程中就实现了设计和实现分离,整个项目的接口也就很清晰
11.多测试
在开发中,我们每写完一个功能之后,最好就使用junit对刚刚写的方法进行测试,这保证了开发的稳步前进
在Dao层有SQL语句的地方我们最好是写一句输出SQL语句的代码,这样每次执行的时候我们都可以看到SQL语句具体是什么样的,也容易进行排错
12.细心
开发过程中往往出现bug的地方都很微小,但是就是微小才不容易发现,从而导致了程序报错
- 比如:SQL语句拼接时的空格问题
- 比如:for循环时等号取不取的问题
- 比如:sql.append(" AND u.userRole = ?");中将u.userRole写成r.roleCode
13.小黄鸭调试法
传说中程序大师随身携带一只小黄鸭,在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码
一边阐述代码的意图一边观察它实际上的意图并做调试,这两者之间的任何不协调会变得很明显,并且更容易发现自己的错误
类似的,有一种现象叫做cone of answers,这是一个常见的现象。你的朋友跑来问你一个问题,但是当他自己把问题说完,或者说到一半的时候就想出了答案走了,留下一脸茫然的你。是的,这个时候你就起到了那只小黄鸭的作用
总的来说,在你试图表述自己的想法的过程中,自然地在促使自己去整理思路,重新考虑问题
14.一些简写(背下来)
- CRUD:crud是指在做计算处理时的增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写,crud主要被用在描述软件系统中数据库或者持久层的基本操作功能
- ACID:ACID,指数据库事务正确执行的四个基本要素的缩写,包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability);一个支持事务(Transaction)的数据库,必须要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求
15.实际开发中,多用事务
在实际的开发中,CRUD操作应该都是用事务操作来提交,这是因为这4种操作在执行的时候都可能失败,所以为了安全,最好的解决方案就是使用事务机制编写代码
注意事务的使用位置:在try开始的时候开启事务,在业务逻辑完成的时候提交事务,在catch中回滚事务
16.开发手册
推荐<阿里巴巴开发手册>,主要就是规约我们开发过程中的规范问题,写代码的结构问题 —— 很重要