MySQL数据库JDBC编程(三):三层架构基础解析

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
这篇博客是MySQL入门专栏的最后一篇博客🐵,会解析Java程序中的三层架构,这个在做Java项目时是必用的,MySQL入门专栏的知识基本上已经够应对基础项目了🐵
请添加图片描述
🎇个人主页:洋不写bug的博客
🎇所属专栏:数据库
🎇mysql8.0和navicate的安装:mysql安装教程
🎇铁汁们对于MySQL数据库的各种常用核心语法,都可以在上面的数据库专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,表的准备

在数据库中创建一个学生表,一个班级表,插入数据,sql语句如下所示:

CREATE TABLE IF NOT EXISTS class (
    id BIGINT PRIMARY KEY COMMENT '班级ID',
    name VARCHAR(20) COMMENT '班级名称'
) COMMENT '班级信息表';

INSERT INTO class (id, name)
VALUES 
    (1, 'Java班'),
    (2, 'C++班'),
    (3, 'python班');
    
    
    
    
CREATE TABLE IF NOT EXISTS student (
    id BIGINT PRIMARY KEY COMMENT '学生ID',
    name VARCHAR(20) COMMENT '学生姓名',
    sno VARCHAR(10) COMMENT '学号',
    age INT COMMENT '年龄',
    gender TINYINT(1) COMMENT '性别(1表示男)',
    enroll_date DATE COMMENT '入学日期',
    class_id BIGINT COMMENT '所属班级ID'
) COMMENT '学生信息表';



INSERT INTO student (id, name, sno, age, gender, enroll_date, class_id)
VALUES 
    (1, '唐三藏', '100001', 20, 1, '1986-09-01', 1),
    (2, '孙悟空', '100002', 18, 1, '1986-09-01', 1),
    (3, '猪悟能', '100003', 18, 1, '1986-09-01', 1),
    (4, '沙悟净', '100004', 18, 1, '1986-09-01', 1),
    (5, '宋江', '200001', 18, 1, '1986-09-01', 1),
    (6, '武松', '200002', 18, 1, '2000-09-01', 2),
    (7, '李逵', '200003', 18, 1, '2000-09-01`在这里插入代码片`', 2),
    (8, '不想毕业', '200003', 18, 1, '2000-09-01', 2);



为了简化JDBC操作,建议写一个DBUtil类,里面把连接代码什么的都写好,再搞两个close方法(方法重载),一个是有resultset,一个没有,后面关闭资源的时候方便调用,sql如下所示:

package utils;

import com.mysql.cj.jdbc.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class DBUtil {
    //定义数据源对象
    private static DataSource dataSource;
    //定义URL
    private static String URL = "jdbc:mysql://127.0.0.1:3308/first_database?characterEncoding=utf8" +
            "&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false";
    //定义用户名
    private static String USER = "root";
    //定义密码
    private static String PASSWORD = "123456";

    //在类加载的时候初始化dataSource对象

    static {
        MysqlDataSource mysqlDataSource = new MysqlDataSource();
        mysqlDataSource.setURL(URL);
        mysqlDataSource.setUser(USER);
        mysqlDataSource.setPassword(PASSWORD);

        dataSource = mysqlDataSource;
    }

    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }



    public static void close(ResultSet resultSet,Statement statement,Connection connection){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

    }


    public static void close(Statement statement,Connection connection){
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

    }


    //构造方法私有化,防止其他地方通过new来创建这个类的对象
    private DBUtil(){}
}

2,三层架构

这里创建4个包,utils包中放前面的DBUtil类,方便后续连接数据库,如下所示:
在这里插入图片描述

