JDBC(2)

119 阅读25分钟

反射reflect

2021-03-29_145630.png

深入的细分类的加载,当程序启动时候使用到某个类,如果该类为被加载到内存中,则JVM会通过是三个步骤进行类的初始化[加载、连接、初始化

2021-03-29_151022.png

什么是Class对象?

如果Java中我们需要创建某个类的对象

Student stu = new Student(); --> 通过Student类创建stu对象,stu对象是Student类的实例,在创建过程中隐藏内容的一些细节,Student类中所存在[属性,方法,修饰符,构造方法]等等

Java是一门面向对象的语言,面向对象核心思想"世间万物皆对象",在看待承载Student类所提供信息时,提供一个可以描述类来描述student类,将类Student看做是类对象,Java专门提供了一个可以描述类的类,这个类就是Class类,Class类是所有类的类对象集合即(类被加载到JVM时会被创建class对象就是Class这个类实例),通过Class类创建出来对象来描述当前类

Class类就是类的.class文件加载到JVM中所创建class对象的描述,Class就是所有类的描述类(描述类的类),既然Class类可以描述类的类,并且可以获得你当前类的.class文件对象即class对象,我们就可以通过这个class对象创建出当前类的类的对象,这个技术就叫做 "反射"

例如:

正常创建对象 --> Student stu = new Student();

但是在JVM中是先的到Student类的.class文件,创建出Student类的class对象

class对象是不是就代表着Student类,就可以通过"class对象"反向的创建出Student类的对象,这个过程就叫做"反射"

图片2.png

class对象是类的字节码文件对象即(.class文件),既然class是对象,需要有一个可以描述class对象类,这个类就是Class类

如何获取Class类的对象

`package com.qfedu.reflect;

public class ReflectInstance { public static void main(String[] args) throws Exception { //获取Class类的对象-->获取Student类的class对象 //1.正常创建Student类的对象 Student stu = new Student(); stu.name = "张三"; System.out.println(stu.name);

    //通过反射获取到Student这类的class对象
    //1.通过Class类中静态方法 forName来获取Student类的对象
    //参数: 类的全限定名称 ---> 包名+类名
    Class aClass = Class.forName("com.qfedu.reflect.Student");
    //此时aClass对象就代表着Student类,利用aClass反向的创建出Student类的对象
    Object o = aClass.newInstance(); //创建对象
    System.out.println(o instanceof Student);

    //2.Java中每个类都有一个静态方法,Class类是描述类的类,那么所有类都是Class类的实例(即class对象)
    //每个类都可以直接调用class静态方法获取哦Class类的对象
    Class<Student> studentClass = Student.class;
    Student student = studentClass.newInstance();

    //3.现场创建Student类的对象
    Student student1 = new Student();
    //3.1通过student1对象调用父类Object所提供getClass方法获取Class类的对象
    Class aClass1 = student1.getClass();
    Object o1 = aClass1.newInstance();

    //强烈推荐第一种,它不受.java源文件的影响,即.java源文件不存在也可以创建(只是报错而已)

   }
}

JavaSE中有就打内置Class对象[他们是JVM预先加载好的Class实例]

byte、short、int、long、float、double、char、boolean、void

其中八种基本数据类型对应包装类中提供一个静态属性Type,这个Type就是基本数据类型对应的class对象

Integer.Type 等价于 int.class

数组支持获取class对象

数据类型[].class 或者 数组对象.getClass()

反射操作实例

反射其实可以帮助做很多操作,通用性方法的封装,通过反射获取到类中所有属性和反方,无视权限修饰符,利用反射提供设计模式(静态和动态代理),在不破坏原有类的结构的前提下,可以对类增加新的方法,反射可以帮组我们加载资源文件

演示反射如何操作类

package com.qfedu.reflect;
​
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
​
//通过反射获取student类对象,并同类对象创建Student类的对象,操作类中方法和属性
public class ReflectStudentInstance {
    public static void main(String[] args) throws Exception {
        //0.先获取到Student类的class对象
        Class aClass = Class.forName("com.qfedu.reflect.Student");
        //直接创建这个Student类对象的方式 --> newInstance() 相当于间接调用Student类中无参构造方法
​
        //1.通过反射获取类中构造方法
        /*
         通过class对象调用getConstructor方法可以获取到一个构造方法的对象(Constructor类的对象)
         在利用这个构造方法对象创建出我们所需要类的对象
         一类中是存在多个构造方法,如何确定或如何指定获取到那个构造方法对象
         getConstructor方法中的参数有关,这个方法中参数是Class类型可变参数
         getConstructor(Class<?>... parameterTypes)
         如果不传递参数数据,此时获取的就是类中无参构造方法
         如果传递参数数据,此时获取的就是与参数匹配的对应构造方法
         PS:因为参数类型是Class类型,所以传递参数时,需要传递构造方法数据类型的class对象即
         */
        Constructor constructor = aClass.getConstructor();
        /*
        不能直接通过Constructor对象创建类的对象,需要用过Constructor对象调用一个方法
         newInstance方法才可以创建类的对象
         newInstance(Object ... initargs) 使用了可变参数
         newInstance方法的参数传递和getConstructor这个方法有关,如果getConstructor方法传递了参数
         那么在调用newInstance的时候就必须传递参数
         newInstance这个方法如果不传递方法参数,使用就是无参构造方法
         newInstance这个方法传递方法参数,使用就是有参方法,参数就是具体对构造方法参数的赋值
         */
        Object o = constructor.newInstance();
        System.out.println("o这个对象是Student类型吗?"+(o instanceof Student));
​
        //有参构造方法获取
        Constructor constructor1 = aClass.getConstructor(String.class, int.class);
        Object o1 = constructor1.newInstance("张三", 18);
        Student stu = (Student)o1;
        System.out.println("姓名:"+stu.name);
        //age' has private access in 'com.qfedu.reflect.Student' 私有属性在外界是无法获取的
        //System.out.println("年龄:"+stu.age);
​
        //反射在获取类中属性或方法时,是无视权限修饰符的
        //利用反射获取私有构造方法创建对象
        //       -->只要通过class对象调用方法时,如果发现方法中使用Declared单词的方法
        //       --> 暴力反射方法 无视权限修饰符获取对应的方法或属性
        Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class);
        //private权限是一个比较特殊的权限,因为权限等级是最低,为了方式使用反射恶意暴力反射
        //需要在访问private权限修饰符的方法或属性时,需要开启权限操作
        declaredConstructor.setAccessible(true);//开始权限访问
        Object o2 = declaredConstructor.newInstance("李四");
        System.out.println("o2是Student类的对象吗?"+(o2 instanceof Student));
        /*
        如果你要获取构造方法
        如果构造方法时候用public修饰 --> getConstructor
        如果构造方法是非public修饰 --> getDeclaredConstructor
        如果碰到使用这个方法还无法操作,一定要开启权限操作  通过方法获取的对象调用.setAccessible(true);
         */
​
        //2.获取类中属性
        /*
        通过Class对象获取类中属性
        如果类中属性是public修饰  ---> getFiled
        如果类中属性是非public修饰 ---> getDeclaredFiled
        如果碰到使用这个方法还无法操作,一定要开启权限操作  通过方法获取的对象调用.setAccessible(true);
         */
        //获取Student类中name属性操作权
        //参数是属性名字.返回的是Filed对象
        Field name = aClass.getField("name");
        //取值操作,通过Filed对象调用get方法就可以获取属性值
        //get方法方法中需要一个参数,这个参数是Student类的对象(通过newInstance创建对象)
        System.out.println(name.get(stu));
        //赋值操作,通过Filed对象调用set方法就可以对属性赋值
        /*
        第一个参数:是Student类的对象(通过newInstance创建对象)
        第二个参数:是对属性赋值
         */
        name.set(stu,"王五");
        System.out.println(name.get(stu));
        /*
         如果获取非public权限属性,只需要改变获取方法其他不需要改变
         如果你获取的是static修饰属性,将面临set方法的第一个参数和get方法的参数传递问题
         如果是static属性,只要传递为null即可
         在反射中如果传递类的对象的位置传递null就代表static修饰符静态属性或方法
           name.set(null,"王五");
          System.out.println(name.get(null));  ---> 代表是静态属性
         */
​
        // 类中方法(重点记忆)
        /*
        通过Class对象获取类中属性
        如果类中方法是public修饰  ---> getMethod
        如果类中方法是非public修饰 ---> getDeclaredMethod
        如果碰到使用这个方法还无法操作,一定要开启权限操作  通过方法获取的对象调用.setAccessible(true);
        获取方法对象getMethod方法参数问题
        第一个参数是: 获取方法的名字
        第二个参数是: Class类型可变参数
        如果不对可变参数赋值,调用就是没有参数的方法
        如果对可变参数赋值,赋值方式就是方法参数类型的class对象,调用就是有参数的方法
         */
        Method show = aClass.getMethod("show");
        Method show1 = aClass.getMethod("show", int.class, String.class);
        //此时获取的是方法对象,需要执行方法需要调用方法中提供invoke
        /*
        invoke方法和getMethod方法是对应的,如果getMethod中没设置参数,invoke就没有参数
        如果getMethod方法设置了参数就需要对调用时对进行参数赋值
        invoke方法第一个参数,如果是成员方法就传递一个是Student类的对象(通过newInstance创建对象)
        如果是静态方法第一个参数就是null
         */
        show.invoke(stu);
        show1.invoke(stu,1,"王五");
​
​
    }
}
​

利用反射可以直接加载Properties文件对象

在IDEA中是可以创建出不同类型的文件夹,已知创建出来一种文件夹是"普通文件夹",除了这个文件夹之外,我们还可以创建,"资源文件夹Source Folder", "这个文件夹的特点是可以将文件夹中文件加载到classPath路径"

classPath路径就是工程中.java文件生成.class文件的路径,利用找那个原则就可以获取到Properties资源文件

package com.qfedu.reflect;
​
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
​
//使用反射读取资源文件
public class ReflectReadProperties {
    public static void main(String[] args) throws IOException {
        //1.创建资源文件对象
        Properties properties = new Properties();
        //2.提供一个类的加载器,类加载器可以加载资源获获取流
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        //任何类都有ClassLoader,上面这种写法方式习惯性,如果是本类获取
       // ClassLoader classLoader = ReflectReadProperties.class.getClassLoader();
        //3.ClassLoader加载文件获取流 ,参数直接传递文件名即可
        InputStream resourceAsStream = contextClassLoader.getResourceAsStream("MyProperties.properties");
        //4.加载文件内容到对象中
        properties.load(resourceAsStream);
        System.out.println(properties);
    }
}
​

封装DBUtil工具类

之前这已经使用DDL、DML和DQL语言进行JDBC编程操作,但是可以发现代码中出现大量重复性操作,其实就触发了软件开发中的一个原则[DRY] (不要写重重复性的代码 ) ,所以就需要将这些重复性代码进行抽取和封装,值提供一次实现,外界只需要调用实现就可以完成操作,此时就需要提供一个工具类,这个类封装着JDBC中常用操作,这个类就叫做"DBUtil"工具类

工具类核心思想-1607585082361.png

提供DBUtil工具类封装


import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Objects;
import java.util.Properties;

//对JDBC中常用操作的封装类
/*
工具类特点:这个类中所有属性和方法都是静态的,并且工具类不允许创建对象
 */
public class DBUtil {
    private DBUtil(){} //防止外界创建对象
    /*
     为了保证驱动加载的执行速度,提供一个静态代码块 随着DBUtil类使用加载驱动
     在获取Connection对象时候,需要配置JDBC的连接参数,如果这个参数写死在方法中是否合理?
     这个配置肯定不合理,如果连接数据库的操作发生改变,就修改源代码
     提供工具类目的就是通用性,如果每次都要源代码才能保证修改连接方式,还是麻烦
     即不在代码中提供,有可以可以在代码操作,此时就可以利用到文件
     将配置信息写入到一个文件中,使用时候只要读取文件,加载配置信息,如果修改连接操作
     无需修改代码,只要修改替换文件即可,需要一种可以保证读取便捷获取配置的文件 --> properties文件
     */
    //因为数据已经写入到资源文件中,所以加载资源文件到Properties中
    private static final Properties p = new Properties();
    static {
        //0.添加资源文件的加载操作
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            InputStream resourceAsStream = loader.getResourceAsStream("jdbc.properties");
            p.load(resourceAsStream);
        } catch (IOException e) {
            throw  new RuntimeException("加载classPath路径下properties文件出现问题:"+e.getMessage());
        }
        //1.加载注册驱动
        try {
            Class.forName(p.getProperty("driver"));
        } catch (ClassNotFoundException e) {
            throw  new RuntimeException("加载注册驱动失败:"+e.getMessage());
        }

    }

    /**
     * 提供Connection对象获取(获取连接对象)
     * @return connection连接对象
     */
    public static Connection getConnection(){
        try {
            //不出问题就是正常创建
            return DriverManager.getConnection(
                    p.getProperty("url"),p.getProperty("username"),p.getProperty("password")
            );
        } catch (SQLException e) {
            throw  new RuntimeException("数据库连接对象创建失败:"+e.getMessage());
        }

    }

    /**
     * JDBC连接资源的统一释放曹组
     * @param connection 连接对象
     * @param statement 静态语句对象
     * @param resultSet 结果集对象
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet){
        try {
            if(Objects.nonNull(connection)){
                connection.close();
            }
            if(Objects.nonNull(statement)){
                statement.close();
            }
            if(Objects.nonNull(resultSet)){
                resultSet.close();
            }
        } catch (SQLException e) {
            throw  new RuntimeException("数据库释放资源对象失败:"+e.getMessage());
        }
    }

}

JDBC资源文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb1
username=root
password=123456

为什么不分装SQL语句操作,因为不用操作中SQL语句是有变化的,现在使用静态语句,后续会使用预编译语句,在后续会使用BDUtils工具类,后续会在使用MyBits,语句是不断变化,操作也是不断变化,所有你不建议封装

实现登录和注册案例

  • 创建一张用户表 User

    • id ,主键、自动增长。
    • 用户名,字符串类型,唯一、非空
    • 密码,字符串类型,非空

控制台上输出 1.注册 和 2.登录

  • 注册和登录都需要通过控制台用户输入用户名和密码。 注册是需要验证用户名和密码是否合法 --》 要求 字母数字组成,并且第一个字符必须为字母 6~16位 如果合法在验证用户名是否存在在数据库中, 如果存在则提示注册失败,否则注册成功!

在弹出 1.注册 和 2.登录 登录不需要验证了直接进入到数据库查询验证操作, 如果该用户存在,提示登录成功,反之提示登录失败。


import com.qfedu.util.DBUtil;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

//JDBC创建一张user表
public class CreateTable {
    public static void main(String[] args) throws SQLException {
        //1.提供静态SQL语句
        String sql = "create table user(" +
                "    id int primary key  auto_increment," +
                "    username varchar(20) unique not null," +
                "    password varchar(20) not null" +
                ")";
        //2.获取Connection对象 --> 使用的就是封装好的DBUtil工具类
        Connection connection = DBUtil.getConnection();
        //3.创建Statement对象
        Statement statement = connection.createStatement();
        //4.通过Statement对象发送SQL语句到数据库中执行
        //建表语句属于一个特殊的DDL语句,所以返回值是0 也代表建表成功
        int i = statement.executeUpdate(sql);
        System.out.println("建表成功");
          DBUtil.closeAll(connection,statement,null);
    }
}

import com.qfedu.util.DBUtil;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

//登录注册操作
public class LoginAndRegister {
    public static void main(String[] args) throws SQLException {
        //1.提供控制台获取数据操作
        Scanner input = new Scanner(System.in);
        //2.获取Connection对象
        Connection connection = DBUtil.getConnection();
        //3.提供一个循环完成主体执行逻辑
        EXIT_WHILE:while(true){
            System.out.println("1.登录\t\t2.注册");
            int num = input.nextInt();
            switch (num){
                //登录逻辑
                case 1:
                    //3.1获取控制台输入的数据
                    System.out.println("请输入用户名:");
                    String username = input.next();
                    System.out.println("请输入密码:");
                    String password = input.next();
                    /*
                    这里最不可取的一种执行语句就是直接查询全表即User
                    因为我们已经获取到了用户名和密码,利用用户名和密码直接查询数据库
                    静态语句的一个问题,静态语句对建库和建表操作是比较友好,语法是定死的没有动态数据
                    但是静态语句对增删改查不是很友好,一般面临需要在SQL语句中和拼接上动态数据,非常麻烦
                     */
                    StringBuilder bs = new StringBuilder("select * from user where username =");
                    bs.append("'").append(username).append("'")
                            .append(" and password =").append("'").append(password).append("'");
                    //3.2创建Statement对象
                    Statement statement = connection.createStatement();
                    //3.3执行查询语句
                    ResultSet resultSet = statement.executeQuery(bs.toString());
                    /*
                    resultSet是DQL语言查询数据库返回结果集的映射,resultSet对象就代表着查询出来结果
                    判断用户输入用户名和密码是否正确,查询语句的构造成已经将用户名和密码传递到数据库中进行
                    查询,resultSet结果应该就是两种
                    第一种查询到,resultSet中有结果集数据
                    第二种没有查询到,resultSet中结果集数据null(空的)
                     */
                    if(resultSet.next()){
                        System.out.println("登录成功");
                        DBUtil.closeAll(null,statement,resultSet);
                    }else{
                        System.out.println("登录失败[输入的用户名和密码错误!]");
                        DBUtil.closeAll(null,statement,resultSet);
                    }
                    break EXIT_WHILE;
                //注册逻辑
                case 2:
                    //注册失败让你继续注册,注册成功为止
                    while(true){
                        //1.获取控制台上的用户名和密码
                        System.out.println("请输入用户名:");
                        String username1 = input.next();
                        System.out.println("请输入密码:");
                        String password1 = input.next();
                        //验证用户名和密码输入是否和法 ---> 第一个字符必须是字母长度6~16位
                        if(username1.matches("[a-zA-z]\\w{5,15}") && password1.matches("[a-zA-z]\\w{5,15}")){
                            //验证用户名是否被注册
                            StringBuilder bs1 = new StringBuilder("select * from user where username = ");
                            bs1.append("'").append(username1).append("'");
                            //创建Statement对象发送SQL语句执行得到返回结果集
                            Statement statement1 = connection.createStatement();
                            ResultSet resultSet1 = statement1.executeQuery(bs1.toString());
                            if(resultSet1.next()){
                                System.out.println("您输入用户名已经被注册,请从新输入");
                                DBUtil.closeAll(null,statement1,resultSet1);
                            }else{
                                //插入数据
                                String sql = "insert into user(username,password) " +
                                        "values("+"'"+username1+"',"+"'"+password1+"')";
                                Statement statement2 = connection.createStatement();
                                int i = statement2.executeUpdate(sql);
                                if(i > 0){
                                    System.out.println("恭喜注册成功!");
                                    break;
                                }
                                DBUtil.closeAll(null,statement2,null);

                            }

                        }else{
                            System.out.println("您输入的用户名和密码不和法请从新输入");
                        }
                    }
                    break;
            }
        }
        DBUtil.closeAll(connection,null,null);
    }
}

预编译语句

之前学习的静态SQL语句在没有输入数据(动态数据)的时候,使用起来还是比较方便,但是如果一旦需要外界数据传入(动态数据),就需要对静态SQL语句进行拼接操作操作,如果参数特别多,拼接语句就会十分麻烦而且还容易拼接错误

静态SQL语句是无法防止"SQL注入"的,只能使用预编译SQL语句来防止SQL注入

Java就提供了一种方便操作,并且可以防止"SQL注入",而且还有SQL语句优化操作的一个语句,这个语句就叫做"预编译语句"

JDBC中提供了预编译语句是Statement接口的子接口他们是继承关系,并且预编译语句的接口名PreparedStatement

因为PreparedStatement是Statement的子接口,所以继承父类接口中操作DDL,DML和DQL的执行方法,依旧是使用executeUpdate()和executeQuery()这个两个方法执行DDL,DML和DQL语言,但是略有不同

预编译语句和静态语句最大的区别在于,预编译语句中数据是以 [?] 占位符进行占位,在执行语句阶段在进行赋值操作者,而不是想静态SQL语句一样直接将数据写好

静态SQL语句

insert into user(username,password) values('zhansgan','123456');

预编译SQL语句

insert into user(username,password) values(?,?);

后再后续SQL语句执行时对 [?] 占位符进行赋值操作

PreparedStatement提供一个方法专门针对[?]占位符进行赋值操作

setXXX(int parameterIndex,XXX value)

XXX代表你要赋值的列的数据类型

例如:上面的预编译语句insert into user(username,password) values(?,?);

username在SQL中是varchar类型 所以在Java中对应String类型

调用setString(int parameterIndex,"张三")

PreparedStatement这个语句是使用 [?] 占位符进行占位,如何指定对那个 [?] 占位符进行赋值

在一个预编译SQL语句中 ? 占位符是按照从左向右的方向进行标记记位,最左边第一个 [?] 占位符的位置是1,依次向右类推,逐渐递增+1

完整的对预编译SQL语句中[?]占位符的赋值是:

例如:上面的预编译语句insert into user(username,password) values(?,?);

username在SQL中是varchar类型 所以在Java中对应String类型

调用setString(1",张三") --->相当于是对 username进行赋值(左边第一个?)

调用setString(2,"123456") --->相当于是对 password进行赋值(第二个?)

2021-03-30_104413.png

简单模拟一个SQL注入操作


import com.qfedu.util.DBUtil;

import java.sql.*;

public class SQLToIN {
    public static void main(String[] args) throws SQLException {
        //使用静态SQL语句,完成对user表登录操作
  /*      *//*
        登录成功原因就是在于 静态SQL语句的问题,将整个SQL语句看做是一个整体
        并且进行where条件判断
        username = '' 整个结果必然是false
        使用 AND 查询 false结果登录失败 查询不到
        OR 1=1  此时这个判断   false OR 1=1  --> 1=1 true 结果是true
        password = 'a123456' 结果为true  true And true 登录成功
         *//*
        String staticSQL = "select * from user where username = '' OR 1=1 AND password='a123456'";
        //进行获取连接操作并且执行SQL语句
        Connection connection = DBUtil.getConnection();
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(staticSQL);
        if(resultSet.next()){
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }
        DBUtil.closeAll(connection,statement,resultSet);*/

        //使用预编译SQL语句完成user表的登录操作
        String preSQL =  "select * from user where username = ? AND password = ?";
        //1.先获取到Connection对象
        Connection connection = DBUtil.getConnection();
        //2.通过Connection对象创建预编译语句对象
        //  调用的预编译语句对象的创建时需要传入参数的,参数就是预编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(preSQL);
        //3.通过预编译语句对象提供赋值语句对[?]占位符进行赋值
        preparedStatement.setString(1,"'' OR 1=1");
        preparedStatement.setString(2,"a123456");
        //通过预编译语句对象调用executeQuery()和 executeUpdate()都不需要参数
        ResultSet resultSet = preparedStatement.executeQuery();
        if(resultSet.next()){
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }
        DBUtil.closeAll(connection,preparedStatement,resultSet);
        /*
        预编译语句会将赋值操作参数看做是一个整体 即认为  '' OR 1=1 是一个整体字符串
        即认为是用户名  生成SQl语句
        select * from user where username = ''' OR 1=1' and password = 'a123456';
         */
    }
}

综上所述:在以后开发汇中如果要使用JDBC进行数据开连接开发,建议编写预编译SQL语句,不见可以方便我们的动态赋值,也可以防止SQL注入,并且具有SQL语句优化的特点,预编译SQL语句可以使用在增删改查上,静态SQL语句可使用在建库,建表,删库和删表上(预编译SQL语句也可以)

DAO数据访问对象(Date Access Object)

在接下来的开发中,我们要创建很多很多不同功能类,所以我们为了方便管理会将这些类进行分层(分包)存储,包的分配会根据类作用来进行定义,随着人们不断的对类进行分包存储包名定义,从而总结出一个编程模式,这个条模式叫做 [三层架构] (三层架构就是MVC设计模式的前身)

什么是三层架构?

人为的对代码分层管理,这样的管理利于开发人员对每个层级中代码进行维护与开发,代码与代码之间存在一个人为的分层,模拟了现实生活中的一些逻辑,一层只做一件事,每层分工合作完成整个代码

2020-12-11_163546.png

2020-12-11_163600.png

image-20210331112017368.png

编写数据访问层 --> DAO

在编写操作数据库的代码时就有命名要求了,包名必须是以dao最为包名,代表这个包是数据访问层级即dao层

例如: com.qfedu.Emproject.dao包名 -->证明和这个dao包是Empproject项目的dao层即数据访问层

在包中创建类名也是有要求的,对数据库一个表的操作对应一套 XXXXDao 和 XXXXDaoImpl

PS:开发中XXXX是要操作表的名字

XXXXDao是接口定义操作表中方法 XXXXDaoImpl是定义接口的实现类

为什么要提供一个接口,而不是在类中直接定义方法的实现?

1.对于后续学习开发框架契合

2.在实际开发中是多人协同开发,在写其他service层的开发人员,不会等你将dao层都写完在开发,需要service层中使用dao层操作就需要有一个"占位方式",service层只要调用你接口中定义方法可以,无需等待你完全开发完毕在调用.

3.提供接口好处在于方便提供不同实现方式,针对某些方法进行重新实现(一个接口多种实现的效果了)

DAO层核心就是提供对数据库CRUD操作,所以在定义接口方法时候无非就是定义对数据库执行操作方法,但是也要结合service层需要的操作在进行定义,方能达到通用性

实体类

Dao层是操作一张表达过程,操作表时,就需要有数据传递和存储,传递主要针对的就是DML操作,存储主要针对的就是DQL操作,提供这个实体类就是为了方便对数据进行存储操作使用,通过DQL操作将数据查询出来存储在Resultset结果集对象中是零散,需要将这些零散数据进行统一从存储,并可以加以操作,就需要提供一个存储方式,这个方式就是"实体类"

实体类的创建也是有规则并且也有自己的包entity,这个包下存储都是实体类

实体类创建的规则就是和表之间形成一个完整的映射关系(ORM)

类名 ---> 表名 类属性 ---> 表中列 类属性数据类型 ---> 表中列的数据类型

表中每一行数据 ----> 类的每一个对象

既然为了封装这些零散数据为了方便使用和存储,所以在设计实体类时候就满足一些需求,这个类也被称之为"Java Bean"

如果某个类被称为Java Bean即实体类需要满足以下需求:

1.类必须使用public修饰

2.类必须保证有public修饰的有参和无参构造方法

3.包含处理属性的全部手段[属性私有化,提供getter和setter]

这个样类就是一个实体类即Java Bean,这类一般不会提供一些方法(成员或静态),这个类也不会作为实现接口类,这个类要求,类名尽量与操作的表名一致,属性名尽量要与列名一致(最好是完全一致,方便后续操作)

编写一个程序,完成DAO层定义与操作

创建一个Person表

    id int primary key  auto_increment, -- id主键列
    name varchar(20) not null, -- 人名字
    age int not null, -- 年龄
    birthday date, -- 生日
    email varchar(100), -- 邮箱
    address varchar(255) -- 地址
);

对这张表完成DAO层操作

1.创建person表是实体类,提供表的数据存储操作


import java.util.Date;

//Person表的实体类
public class Person {
     //表中列就是类中属性
     private int id;
     private String name;
     private int age;
     private Date birthday;
     private String email;
     private String address;
    public Person() {
        
    }
    public Person(int id, String name, int age, Date birthday, String email, String address) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.birthday = birthday;
        this.email = email;
        this.address = address;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
    //如果向观察实体类中存储数据是否正确,建议从写toString

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                ", email='" + email + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

2.编写dao层操作

2.1定义Person表操作的接口


import com.qfedu.entity.Person;

import java.sql.SQLException;
import java.util.List;

/*
这个接口定义的就是操作Person表的方法
接口名字命名 ---> I 代表接口   Person 代表是操作表的名字
            ---> Dao代表这个接口是dao层的
 */
public interface IPersonDao {
    /**
     * 向Person表中插入数据
     * @param person 实体类的对象封装这要添加到表中数据
     * @return 大于0 证明插入成功,否则插入失败
     * @throws SQLException  处理SQL的异常
     */
    int insert(Person person)throws SQLException;

    /**
     * 向Person表中更新数据
     * @param person 实体类的对象封装这要添加到表中数据
     * @return 大于0 证明插入成功,否则插入失败
     * @throws SQLException  处理SQL的异常
     */
    int update(Person person)throws SQLException;

    /**
     * 通过ID删除表中单条数据
     * @param id 根据表中id列进行删除
     * @return 大于0 证明插入成功,否则插入失败
     * @throws SQLException  处理SQL的异常
     */
    int delete(int id)throws SQLException;

    /**
     * 通过ID查询表中单条数据
     * @param id 表中的主键列
     * @return 封装着结果集的数据Person对象
     * @throws SQLException
     */
    Person select(int id)throws SQLException;

    /**
     * 查询整个Person表中的数据
     * @return List集合存储的是这个Person表中所有数据即Person实体类对象
     * @throws SQLException
     */
    List<Person> selectAll()throws SQLException;

}

2.2实现Dao接口


import com.qfedu.dao.IPersonDao;
import com.qfedu.entity.Person;
import com.qfedu.util.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 IPersonDAOImpl  implements IPersonDao {
    @Override
    public int insert(Person person) throws SQLException {
        //1.提供预编译SQL语句
        String sql = "insert into person(name,age,birthday,email,address) values(?,?,?,?,?)";
        //2.创建Connection对象
        Connection connection = DBUtil.getConnection();
        //3.构建预编译语句对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //4.通过预编译语句对象中赋值操作对占位符进行赋值操作
        preparedStatement.setString(1,person.getName());
        preparedStatement.setInt(2,person.getAge());
        //如果将java.util.Date对象存储到数据库中,需要转换为java.sql.Date才可以存储
        preparedStatement.setDate(3,null);
        preparedStatement.setString(4,person.getEmail());
        preparedStatement.setString(5,person.getAddress());
        int i = preparedStatement.executeUpdate();
        DBUtil.closeAll(connection,preparedStatement,null);
        return i;
    }

    @Override
    public int update(Person person) throws SQLException {
        //1.提供预编译SQL语句
        String sql = "update person set name = ?,age = ?,birthday = ?," +
                "email = ?,address = ? where id = ?";
        //2.获取连接对象
        Connection connection = DBUtil.getConnection();
        //3.获取预编译语句对象
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //4.通过预编译语句对象对占位符进行赋值操作
        preparedStatement.setString(1,person.getName());
        preparedStatement.setInt(2,person.getAge());
        preparedStatement.setDate(3,null);
        preparedStatement.setString(4,person.getEmail());
        preparedStatement.setString(5,person.getAddress());
        preparedStatement.setInt(6,person.getId());
        int i = preparedStatement.executeUpdate();
        DBUtil.closeAll(connection,preparedStatement,null);
        return i;
    }

    @Override
    public int delete(int id) throws SQLException {
        String sql = "delete from person where id = ?";
        Connection connection = DBUtil.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,id);
        int i = preparedStatement.executeUpdate();
        DBUtil.closeAll(connection,preparedStatement,null);
        return i;
    }

    @Override
    public Person select(int id) throws SQLException {
        String sql = "select * from person where id = ?";
        Connection connection = DBUtil.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,id);
        ResultSet resultSet = preparedStatement.executeQuery();
        Person person = null;
        while(resultSet.next()){
            int id1 = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            Date birthday = resultSet.getDate("birthday");
            String email = resultSet.getString("email");
            String address = resultSet.getString("address");
            person = new Person(id1,name,age,birthday,email,address);

        }
        DBUtil.closeAll(connection,preparedStatement,resultSet);
        return person;
    }

    @Override
    public List<Person> selectAll() throws SQLException {
        String sql = "select * from person ";
        Connection connection = DBUtil.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ResultSet resultSet = preparedStatement.executeQuery();
        List<Person> list = new ArrayList<>();
        while(resultSet.next()){
            int id1 = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            Date birthday = resultSet.getDate("birthday");
            String email = resultSet.getString("email");
            String address = resultSet.getString("address");
           Person person = new Person(id1,name,age,birthday,email,address);
           list.add(person);

        }
        DBUtil.closeAll(connection,preparedStatement,resultSet);
        return list;
    }
}