基于JavaSE + JDBC的图书管理系统
Posted 庸人冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于JavaSE + JDBC的图书管理系统相关的知识,希望对你有一定的参考价值。
前言
这个数据库管理系统是纯后端的,基于JavaSE和JDBC,第三方jar包用了druid的数据库连接池,本来想用DButils做CRUD,后来想想自己对增删改查的流程还不够熟练所以就自己写了DAO。
在写之前本来觉得挺简单一个事,谁知道越写越复杂,越写发现自己技术的欠缺。所以这个图书管理系统,还存在许多问题,贴出来大家共同学习进步,也真心希望有大佬能提出一些宝贵的建议,在此提前感谢!
设计思路
此处内容属于补充内容,是我在写完整个管理系统后,对于整个项目逻辑的梳理,在实际的代码实现上,可能并没有达到这个设计思路所要求的标准,主要原因在于自己经验和技术的欠缺。
这个设计思路只是一个笼统的概述,不涉及到细节层面,并且这只是我个人的一些设计思路。作为一个Java入门学习者,写出来的目的不是为了授业,而只是为了总结与交流。
本来想写一个针对于细节的总结博客,但是时间实在有限,Java学习之路前途漫漫,还有很多未知的技术等着学习,所以不能止步于眼下的风景,而迷失了学习的目标。相信随着未来学习的深入,对于项目的设计会有更加清晰的认知。
整个项目的设计思路,我总结了3个方面,分别为:数据处理方面(后端),用户交互方面(前端),接口对接方面(前后端交互)。
数据处理方面
- 分析管理系统所需要的信息,然后捋清楚信息之间的关系,并以此创建数据表。
- 分析系统所针对的用户群体,以及用户群体间的关系,并以此创建数据表。
- 分析针对于不同用户所需要提供的功能。
- 将所有功能进行整合,把每一个功能对应到所要操作的数据表中,并统计出每一张表所要提供的CRUD操作、操作需求的参数、以及操作所返回的结果。(CRUD操作的设计要尽可能通用)。
- 根据数据表创建相应的实体类。
- 根据所需提供的操作,编写针对于不同表的CRUD方法以及sql语句,并提供接口。
用户交互方面
- 分析系统所需要的页面,以及每一个页面所需要提供的功能。
- 一般需要提供登录和注册页面,登录页面需要校验用户输入的用户名和密码,将用户名和密码信息传递至后端数据库中查找此条纪录,找到根据用户的身份登录到不同的页面,找不到则提示错误。(对于用户名和密码校验,后端也应该提供接口用于查询)
- 根据用户的身份跳转到相应的页面,该页面中提供了针对于该用户群体所需要的操作。
- 对于用户选择的不同操作。创建不同的接口与后端对应的接口进行交互。
- 如果是添加操作,应该校验所用户输入的信息,对于一些特殊字段,应该先将信息传递至数据库中进行查询校验,并返回结果来决定后续操作是否可以正常进行。当操作完毕后,提示用户操作的结果。
- 如果是删除操作,将用户输入的信息传递至数据库中进行delete操作,如果纪录存在,则会删除成功并返回操作所影响的纪录数。如果纪录不存在,则不会删除数据库任何纪录,并返回0。根据返回值来提示用户操作的结果。
- 如果是修改操作,需要用户输入一个关键字段信息(一般为主键),并在数据库中查询是否存在。
1)不存在则返回null,并提示重新输入。
2)存在则返回承载了该条纪录的对象。并提示用户输入修改信息。(对此操作应该提供给用户一个不修改字段的权力,即如果用户不想修改该字段信息,那么可以提示输入回车表示默认不修改)。
3)当用户输入完毕后,将用户输入的信息重新封装在返回的对象中,并传递至数据库执行update操作。 - 如果是查询操作,针对于不同用户的查询权限,提供相应的功能,并创建不同的接口对接到后端对应的查询接口。对于查询的需求很多,可能涉及到单表查询,多表联查,条件查询,查询关键字段等等,对于不同的查询所需求的参数不同,并且返回的结果也不相同,对于这些查询需求,后端都应该提供相应的接口支持。这一块个人感觉是最难的地方,以自己目前的经验和技术很难设计出通用的查询接口。因此在具体实现时,出现了大量冗余的代码。
接口对接方面
- 针对于不同的功能需要提供不同的接口进行对接,完成前后端的交互。
- 前端需要将用户输入的信息通过接口传递至数据库中。
- 后端需要根据用户的请求对数据进行相应的CRUD操作。
接口对接相关的知识自己还没有学习过,所以在写的时候属于盲人抹黑的状态,需要什么接口就临时写一个,导致了代码在非常冗余,没用很好的运用到自己学习的面向对象特性。
数据库的搭建
刚开始准备写的时候,只想建3张表,一张user表,一张admin表和一张book表,但是后面开始写的时候发现3张表貌似不够用,所以最终我定义了5张表。分别为:
tb_booktype
图书类型表
表结构如下:
该表存放了图书的类型,作为下面的图书信息表的父表。
tid
类型编号,主键自增type
图书类型,唯一键。
tb_bookinfo
图书信息表
表结构如下:
该表用于存放图书详细信息,包括:
bid
图书编号,主键自增,主要用于查询。name
图书名称。author
作者publish
出版社
name
、author
和publish
三个字段我做了联合唯一约束,目的防止插入相同的书籍纪录。price
价格num
图书数量tid
图书类型编号,作为外键关联到tb_booktype
表
tb_borrowinfo
图书借阅信息表
表结构如下:
该表用于保存图书借阅的信息,该表关联了tb_bookinfo
表,以及下面介绍的tb_user
表。
bor_id
图书借阅编号,主键自增。book_id
图书编号,作为外键关联tb_bookinfo
表。user_id
用户编号,作为外键管理tb_user
表。bordate
借阅日期。retdate
应归还日期。在Java中默认为1个月之后。comment
备注。
tb_status
用户身份表
表结构如下:
该表用于纪录管理系统中用户的身份信息。在这个管理系统中,我没有分别定义管理员表和用户表而是用这个status表来区分不同的用户身份。
sid
身份编号,主键自增,设置1为管理员,2为客户。status
用户身份,唯一键,该管理系统中只设置了2个身份。
tb_user
用户信息表
表结构如下:
该表用于存放用户的个人信息,sid
作为外键与tb_status
连接,其它字段不太重要,不在此介绍。
功能
在这个管理系统中,用户分为管理员和客户,根据用户的身份不同给予了不同的功能。
管理员的功能
客户的功能
功能演示视频
视频地址:https://www.bilibili.com/video/BV18h411n75U/
CSDN我没有资格上传。。。
项目结构
对于MVC设计模式只是听过,但具体每一个模块是做什么的不太了解,所以就照葫芦画瓢仿造了一个。
在该管理系统中,我分成了5个package存放不同功能的类和接口。
dao模块
dao模块中是关于数据库增删改查的操作。
baseDAO
baseDAO中定义的是通用的增删改查操作,其它针对于一张表的xxxxDAOImpl类都继承了该类。
package com.goodgoodstudy.dao;
import com.goodgoodstudy.Util.JDBCUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// 定义为抽象类表示该类不能被实例化
public abstract class BaseDAO<T> {
// BaseDAO的子类(XxxDAOImpl)在创建对象时,会调用父类(即BaseDAO)的构造器,并加载父类的结构,代码块就会给Clazz初始化
private Class<T> clazz;
{
// 获取参数化类型实例, this既代表了实例化的子类
ParameterizedType paramType = (ParameterizedType) this.getClass().getGenericSuperclass();
// 调用getActualTypeArguments() 【获取泛型类型】
Type[] types = paramType.getActualTypeArguments();
// 只有一个泛型参数,因此types[0]就是子具体要操作的那个类。
clazz = (Class<T>) types[0];
}
/**
* 通用的增删改操作
*
* @param conn 数据库连接
* @param sql sql语句
* @param param 填充占位符的参数
* @return
*/
public int update(Connection conn, String sql, Object... param) throws SQLException {
// 获取数据库操作对象
PreparedStatement ps = null;
int updateCount = 0;
try {
ps = conn.prepareStatement(sql);
// 填充占位符
for (int i = 0; i < param.length; i++) {
ps.setObject(i + 1, param[i]); // 与数据库相关的索引都是从1开始
}
// 执行操作
updateCount = ps.executeUpdate(); // 接收操作所影响的纪录数量
} catch (SQLException e) {
throw new SQLException(e);
} finally {
JDBCUtils.closeResource(null, ps, null); // 关闭资源,考虑事务,因此不在方法内关闭外部获取的数据库连接
}
return updateCount;
}
/**
* 通用的查询操作(获取一条纪录)
*
* @param conn
* @param sql
* @param param
* @return
*/
public T getInstance(Connection conn, String sql, Object... param) {
PreparedStatement ps = null;
ResultSet rs = null;
T t = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < param.length; i++) {
ps.setObject(i + 1, param[i]);
}
// 执行查询操作, 并获取结果集
rs = ps.executeQuery();
// 获取结果集元数据
ResultSetMetaData md = rs.getMetaData();
// 通过元数据获取查询纪录的列数
int colCount = md.getColumnCount();
// 判断结果集下一条是否有数据,
if (rs.next()) {
t = clazz.getDeclaredConstructor().newInstance(); // 利用clazz动态创建相应的对象,用了接收结果集的数据
for (int i = 0; i < colCount; i++) {
Object colVal = rs.getObject(i + 1); // 获取每一列的数据
String colLabel = md.getColumnLabel(i + 1); // 获取每一列的别名
Field field = clazz.getDeclaredField(colLabel); // 通过别名创建对应属性的Field对象
field.setAccessible(true); // 访问私有属性,需要设置为true
field.set(t, colVal); // 给t对象的field属性赋值为colVal
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return t; // 返回承载了纪录的对象
}
/**
* 通用的查询操作(获取多条纪录)
*
* @param conn
* @param sql
* @param param
* @return
*/
public List<T> getForList(Connection conn, String sql, Object... param) {
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < param.length; i++) {
ps.setObject(i + 1, param[i]);
}
rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData();
int colCount = md.getColumnCount();
// 获取多条纪录,将if 改为 while
while (rs.next()) {
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 0; i < colCount; i++) {
Object colVal = rs.getObject(i + 1);
String colLabel = md.getColumnLabel(i + 1);
Field field = clazz.getDeclaredField(colLabel);
field.setAccessible(true);
field.set(t, colVal);
}
list.add(t); // 将接收纪录的对象添加近list中
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return list; // 返回list
}
/**
* 用于多表联查返回一条记录
*
* @param conn
* @param sql
* @param param
* @return
*/
public Map<String, Object> getInstanceMap(Connection conn, String sql, Object... param) {
PreparedStatement ps = null;
ResultSet rs = null;
Map<String, Object> row = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < param.length; i++) {
ps.setObject(i + 1, param[i]);
}
rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData();
int colCount = md.getColumnCount();
if (rs.next()) {
// 创建Map接收查询到的一行纪录
row = new HashMap<>();
for (int i = 0; i < colCount; i++) {
row.put(md.getColumnLabel(i + 1), rs.getObject(i + 1));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return row; // 返回row
}
/**
* 用于多表联查返回多条纪录,将查询的每条纪录以键值对的形式封装在Map中,再存入List集合中并返回
*
* @param conn
* @param sql
* @param param
* @return
*/
public List<Map<String, Object>> getForMapList(Connection conn, String sql, Object... param) {
PreparedStatement ps = null;
ResultSet rs = null;
List<Map<String, Object>> list = new ArrayList<>();
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < param.length; i++) {
ps.setObject(i + 1, param[i]);
}
rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData();
int colCount = md.getColumnCount();
while (rs.next()) {
// 创建Map接收每一行的纪录
Map<String, Object> row = new HashMap<>();
for (int i = 0; i < colCount; i++) {
row.put(md.getColumnLabel(i + 1), rs.getObject(i + 1));
}
list.add(row); // 将map添加进list
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
JDBCUtils.closeResource(null, ps, rs);
}
return list; // 返回list
}
/**
* 用于查询特殊值的通用方法,返回一个特殊值
* 例如:查询总数,最大值等
*
* @param conn
* @param sql
* @param params
* @return
*/
public Object getValue(Connection conn, String sql, Object... params) {
PreparedStatement ps = null;
Object value = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
value = null;
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
rs = ps.executeQuery();
if (rs.next()) {
value = rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return value;
}
}
BookInfoDAO 与 BookInfoDAOImpl
BookInfoDAO
接口
BookInfoDAO
作为接口规定了针对于tb_bookinfo
表的CRUD操作。
package com.goodgoodstudy.dao;
import com.goodgoodstudy.entity.BookInfo;
import java.sql.javaSE_《图书馆管理系统》_