这个Dao包,Entity包,以及Service包,就是我们常说的数据库中的三层架构

  • Entity层:实体层,例如这个项目中数据库中有user表,class表
    那就把它们写成user类和class类放到entity层中,user表和class表中的列就对应着类中创建的私有属性
  • Dao层:数据访问层,Dao层中只负责数据的访问,向数据库传入增删改查的SQL指令,Dao层不负责任何逻辑判断(例如不判断插入数据是否合理,逻辑的判断是在service层实现的)
  • Service层:业务逻辑层,负责处理业务逻辑,例如进行逻辑判断(例如校验班名是否重复,进行事务控制,操作多张表等)


  • 例如想要插入数据,就是这样一个流程:首先在main方法中读取用户输入,接着main方法调用Service层,在Service层中对数据进行校验,或者执行其他逻辑;Service层再调用Dao层,访问数据库中的数据;
    Dao层插入数据后,将是否插入成功返回给service层,service层再返回给main层

    使用三层架构好处如下:

    1. 代码耦合度低,修改代码和扩展时都比较方便,例如要把数据库从 MySQL 换成 Oracl,只需要修改 Dao 层的 SQL 语法即可
    2. 代码结构清晰,方便在测试时快速定位bug
    3. 可以提高代码的复用率,写一次「查询用户」的方法,所有业务场景(登录、修改信息、统计)都能直接调用,不用在每个功能里复制粘贴相同的代码,大幅减少冗余



    现在在Entity层中创建一个学生类,一个班级类
    在Dao层里面创建两个类,一个负责班级信息对数据库的访问,一个负责学生信息对数据库的访问
    在Service层中创建两个类,一个里面写有关学生信息的读取和修改的逻辑,一个里面写有关班级信息的读取和修改的逻辑
    在这里插入图片描述



    在实体层中写两个类,一个Classes类,代表班级(Class没办法做类名)
    一个Student类,代表学生,两个类中的属性就是表中的列名,再写下get和set方法代码如下:

    package Entity;
    
    public class Classes {
        private long id;
        private String name;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    
    package Entity;
    
    import java.util.Date;
    
    public class Student {
        private long id;
        private String name;
        private String sno;
        private Integer age;
        private byte gender;
        private Date enroll_date;
        private long class_id;
    
        public long getId() {
            return id;
        }
    
        public void setId(long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSno() {
            return sno;
        }
    
        public void setSno(String sno) {
            this.sno = sno;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public byte getGender() {
            return gender;
        }
    
        public void setGender(byte gender) {
            this.gender = gender;
        }
    
        public Date getEnroll_date() {
            return enroll_date;
        }
    
        public void setEnroll_date(Date enroll_date) {
            this.enroll_date = enroll_date;
        }
    
        public long getClass_id() {
            return class_id;
        }
    
        public void setClass_id(long class_id) {
            this.class_id = class_id;
        }
    }
    
    

    3,实现班级插入

    下面就用三层架构来实现班级插入

    首先在ClassDao类中写一个insert方法,这个insert方法接收的是一个Classes类的对象,返回一个int值,如果int等于1,表示插入成功了,返回0就是插入失败了,代码如下:

    package Dao;
    
    import Entity.Classes;
    import utils.DBUtil;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class ClassDao {
    
        public int insert(Classes record){
            Connection connection = null;
            PreparedStatement preparedStatement = null;
    
            try {
                connection = DBUtil.getConnection();
                String sql = "insert into class(name) values (?)";
                preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setString(1,record.getName());
                int i = preparedStatement.executeUpdate();
                return i;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }finally {
                DBUtil.close(preparedStatement,connection);
            }
        }
    }
    
    



    接着就是ClassService,创建一个私有化的ClassDao对象,方便在Service层来调用Dao层,写一个addClass方法,返回值是int

    这里就可以在Service层的addClass方法中加一些判断逻辑,例如判断输入的班级名之前是否已经重复了(这就需要在ClassDao再写一个findByName方法来访问数据库),或者输入必须按照一定的格式(这里博主就不加了)

    package Service;
    
    import Dao.ClassDao;
    import Entity.Classes;
    
    public class ClassService {
        private ClassDao classDao = new ClassDao();
    
        public int addClass(Classes classes){
            return classDao.insert(classes);
        }
    }
    



    接着在demo包里面写一个Test类来读取下用户输入的班级名,创建一个班级对象,再创建个ClassService对象,方法调用一下即可

    package demo;
    
    import Entity.Classes;
    import Service.ClassService;
    
    import java.util.Scanner;
    
    public class Test {
        public static void main(String[] args) {
            System.out.println("请输入要添加的班级名:");
            Scanner scanner = new Scanner(System.in);
            String name = scanner.next();
    
            Classes classes = new Classes();
            classes.setName(name);
            ClassService service = new ClassService();
            int row = service.addClass(classes);
            if(row == 1){
                System.out.println("插入成功");
            }else{
                System.out.println("插入失败");
            }
    
        }
    }
    
    

    在这里插入图片描述

    4,lombok的使用

    在JDBC第一篇博客中,提到从Maven仓库中下载JDBC驱动,Maven仓库中有很多实用的工具库,框架,例如lombok,就是一个非常实用的工具库,接下来介绍下它的使用

    打开检索平台(链接放在下面了),搜索lombok,打开第一个,再选择1.18版本,复制下面文本框中的内容
    Maven依赖检索平台
    在这里插入图片描述
    在这里插入图片描述



    把内容粘贴到项目的pom.xml中的dependencies标签中(这部分不明白的铁汁可以看下博主JDBC的第一篇博客,里面详细介绍了驱动的引入),点击一下右上角的这个按钮,Maven 就会从中央仓库下载这个工作库,并将其引入到项目中
    在这里插入图片描述



    使用IDEA的铁汁要在设置的Plugins中下载一个Lombok插件,这样才能正常使用
    在这里插入图片描述



    这个Lombok的作用就是通过通过注解自动生成getter/setter、构造器、toString 等代码,进而减少代码的冗余

    就比如在Classes类中,之前要我们手动生成get,set,构造方法,看起来代码很长,引入了lombok工具库后,只需要加个@Data,就相当于把这些方法都添加进去了,非常方便(这些代码会在编译时自动生成)

    可以点击Structure看一下,虽然这里我们什么都没写,但是Classes类中的这些方法已经自动生成了,是不是非常方便🐵
    在这里插入图片描述

    5,根据班级名查找同学信息

    如果想要达到下图这样根据班名查找同学信息的效果,我们分析下用三层架构怎么搞
    在这里插入图片描述

    其实查找跟插入的套路大概是一样的,插入就是在main方法中创建个对象,调用Sercice层,Sercice层再把对象传给Dao层,Dao层提取对象中的信息后插入到数据库中(可参考班级的插入的步骤)

    查找只是反了一下,main方法调用Sercice层的查找方法,传入参数,Sercice层再调用Dao层,把参数传给Dao层,dao层查询到数据后以对象的形式传给Sercice层,Sercice层再把对象传给main方法,这样就查询成功了




    这里就需要解决两个问题

    1. 查询时结果中要求有班级名称,但是如果返回学生对象,学生对象中只有班级编号,是没有班级名称的
    2. 我们查询到的是多行数据,只返回单个对象肯定是不行的

    对于第一个问题,就是在Student类中创建个复合变量,把Classes类放进去当作一个属性,这样后续main方法得到Student对象后,就可以提取出班级名

    对于第二个问题,因为查询到的是多行数据,可以把每一行创建成一个student对象,搞一个链表,把这些student对象储存到链表中,最后再把链表返回给main方法



    Student类代码如下所示(这里我们测试下lombok帮我们生成的toString方法,就不再重写toString方法了)

    package Entity;
    
    import lombok.Data;
    
    import java.util.Date;
    @Data
    
    public class Student {
        private long id;
        private String name;
        private String sno;
        private Integer age;
        private byte gender;
        private Date enroll_date;
        private long class_id;
        private Classes classes;
    }
    
    



    在StudentDao中,写一个selectByClassName,核心就是用while循环读取resultSet,然后创建student对象,把读取到的数据set给对象,再创建链表,最后返回链表

    package Dao;
    
    import Entity.Classes;
    import Entity.Student;
    import utils.DBUtil;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    public class StudentDao {
    
        public List<Student> selectByClassName(String className){
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
    
            try {
                connection = DBUtil.getConnection();
                String sql = "select s.id,s.name,s.sno,s.age,s.gender,s.enroll_date,s.class_id,c.name class_name"
                        +" from student s,class c where s.class_id = c.id and c.name = ?";
                preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setString(1,className);
                resultSet = preparedStatement.executeQuery();
                List<Student> list = null;
                while (resultSet.next()){
                    long id = resultSet.getLong("id");
                    String name = resultSet.getString("name");
                    String sno = resultSet.getString("sno");
                    int age = resultSet.getInt("age");
                    byte gender = resultSet.getByte("gender");
                    Date enroll_date = resultSet.getDate("enroll_date");
                    long class_id = resultSet.getLong("class_id");
                    String class_name = resultSet.getString("class_name");
                    Student student = new Student();
                    student.setId(id);
                    student.setName(name);
                    student.setSno(sno);
                    student.setAge(age);
                    student.setGender(gender);
                    student.setEnroll_date(enroll_date);
                    student.setClass_id(class_id);
    
                    //创建班级对象
                    Classes classes = new Classes();
                    classes.setName(className);
                    classes.setId(class_id);
    
                    //学生和班级建立联系
                    student.setClasses(classes);
                    if(list == null){
                        list = new ArrayList<>();
                    }
                    list.add(student);
                }
                return list;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    



    接着再写下Studentervice类,创建StudentDao对象,接着进行输入校验(根据实际要求来写),如果有输入异常的情况,就抛出异常
    输入校验通过后,再调用StudentDao对象的selectByClassName,把返回的list再返回给main方法

    package Service;
    
    import Dao.StudentDao;
    import Entity.Student;
    import java.util.List;
    
    public class StudentService {
        private StudentDao studentDao = new StudentDao();
    
        public List<Student> getStudentsByClassName(String className) {
            // 1. 空值验证
            if (className == null) {
                throw new IllegalArgumentException("班级名不能为null");
            }
            // 2. 空字符串验证
            String trimmedClassName = className.trim();
            if (trimmedClassName.isEmpty()) {
                throw new IllegalArgumentException("班级名不能为空字符串");
            }
            // 3. 长度验证
            if (trimmedClassName.length() > 50) {
                throw new IllegalArgumentException("班级名长度不能超过50个字符");
            }
           
            return studentDao.selectByClassName(trimmedClassName);
        }
    }
    
    



    接着在main方法中,就通过调用service层,得到返回的list结果,再遍历list即可,代码如下所示:

    package demo;
    
    
    import Service.StudentService;
    import Entity.Student;
    
    import java.util.List;
    import java.util.Scanner;
    
    public class Test2 {
        public static void main(String[] args) {
            System.out.println("请输入班级名->");
            Scanner scanner = new Scanner(System.in);
            String className = scanner.next();
            StudentService studentService = new StudentService();
            List<Student> list = studentService.getStudentsByClassName(className);
            if(list == null || list.isEmpty()){
                System.out.println("没有查询到结果");
                return;
            }
            for(Student stu : list){
                System.out.println(stu.toString());
            }
        }
    }
    
    

    在这里插入图片描述

    结语💕💕

  • 这篇博客是数据库基础专栏的最后一篇博客 使用数据库三层架构是写程序时的基础要求,这部分在写的时候因为有两次调用,也是很容易出bug的
  • 刚学习的铁汁,如果有报错的话,不要怕复杂,可以结合报错信息来排查bug,从main方法调用处,看到Service层,再看到Dao层,分析出bug


  • 数据库专栏的内容到这里就更新完了~完结撒花~🥳🎉🎉

    在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值