基于JavaSE + JDBC的图书管理系统

Posted 庸人冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于JavaSE + JDBC的图书管理系统相关的知识,希望对你有一定的参考价值。

前言

这个数据库管理系统是纯后端的,基于JavaSE和JDBC,第三方jar包用了druid的数据库连接池,本来想用DButils做CRUD,后来想想自己对增删改查的流程还不够熟练所以就自己写了DAO。
在写之前本来觉得挺简单一个事,谁知道越写越复杂,越写发现自己技术的欠缺。所以这个图书管理系统,还存在许多问题,贴出来大家共同学习进步,也真心希望有大佬能提出一些宝贵的建议,在此提前感谢!

设计思路

此处内容属于补充内容,是我在写完整个管理系统后,对于整个项目逻辑的梳理,在实际的代码实现上,可能并没有达到这个设计思路所要求的标准,主要原因在于自己经验和技术的欠缺。
这个设计思路只是一个笼统的概述,不涉及到细节层面,并且这只是我个人的一些设计思路。作为一个Java入门学习者,写出来的目的不是为了授业,而只是为了总结与交流。
本来想写一个针对于细节的总结博客,但是时间实在有限,Java学习之路前途漫漫,还有很多未知的技术等着学习,所以不能止步于眼下的风景,而迷失了学习的目标。相信随着未来学习的深入,对于项目的设计会有更加清晰的认知。

整个项目的设计思路,我总结了3个方面,分别为:数据处理方面(后端),用户交互方面(前端),接口对接方面(前后端交互)。
数据处理方面

  1. 分析管理系统所需要的信息,然后捋清楚信息之间的关系,并以此创建数据表。
  2. 分析系统所针对的用户群体,以及用户群体间的关系,并以此创建数据表。
  3. 分析针对于不同用户所需要提供的功能。
  4. 将所有功能进行整合,把每一个功能对应到所要操作的数据表中,并统计出每一张表所要提供的CRUD操作、操作需求的参数、以及操作所返回的结果。(CRUD操作的设计要尽可能通用)。
  5. 根据数据表创建相应的实体类。
  6. 根据所需提供的操作,编写针对于不同表的CRUD方法以及sql语句,并提供接口。

用户交互方面

  1. 分析系统所需要的页面,以及每一个页面所需要提供的功能。
  2. 一般需要提供登录和注册页面,登录页面需要校验用户输入的用户名和密码,将用户名和密码信息传递至后端数据库中查找此条纪录,找到根据用户的身份登录到不同的页面,找不到则提示错误。(对于用户名和密码校验,后端也应该提供接口用于查询)
  3. 根据用户的身份跳转到相应的页面,该页面中提供了针对于该用户群体所需要的操作。
  4. 对于用户选择的不同操作。创建不同的接口与后端对应的接口进行交互。
  • 如果是添加操作,应该校验所用户输入的信息,对于一些特殊字段,应该先将信息传递至数据库中进行查询校验,并返回结果来决定后续操作是否可以正常进行。当操作完毕后,提示用户操作的结果。
  • 如果是删除操作,将用户输入的信息传递至数据库中进行delete操作,如果纪录存在,则会删除成功并返回操作所影响的纪录数。如果纪录不存在,则不会删除数据库任何纪录,并返回0。根据返回值来提示用户操作的结果。
  • 如果是修改操作,需要用户输入一个关键字段信息(一般为主键),并在数据库中查询是否存在。
    1)不存在则返回null,并提示重新输入。
    2)存在则返回承载了该条纪录的对象。并提示用户输入修改信息。(对此操作应该提供给用户一个不修改字段的权力,即如果用户不想修改该字段信息,那么可以提示输入回车表示默认不修改)。
    3)当用户输入完毕后,将用户输入的信息重新封装在返回的对象中,并传递至数据库执行update操作。
  • 如果是查询操作,针对于不同用户的查询权限,提供相应的功能,并创建不同的接口对接到后端对应的查询接口。对于查询的需求很多,可能涉及到单表查询,多表联查,条件查询,查询关键字段等等,对于不同的查询所需求的参数不同,并且返回的结果也不相同,对于这些查询需求,后端都应该提供相应的接口支持。这一块个人感觉是最难的地方,以自己目前的经验和技术很难设计出通用的查询接口。因此在具体实现时,出现了大量冗余的代码。

接口对接方面

  1. 针对于不同的功能需要提供不同的接口进行对接,完成前后端的交互。
  2. 前端需要将用户输入的信息通过接口传递至数据库中。
  3. 后端需要根据用户的请求对数据进行相应的CRUD操作。

接口对接相关的知识自己还没有学习过,所以在写的时候属于盲人抹黑的状态,需要什么接口就临时写一个,导致了代码在非常冗余,没用很好的运用到自己学习的面向对象特性。

数据库的搭建

刚开始准备写的时候,只想建3张表,一张user表,一张admin表和一张book表,但是后面开始写的时候发现3张表貌似不够用,所以最终我定义了5张表。分别为:

tb_booktype 图书类型表

表结构如下:

该表存放了图书的类型,作为下面的图书信息表的父表。

  1. tid 类型编号,主键自增
  2. type 图书类型,唯一键。

tb_bookinfo 图书信息表

表结构如下:

该表用于存放图书详细信息,包括:

  1. bid 图书编号,主键自增,主要用于查询。
  2. name 图书名称。
  3. author 作者
  4. publish 出版社
    nameauthorpublish 三个字段我做了联合唯一约束,目的防止插入相同的书籍纪录。
  5. price 价格
  6. num 图书数量
  7. tid 图书类型编号,作为外键关联到tb_booktype

tb_borrowinfo 图书借阅信息表

表结构如下:

该表用于保存图书借阅的信息,该表关联了tb_bookinfo表,以及下面介绍的tb_user表。

  1. bor_id 图书借阅编号,主键自增。
  2. book_id 图书编号,作为外键关联 tb_bookinfo表。
  3. user_id 用户编号,作为外键管理tb_user 表。
  4. bordate 借阅日期。
  5. retdate 应归还日期。在Java中默认为1个月之后。
  6. comment 备注。

tb_status 用户身份表

表结构如下:

该表用于纪录管理系统中用户的身份信息。在这个管理系统中,我没有分别定义管理员表和用户表而是用这个status表来区分不同的用户身份。

  1. sid 身份编号,主键自增,设置1为管理员,2为客户。
  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

  1. BookInfoDAO 接口
    BookInfoDAO 作为接口规定了针对于tb_bookinfo表的CRUD操作。
package com.goodgoodstudy.dao;

import com.goodgoodstudy.entity.BookInfo;

import java.sql.javaSE_《图书馆管理系统》_

Servlet+JDBC设计实现图书系统管理功能实现

使用JDBC+javafx写一个简单功能齐全的图书管理系统

javase基础第十五篇:图书管理系统

基于JavaWeb实现网上图书商城系统

JDBC-图书管理系统