Socket

Socket

TCPServer

  • TCP通信的服务器端:接收客户端的请求,读取客户端发送的数据,给客户端回写数据

  • 表示服务器的类:

    • java.net.ServerSocket:此类实现服务器套接字。
  • 构造方法:
    ServerSocket(int port);创建绑定到特定端口的服务器套接字。

  • 服务器端必须明确一件事,必须得知道哪个客户端请求的服务器

  • 所以可以使用accept方法获取到请求的客户端Socket

  • 成员方法:

    • Socket accept();侦听并接受到此套接字的连接。
  • 服务器实现步骤:

    1. 创建一个服务器ServerSocket对象,和系统要指定的端口号
    2. 使用ServerSocket对象中的方法accept获取到请求的客户端对象Socket
    3. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
    4. 使用网络字节输入流InputStream对象中的方法read读取客户端发送的数据
    5. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
    6. 使用网络字节输出流OutputStream对象中的方法write给客户端发送数据
    7. 释放资源

TCPClient

  • TCP通信的客户端:向服务器发送连接请求,给服务器发送数据,读取服务器写回的数据

  • 表示客户端的类:

    • java.net.Socket:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
    • 套接字:包含了IP地址和端口号的网络单位
  • 构造方法:

    • Socket(InetAddress address, int port);创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
    • 参数:
      • InetAddress address:服务器主机的名称/服务器的IP地址
      • int port:服务器的端口号
  • 成员方法:

    • OutputStream getOutputStream();返回此套接字的输出流。
    • InputStream getInputStream();返回此套接字的输入流。
    • void close();关闭此套接字。
  • 实现步骤:

    1. 创建一个客户端对象Socket,构造方法绑定服务器的IP地址和端口号
    2. 使用Socket对象中的方法getOutputStream()获取网络字节输出流OutputStream对象
    3. 使用网络字节输出流OutputStream对象中的方法write给服务器发送数据
    4. 使用Socket对象中的方法getInputStream()获取网络字节输入流InputStream对象
    5. 使用网络字节输入流InputStream对象中的方法read读取服务器回写的数据
    6. 释放资源
  • 注意:

    1. 客户端和服务器进行交互,必须使用Socket中提供的网络流,不能使用自己创建的流对象
    2. 当我们创建客户端对象Socket的时候,就会去请求服务器和服务器经过3次握手建立连接通路
      • 这时如果服务器没有启动,那么就会抛出异常
      • 如果服务器已经启动那么可以进行交互

客户端服务器阻塞解决

tqILo8.jpg


模拟BS(浏览器与服务器)

tqoPe0.jpg

Stream流

Stream流

Stream流中的一些方法

1. 获取Stream流

  • java.util.stream.Stream< T >是Java 8新加入的最常用的流接口。(这并不是一个函数式接口)
  • 获取一个流非常简单,有以下几种常用方式:
    • 所有的Collection集合都可以通过stream默认方法获取流:
      • default Stream<E> stream​();
    • Stream接口的静态方法of可以获取数组对应的流:
      • static <T> Stream<T> of​(T... values);
      • 参数是一个可变参数,可以传递一个数组

2. Stream流中计数

  • Stream流中的常用方法count:用于统计Stream流中元素的个数。
    • long count();
  • 是一个终结方法,返回值是long类型,所以不能调用Stream类型中其他方法。

3. Stream流中过滤

  • Stream中的常用方法filter:用于对Stream流中的数据进行过滤。
  • Stream<T> filter​(Predicate<? super T> predicate) ;
  • 参数是一个Predicate接口,是一个函数式接口,可以传递lambda表达式,对数据进行过滤。

4. Stream流中遍历

  • Stream中两种方法:

    • 延迟方法:方法的返回值仍然是Stream类型,可以继续调用Stream中的方法。
    • 终结方法:方法的返回值不再是Stream类型,不能再调用Stream中的方法。
  • Stream中的常用方法forEach

  • void forEach​(Consumer<? super T> action) :该方法接收一个Consumer接口函数,会将每一个流元素交给函数进行处理。

  • Consumer接口是一个消费型的函数式接口,可以传递lambda表达式消费数据。

  • 简单记:

    • 用来遍历流中的数据,是一个终结方法,b遍历之后就不能再继续调用Stream流中的其他方法。

5. Stream流中截取

  • Stream流中的常用方法limit:用于截取流中的元素。
  • limit方法可以对流进行截取,只取用前n个。
  • Stream<T> limit​(long maxSize);
    • 参数是一个long类型,如果集合当前长度大于截取长度则进行截取(一个截取完以后的新的流),否则不进行操作(一个元素和之前一模一样的新的流)。

6. Stream流中的映射转化

  • 如果需要将流中的元素映射到另一个流中,可以使用map方法。
  • <R> Stream<R> map​(Function<? super T,? extends R> mapper) ;
  • 该接口需要一个Function函数式接口参数,可以将当前l流中的T类型数据转换为另一种类型的R类型数据。

7. Stream流中跳过元素

  • Stream流中的常用方法skip:用于跳过元素。
  • 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流。
  • Stream<T> skip​(long n) ;
    • 如果流的当前长度大于n,则跳过前n个,否则将会得到一个长度为0的空流。

8. Stream流将流组合

  • Stream流中的常用方法concat:用于把流组合到一起。
  • 如果有两个流,希望合并为一个流,那么可以使用Stream接口的静态方法concat。
  • static <T> Stream<T> concat​(Stream<? extends T> a, Stream<? extends T> b)

File

File

Lambda表达式

new Thread(()->{方法体}).start();
    ()->{方法体}
        ()代表参数
        {}代表方法的代码
        ->代表把参数传给后面的代码
  • 可以使代买更简洁,冗余更少

File类

  • java.io.File类

    • 文件和目录路径名的抽象表示形式
  • java把电脑中的文件和文件夹(目录)封装为了一个File类,我们可以使用File类对文件和文件夹进行操作

  • 我们可以使用File类的方法

    创建一个文件/文件夹
    删除文件/文件夹
    获取一个文件/文件夹
    判断文件/文件夹是否存在
    对文件夹进行遍历
    获取文件的大小
  • File类是一个与系统无关的类,任何的操作系统都可以使用类中的方法

  • 重点:记住三个单词

    1. file:文件
    2. directory:文件夹/目录
    3. path:路径
  • 系统中的分隔符:
    static String pathSeparator;与系统有关的路径分隔符,为了方便,它被表示为一个字符串。
    static char pathSeparatorChar:与系统有关的路径分隔符。

static String separator;与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。
static char separatorChar;与系统有关的默认名称分隔符。


File类的构造方法

  1. File(String pathname);通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。

    • 参数:
      • String pathname 字符串的路径名称
      • 路径可以是真实存在,也可以不存在
      • 因为创建File对象,只是把字符串封装为File对象,不考虑路径的真假情况
  2. File(String parent, String child);根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。

    • 参数:
      • String parent 父路径
      • String child 子路径
    • 好处:
      • 父路径和子路径可以单独的书写,使用起来灵活,父路径和子路径都可以变化
  3. File(File parent, String child);根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。

    • 参数:
      • String parent 父路径
      • String child 子路径
    • 好处:
      • 父路径和子路径可以单独的书写,使用起来灵活,父路径和子路径都可以变化
      • 父路径是File类型,可以使用File的方法对路径进行一些操作,再使用路径创建对象

创建删除文件/文件夹相关的一些方法:

  1. public boolean createNewFile();当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
    • 文件不存在才会创建,文件存在则不创建
    • 创建文件的路径必须存在,否则会抛出异常
  2. public boolean delete();删除此抽象路径名表示的文件或目录。
  3. public boolean mkdir();创建此抽象路径名指定的目录。
    • 文件夹不存在才会创建,存在则不会创建。
    • 如果方法中的路径不存在,则返回false
  4. public boolean mkdirs();创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。
    • 文件夹不存在才会创建,存在则不会创建。
    • 如果方法中的路径不存在,则返回false

File类中与获取相关的一些方法

  1. public String getAbsolutePath();返回此抽象路径名的绝对路径名字符串。
    • 无论构造方法中是相对路径还是绝对路径,此方法返回的都是绝对路径
  2. public String getPath();将此抽象路径名转换为一个路径名字符串。
    • 构造方法中是相对路径返回的就是相对路径,绝对路径返回的就是绝对路径。
  3. public String getName();返回由此抽象路径名表示的文件或目录的名称。
    • 获取的就是构造方法中传递路径的结尾部分,要么是文件,要么就是文件夹
  4. public long length();返回由此抽象路径名表示的文件的长度。
    • 获取的是构造方法指向的文件的大小,以字节为单位
    • 注意:
      1. 文件夹没有大小概念,不能获取文件夹大小
      2. 如果构造方法中给出的路径不存在,那么length方法返回0

File类中与判断相关的一些方法:

  1. public boolean exists();测试此抽象路径名表示的文件或目录是否存在。
  2. public boolean isDirectory();测试此抽象路径名表示的文件是否是一个目录。
    • 注意:使用前提是路径必须是存在的,否则都返回false
  3. public boolean isFile();测试此抽象路径名表示的文件是否是一个标准文件。
      • 注意:使用前提是路径必须是存在的,否则都返回false

File类遍历目录(文件夹)

  1. public String[] list();返回一个String数组,表示该File类目录中的所有子文件或目录
  2. public File[] listFiles();返回一个File数组,表示该File目录中的所有子文件或目录
  • 注意:
    1. list方法和listFiles方法遍历的是构造方法中给出的目录
    2. 如果构造方法中给出的目录的路径不存在,会抛出空指针异常
    3. 如果构造方法中给出的路径不是一个目录,也会抛出空指针异常

文件过滤器

  • 在File类中有两个和listFiles重载的方法,方法的参数传递的就是过滤器:

    • File[] listFiles(FileFilter filter)

      • java.io.FileFilter接口:用于抽象路径名的过滤器
        • 作用:用来过滤文件
        • 抽象方法:用来过滤文件的方法:
          • boolean accept(File pathname);测试指定抽象路径名是否应该包含在某个路径名列表中。
          • 参数:
            • File pathname:使用listFiles遍历目录得到的每一个文件对象
      • File[] listFiles(FileFilter filter)
      • listFiles做了三件事:
        1. 遍历了目录
        2. 把遍历的File对象传递给FileFilter实现类对象的accept方法
        3. 如果accept返回值为true则保存到File数组中
    • File[] listFiles(FilenameFilter filter)

      • java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名
        • 作用:用于过滤文件名称
        • 抽象方法:用来过滤文件的方法:
          • boolean accept(File dir, String name);测试指定文件是否应该包含在某一文件列表中。
          • 参数:
            • File dir:构造方法中传递的被遍历的目录
            • String name:使用listFiles方法遍历目录获取的每一个文件/文件夹的名称
      • 原理与上面差不多,只是将File dir与String name封装称一个对象
  • 注意:两个过滤器接口,没有实现类,需要自己写实现类,重写accept方法,在方法中自己定义过滤的规则

Expection

Expection

相关的概念与结构

  • java.lang.Throwable类是是java语言中所有错误或异常的祖类
    • Expection:编译的异常,进行编译java程序出现的问题
      • RuntimeExpection:运行期异常,java程序运行过程中出现的问题
    • Error:错误
      • 错误就相当于程序得了一个无法治愈的毛病,必须修改源代码,程序才能运行

tqYrGj.png


异常产生过程解析

tqtPyt.png


异常的处理

1. throw

  • throw关键字

  • 作用:

    • 可以适用于throw关键字在指定的位置抛出指定的异常
  • 使用格式:

    • throw new xxxExpection(“异常产生的原因”);
  • 注意:

    1. throw关键字必须写在方法的内部
    2. throw关键字后边new的对象必须是Expection或者是Expection的子类对象
    3. throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
      • throw关键字后面创建的是RuntimeExpection或者是RuntimeExpection的子类对象,我们可以不处理,默认交给JVM处理
      • throw关键字后面创建的是编译异常,我们就必须处理这个异常,要么throws要么trycatch

2. throws

  • throws关键字:异常处理的第一种方式,交给别人处理

  • 作用:

    • 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象
    • 可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理)
      最终交给JVM处理
  • 使用格式:

    修饰符 返回值类型 方法名(参数列表) throws AAAExpection,BBBExpection....{
        throw new AAAExpection("产生原因");
        throw new BBBExpection("产生原因");
    }
  • 注意:

    1. throws关键字必须写在方法声明处
    2. throws关键字后边声明的异常必须是Expection或者是Expection的子类
    3. 方法内部如果抛出了多个异常对象,那么throws后边也必须声明多个异常
      • 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
    4. 调用了一个声明抛出异常的方法,我们就必须处理声明的异常
      • 要么继续使用throws把异常交给方法的调用者处理,最终交割jvm处理
      • 要么trycatch自己处理异常

3. try catch

  • try…catch:异常处理的第二种方式,自己处理异常

  • 格式:

    try{
        可能产生异常的代码
    }catch(定义一个异常变量,用来接收try中抛出的异常对象){
        异常的处理逻辑,产生异常对象之后怎么处理异常对象
        一般在工作中会把异常的信息记录在日志中
    }
    ...
    catch(异常类目 变量名){
    
    }
    catch可以有多个
  • 注意:

    1. try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
    2. 如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try…catch之后代码
    3. 如果try中没有产生异常,就不会执行catch中的异常处理逻辑,继续执行try…catch之后代码
  • try catch 可以和finally一起使用

    1. finally不能单独使用,必须和try一起使用
    2. finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放
    3. 无论如何finally中的代码都会执行

自定义异常类

  • 自定义异常类:Java提供的异常类不够我们使用,需要自己定义一些异常类

  • 格式:

    public class xxxExpection extends Expection | RuntimeExpection{
            添加一个空参数的构造方法
            添加一个带异常信息的构造方法
    }
  • 注意:

    1. 自定义异常类一般都是以Expection结尾,说明该类是一个异常类
    2. 自定义异常类必须继承Expection或者是RuntimeExpection
      • 继承Expection:那么自定义类异常就是一个编译器异常,如果方法内部抛出了编译器异常就必须处理这个异常,要么throws要么trycatch
      • 继承RuntimeExpection:那么自定义异常类就是一个运行期异常,无需处理,交给虚拟机处理

异常继承之间的一些关系

  • 子父类的异常:
    1. 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常
    2. 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生异常,只能通过捕获处理,不能声明抛出。
  • 注意:父类异常是什么样子类异常就是什么样

Thread

Thread线程

创建多线程的方式

第一种:创建Thread类的子类

  • java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类

  • 实现步骤:

    1. 创建一个Thread类的子类
    2. 在Thread的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
    3. 创建一个Thread类的子类对象
    4. 调用Thread类中的方法start方法,开启新的线程,执行run方法
      • void start();使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
  • 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

第二种:实现Runnable接口

  • Java.lang.Runnable

    • Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
  • Java.lang.Thread的构造方法:

    • Thread(Runnable target);分配新的 Thread 对象。
    • Thread(Runnable target, String name);分配新的 Thread 对象。
  • 实现步骤:

    1. 创建一个Runnable接口的实现类
    2. 在实现类中重写Runnable接口的run方法,设置线程任务
    3. 创建一个Runnable接口的实现对象
    4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5. 调用Thread类中的start方法,开启新的线程,执行run方法
  • 实现Runnable接口创建多线程的好处:

    1. 避免了单线程的局限性
      • 一个类只能继承一个类,类继承了Thread类就不能继承其他类
      • 实现了Runnable接口还可以继承其他类实现其他接口
    2. 增强了程序的扩展性,降低了程序的耦合性
      • 实现Runnable接口的方式,把设置线程任务和开启新的线程进行了分离
      • 实现类中,重写了run方法,用来设置线程任务
      • 创建Thread类对象,调用start方法,用来开启新的线程
  • 匿名内部类实现线程创建

    • 可以简化代码,把子类继承父类,重写父类的方法,创建子类对象合成一步完成,或者把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成

    • 格式:

      new 父类/接口(){
          重写方法
      }
      
      //线程的父类是Thread
      new Thread() {
          @Override
          public void run() {
              for (int i = 0; i < 20; i++) {
                  System.out.println(Thread.currentThread().getName() + "  " + i);
              }
          }
      }.start();
      
      //线程的接口Runnable
      new Thread(new Runnable() {
          @Override
          public void run() {
              for (int i = 0; i < 20; i++) {
                  System.out.println(Thread.currentThread().getName() + "  " + i);
              }
          }
      }).start();

获取线程的名称

  • 获取线程的名称:

    1. 使用Thread类中的方法getName();
      • String getName();返回该线程的名称。
    2. 可以先获取到当前正在执行的线程,再使用线程中的方法getName()获取线程名称
      • static Thread currentThread();返回对当前正在执行的线程对象的引用。
  • 线程的名称:

    • 主线程:main
    • 子线程:Thread-0,Thread-1,Thread-2….

暂停线程

public static void sleep(long millis, int nanos); 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)

  • 此操作受到系统计时器和调度程序精度和准确性的影响。
  • 注意:毫秒数结束后,线程继续执行

多线程内存图解

tq0zH1.jpg


多线程原理

tqBNEq.jpg


线程不安全的解决方案

第一种:使用同步代码块

  • 格式:

    synchronized (锁对象){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
  • 注意:

    1. 同步代码块中的锁对象可以是任意的对象
    2. 但是必须保证多个线程使用的锁对象是同一个
    3. 锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

第二种:使用同步方法

  • 使用步骤:

    1. 把访问了共享数据的方法抽取出来,放到一个方法中
    2. 在方法上添加一个修饰符synchronized
  • 格式:

    修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

第三种:Lock锁

  • java.util.concurrent.locks.lock接口

  • Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

  • Lock接口中的常用方法:

    • void lock();获取锁。
    • void unlock();释放锁。
  • java.util.concurrent.locks.ReentrantLock implements Lock接口

  • 使用步骤:

    1. 在成员位置创建一个ReentrantLock对象
    2. 在可能会出现线程安全问题的代码前调用Lock接口中的方法lock()获取锁
    3. 在可能会出现线程安全问题的代码后调用Lock接口中的方法unlock()释放锁

线程池

  • 线程池:JDK1.5之后提供的

  • java.util.concurrent.Executors:线程池的工厂类,用来生产线程池

  • Executors类中的静态方法:

    • static ExecutorService newFixedThreadPool(int nThreads);创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
      • 参数:
        • int nThreads:创建线程池中包含的线程数量
      • 返回值:
        • ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收
  • java.util.concurrent.ExecutorService:线程池接口

  • 用来从线程池中获取线程,调用start方法执行线程任务:

    • Future<?> submit(Runnable task);提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
  • 关闭销毁线程池的方法:

    • void shutdown();启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
  • 线程池的使用步骤:

    1. 使用线程池工厂类Executors里边的静态方法newFixedThreadPool生产一个指定线程数量的线程池
    2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
    3. 调用ExecutorService中的方法submit传递线程任务,开启线程,执行run方法

线程池类似原理

tqr9SO.jpg

输入输出流

输入输出流

InputStream

  • java.io.InputStream:字节输入流。此抽象类是表示字节输入流的所有类的超类。

  • 定义了所有子类共性的方法:

    • abstract int read();从输入流中读取数据的下一个字节。读取到末尾返回-1。
    • int read(byte[] b);从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。返回值为为-1则读到结束标记。
    • int read(byte[] b, int off, int len);将输入流中最多 len 个数据字节读入 byte 数组。
    • void close();关闭此输入流并释放与该流关联的所有系统资源。
  • java.io.FileInputStream extends InputStream:文件字节输入流

  • 作用:把硬盘文件中的数据读取到内存中使用

  • 构造方法:

    • FileInputStream(File file);通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
    • FileInputStream(String name);通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
    • 参数:
      • File file,String name:读取文件的数据源
    • 作用:
      1. 创建一个FileInputStream对象
      2. 会把FileInputStream对象指向要读取的文件

OutputStream

  • java.io.OutputStream:此抽象类是表示输出字节流的所有类的超类。是一个抽象类,不能直接创建对象,需要用到它的子类。

  • 定义了一些子类共性的成员方法:

    • void close();关闭此输出流并释放与此流有关的所有系统资源。
    • void flush();刷新此输出流并强制写出所有缓冲的输出字节。
    • void write(byte[] b);将 b.length 个字节从指定的 byte 数组写入此输出流。
    • void write(byte[] b, int off, int len);将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
    • abstract void write(int b);`将指定的字节写入此输出流。
  • java.io.FileOutputStream extends OutputStream:文件字节输出流

  • 作用:

    • 把内存中的数据写入到硬盘的文件中
  • 构造方法:

    • FileOutputStream(File file);创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
    • FileOutputStream(String name);创建一个向具有指定名称的文件中写入数据的输出文件流。
    • 参数:写入数据的目的地
      • File file:写入数据的文件
      • String name:写入数据的文件的路径
  • 构造方法作用:

    1. 创建一个FileOutputStream对象
    2. 根据构造方法中传递的文件或者文件的路径创建一个空的文件
    3. 会把FileOutputStream对象指向创建好的文件
  • 追加写:

  • 文件的追加写/续写:使用两个参数的构造方法

    • FileOutputStream(File file, boolean append);创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
    • FileOutputStream(String name, boolean append);创建一个向具有指定 name 的文件中写入数据的输出文件流。
    • 参数:
      • File file,String name:写入数据的目的地
      • boolean append:追加写的开关,true为在文件末尾追加写数据,false则创建新文件覆盖原文件
  • 写换行:写换行符号

    1. windows:\r\n
    2. Linux:\n
    3. mac:\r

Reader

  • 使用字符流与字节流的区别:编码表不同,ascll中中文为2字节,UTF-8中为3字节,字节流单个读取中文时,再将其打印可能出现乱码

  • java.io.Reader:字符输入流,是字符输入流的最顶层的父类,定义了一些共性的成员方法,是一个抽象类。

  • 共性的成员方法:

    • int read();读取单个字符。
    • int read(char[] cbuf);将字符读入数组。
    • abstract void close();关闭该流并释放与之关联的所有资源。
  • java.io.FileReader extends InputStreamReader extends Reader:文件字符输入流

  • 作用:把硬盘文件中的数据以字符的方式读取到文件中

  • 构造方法:

    • FileReader(File file);在给定从中读取数据的 File 的情况下创建一个新 FileReader。
    • FileReader(String fileName);在给定从中读取数据的文件名的情况下创建一个新 FileReader。
    • 参数:
      • File file,String fileName:读取文件的数据源
  • 作用:

    1. 创建一个FileReader对象
    2. 会把FileReader对象指向要读取的文件

Writer

  • java.io.Writer:字符输出流。所有字符输出流的最顶层的父类,是一个抽象类,定义了共性的成员方法。

    • void write(char[] cbuf);写入字符数组。
    • abstract void write(char[] cbuf, int off, int len);写入字符数组的某一部分。
    • void write(int c);写入单个字符。
    • void write(String str);写入字符串。
    • void write(String str, int off, int len);写入字符串的某一部分。
    • abstract void close();关闭此流,但要先刷新它。
    • abstract void flush();刷新该流的缓冲。
  • java.io.FileWriter extends OutputStreamWriter extends Writer:文件字符输出流

  • 作用:把内存中的字符数据写入到内存缓冲区中,调用flush或者close方法后才把转换的字节刷新到硬盘中的文件中

  • 构造方法:

    • FileWriter(File file);根据给定的 File 对象构造一个 FileWriter 对象。
    • FileWriter(String fileName);根据给定的文件名构造一个 FileWriter 对象。
    • 参数:
      • File file,String fileName:写入数据的目的地
  • 作用:

    1. 会创建一个FileWriter对象
    2. 根据构造方法中传递的参数创建一个文件
    3. 把FileWriter对象指向创建好的文件
  • 追加写:

    • 与OutpputStream类似,续写用两个参数的构造方法,换行用换行符号

Properties

  • java.util.Properties extends HashTable< K,V > implements Map< K,V >

  • Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。

  • Properties集合是唯一一个和IO流相结合的集合

    • 可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储。
    • 可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用。
  • 属性列表中每个键及其对应值都是一个字符串。

    • Properties集合是一个双列集合,key和value默认都是字符串。
  • Object setProperty(String key, String value);调用 Hashtable 的方法 put。

  • String getProperty(String key);用指定的键在此属性列表中搜索属性。

  • Set<String> stringPropertyNames();返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。

store

  • 使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储

  • void store(OutputStream out, String comments);以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。

  • void store(Writer writer, String comments);以适合使用 load(Reader) 方法的格式,将此 Properties 表中的属性列表(键和元素对)写入输出字符。

  • 参数:

    • OutputStream out:字节输出流,不能写入中文
    • Writer writer:字符输入流,可以写入中文
    • String comments:注释,用来解释说明保存的文件是做什么的,不能使用中文,因为会产生乱码,默认是unicode编码,一般使用“”空字符串
  • 使用步骤:

    1. 创建Properties集合对象,添加数据
    2. 创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地
    3. 使用Properties集合中的方法store
    4. 释放资源

load

  • 使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用

  • void load(InputStream inStream);从输入流中读取属性列表(键和元素对)。

  • void load(Reader reader);按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。

  • 参数:

    • InputStream inStream:字节输入流,不能读取含有中文的键值对
    • Reader reader:字符输入流,能读取还含有中文的键值对
  • 使用步骤:

    1. 创建Properties集合对象
    2. 使用Properties集合中的方法load读取保存键值对的文件
    3. 遍历Properties集合
  • 注意:

    1. 存储键值对的文件中,键与值默认的连接符号可以使用等号,空格,其他符号
    2. 存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取
    3. 存储键值对的文件中,键与值,默认都是字符串不用再加引号

BufferedInputStream

  • java.io.BufferedInputStream extends FilterInputStream extends InputStream:字节缓冲输入流

  • 构造方法:

    • BufferedInputStream(InputStream in);创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
    • BufferedInputStream(InputStream in, int size);创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
  • 参数:

    • InputStream in:字节输入流,给其增加一个缓冲区,提高读取效率
    • int size:指定的缓冲区大小,不写则是默认的
  • 使用步骤:

    1. 创建一个字节输入流对象,构造方法中绑定读取的数据源
    2. 创建一个BufferedInputStream对象,构造方法中传递字节输入流对象,提高字节输入流读取效率
    3. 使用BufferedInputStream对象中的方法read读取文件
    4. 释放资源

BufferedOutputStream

  • java.io.BufferedOutputStream extends FilterOutputStream extends OutputStream:字节缓冲输出流

  • 构造方法:

    • BufferedOutputStream(OutputStream out);创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
    • BufferedOutputStream(OutputStream out, int size);创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
  • 参数:

    • OutputStream out:字节输出流,给传递的参数添加一个缓冲区,提高它的写入效率
    • int size:指定缓冲流内部缓冲区的大小,不指定则为默认大小
  • 使用步骤:

    1. 创建一个字节输出流对象
    2. 创建BufferedOutputStream对象,构造方法中传递字节输出流对象,提高它的效率
    3. 使用BufferedOutputStream对象中的方法write把数据写入到内部的缓冲区中
    4. 使用BufferedOutputStream中的方法flush把内部缓冲区中的数据刷新到文件中
    5. 释放资源

缓冲流原理

tqfXvt.jpg


BufferedReader

  • java.io.BufferedReader extends Reader:字符缓冲输入流

  • 构造方法:

    • BufferedReader(Reader in);创建一个使用默认大小输入缓冲区的缓冲字符输入流。
    • BufferedReader(Reader in, int sz);创建一个使用指定大小输入缓冲区的缓冲字符输入流。
  • 参数:

    • Reader in:字符输入流,缓冲流为其增加一个缓冲区,提高读取的效率
    • int sz;指定缓冲流的缓冲区大小,不写则为默认
  • 特有的成员方法:

    • String readLine();读取一个文本行。 读取一行数据
      • 行的终止符号:通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行(\r\n)。
      • 返回值:包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
  • 使用步骤:

    1. 创建字符缓冲输入流对象,构造方法中传递字符输入流
    2. 使用字符缓冲输入流对象中的方法read/readLine读取文本
    3. 释放资源

BufferedWriter

  • java.io.BufferedWriter extends Writer:字符缓冲输出流

  • 构造方法:

    • BufferedWriter(Writer out);创建一个使用默认大小输出缓冲区的缓冲字符输出流。
    • BufferedWriter(Writer out, int sz);创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
  • 参数:

    • Writer out:字符输出流,缓冲流会给其一个缓冲区,提高写入的效率
    • int sz:指定缓冲区的大小,不写则默认大小
  • 特有的成员方法:

    • void newLine();写入一个行分隔符。会根据不同的操作系统获取不同的行分隔符
  • 使用步骤:

    1. 创建一个字符缓冲输出流对象,构造方法中传递字符输出流
    2. 调用字符缓冲输出流的方法write,把数据写入到内存缓冲区中
    3. 调用flush方法
    4. 释放资源

InputStreamReader

  • java.io.InputStreamReader extends Reader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。(解码)

  • 构造方法:

    • InputStreamReader(InputStream in);创建一个使用默认字符集的 InputStreamReader。
    • InputStreamReader(InputStream in, String charsetName);创建使用指定字符集的 InputStreamReader。
    • 参数:
      • InputStream in:字节输入流,用来读取文件中保存的字节
      • String charsetName:指定的编码表名称
  • 使用步骤:

    1. 创建一个InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
    2. 使用InputStreamReader对象中的方法读取文件
    3. 释放资源
  • 注意:构造方法中指定的编码表要和文件编码相同,否则会发生乱码


OutputStreamWriter

  • java.io.OutputStreamWriter extends Writer:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。(编码)

  • 构造方法:

    • OutputStreamWriter(OutputStream out);创建使用默认字符编码的 OutputStreamWriter。
    • OutputStreamWriter(OutputStream out, String charsetName);创建使用指定字符集的 OutputStreamWriter。
    • 参数:
      • OutputStream out:字节输出流,可以用来写转换之后的文件到文件中
      • String charsetName:指定的编码表名称,不区分大小写
  • 使用步骤:

    1. 创建一个OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
    2. 使用OutputStreamWriter对象中的方法write把字符转换为字节,存储到缓冲区中
    3. 使用OutputStreamWriter中的方法flush把缓冲区的字节刷新到文件中(使用字节流写字节的过程)
    4. 释放资源

ObjectInputStream

  • java.io.ObjectInputStream extends InputStream:对象的反序列化流,把文件中保存的对象以流的方式读取出来使用

  • 构造方法:

    • ObjectInputStream(InputStream in);创建从指定 InputStream 读取的 ObjectInputStream。
    • 参数:
      • InputStream in:字节输入流
  • 特有的成员方法:

    • Object readObject();从 ObjectInputStream 读取对象。
  • 使用步骤:

    1. 创建一个ObjectInputStream对象,构造方法中传递字节输入流
    2. 使用ObjectInputStream对象中的方法readObject读取保存对象的文件
    3. 释放资源

ObjectOutputStream

  • java.io.ObjectOutputStream extends OutputStream:对象的序列化流,把对象以流的方式写入到文件中保存

  • 构造方法:

    • ObjectOutputStream(OutputStream out);创建写入指定 OutputStream 的 ObjectOutputStream。
    • 参数:
      • OutputStream out:字节输出流
  • 特有的成员方法:

    • void writeObject(Object obj);将指定的对象写入ObjectOutputStream。
  • 使用步骤:

    1. 创建一个ObjectOutputStream对象,构造方法中传递字节输出流
    2. 使用ObjectOutputStream对象中的方法writeObject把对象写入到文件中
    3. 释放资源

对象的序列化与反序列化

  • 当这个类创建的对象需要序列化时,必须实现Serializable接口,不然会抛出NotSerializableException异常
  • Serializable也叫标记型接口:要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记
  • 当我们进行序列化和反序列化的时候,就会检测类上是否有标记,如果有,就可以序列化和反序列化如果没有,就会抛出异常

tq4PeK.jpg

  • 序列化时可能会遇到的序列号问题InvalidClassException的解决方案:手动添加一个序列号
    tq4Wm6.jpg

MyBatis四

MyBatis四

Mybatis 延迟加载

  • 什么是延迟加载?

    • 就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载.
    • 好处:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
    • 坏处:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
  • 一般对于对多查询时使用延迟加载,而对于对一查询时不使用延迟加载。

  • 如:之前的查找用户对应的账户操作时可以使用延迟加载,提高性能

  • 映射文件配置:

    <resultMap type="account" id="accountMap"> 
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <!-- 它是用于指定从表方的引用实体属性的 --> 
        <association property="user" javaType="user"> <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>
  • SQL语句:SELECT u.*, acc.id id, acc.uid, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.uid

  • 开启延迟加载后的映射文件:

    <resultMap type="account" id="accountMap"> 
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user"
        select="com.itheima.dao.IUserDao.findById"
        column="uid">
        </association>
    </resultMap>
    
    <select id="findAll" resultMap="accountMap">
        select * from account
    </select>
  • 如何开启延迟加载

    • 在主配置文件中设置

      <settings> 
          <setting name="lazyLoadingEnabled" value="true"/>
          <setting name="aggressiveLazyLoading" value="false"/>
      </settings>

Mybatis 缓存

像大多数的持久化框架一样,Mybatis 也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。

Mybatis 中缓存分为一级缓存,二级缓存。

一级缓存

  • 一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就存在。

  • 一级缓存中存放的是实体类对象。

二级缓存

  • 二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

  • 一级缓存中存放的是数据。

  • 共享二级缓存的对象中如果有一个对象对二级缓存所对应的sql语句执行了增删改操作,将会清空该二级缓存区域的数据

  • 二级缓存的开启与关闭:

    1. 在 SqlMapConfig.xml 文件开启二级缓存

      <settings>
      <!-- 开启二级缓存的支持 --> 
          <setting name="cacheEnabled" value="true"/>
      </settings>
    2. 配置相关的 Mapper 映射文件

      • 在映射文件中添加<cache></cache>
    3. 配置对应的想用二级缓存方法

      <select id="findById" resultType="user" parameterType="int" useCache="true">
          select * from user where id = #{uid}
      </select>

Mybatis 注解开发

mybatis 的常用注解说明

@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与@Result 一起使用,封装多个结果集
@ResultMap:实现引用@Results 定义的封装
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
@SelectProvider: 实现动态 SQL 映射
@CacheNamespace:实现注解二级缓存的使用
  • 使用注解进行一对一查询时,可在@Results对应的@Result中配置,如:

    @Result(column="uid",
            property="user",
            one=@One(select="com.itheima.dao.IUserDao.findById",
            fetchType=FetchType.EAGER) )
  • 使用注解一对多查询时,可在@Results对应的@Result中配置,如:

    @Result(column="id",property="accounts",
            many=@Many(
            select="com.itheima.dao.IAccountDao.findByUid",
            fetchType=FetchType.LAZY
            ) )

mybatis 基于注解的二级缓存

  1. 在主配置文件中配置二级缓存

    <settings>        
        <setting name="cacheEnabled" value="true"/>
    </settings>
  2. 在持久层接口中使用注解配置二级缓存

    @CacheNamespace(blocking=true)
    public interface IUserDao {}

MyBatis三

MyBatis三

Mybatis 的连接池技术

我们在前面的 WEB 课程中也学习过类似的连接池技术,而在 Mybatis 中也有连接池技术,但是它采用的是自己的连接池技术。在 Mybatis 的SqlMapConfig.xml 配置文件中,通过<dataSource type=”pooled”>来实现Mybatis中连接池的配置。

1. Mybatis 连接池的分类

Mybatis 将它自己的数据源分为三类:

  1. UNPOOLED 不使用连接池的数据源
  2. POOLED 使用连接池的数据源
  3. JNDI 使用 JNDI 实现的数据源

2. PooledDataSource 工作原理

tTfPf0.png

Mybatis 的事务控制

1. Mybatis 自动提交事务的设置

连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了 JDBC中的connection.commit()方法实现事务提交。

因此可以这样取出连接session = factory.openSession(true);


Mybatis 的动态 SQL 语句

1. 动态 SQL 之< if>标签

例如:

<select id="findByUser" resultType="user" parameterType="user">
    select * from user where 1=1
    <if test="username!=null and username != '' ">
    and username like #{username}
    </if> 
    <if test="address != null">
    and address like #{address}
    </if>
</select>

2. 动态 SQL 之< where>标签

例如:

<select id="findByUser" resultType="user" parameterType="user">
    <include refid="defaultSql"></include> 
    <where> 
        <if test="username!=null and username != '' ">
        and username like #{username}
        </if> 
        <if test="address != null">
        and address like #{address}
        </if>
    </where>
</select>

3. 动态标签之< foreach>标签

QueryVo 中有一个 List 集合用于封装参数

配置文件例如:

<!-- 查询所有用户在 id 的集合之中 --> 
<select id="findInIds" resultType="user" parameterType="queryvo">
    <!-- select * from user where id in (1,2,3,4,5); --> 
    <include refid="defaultSql"></include> 
    <where> 
        <if test="ids != null and ids.size() > 0"> <foreach collection="ids" open="id in ( " close=")" item="uid" separator=",">
            #{uid}
        </foreach>
        </if>
    </where>
</select>
  • <foreach>标签用于遍历集合,它的属性:
  1. collection:代表要遍历的集合元素,注意编写时不要写#{}
  2. open:代表语句的开始部分
  3. close:代表结束部分
  4. item:代表遍历集合的每个元素,生成的变量名
  5. sperator:代表分隔符

Mybatis 多表查询之一对一(多对一)

方式一:

  • 定义一个实体类继承其中一方的实体类,然后属性增加另一实体类中的是属性作为属性。

  • 如:Account和User,定义一个AccountUser类继承Account类,然后将想要的User中的属性加入AccountUser中,比如添加username和address两个属性,从而可以完成一对一的查询,一个AccountUser中包含了一个Account对应的一个User。

  • 小结:定义专门的 po 类作为输出类型,其中定义了 sql 查询结果集所有的字段。此方法较为简单,企业中使用普遍。

方式二:

使用 resultMap,定义专门的 resultMap 用于映射一对一查询结果。
通过面向对象的(has a)关系可以得知,我们可以在 Account 类中加入一个 User 类的对象来代表这个账户是哪个用户的。

  • 在Account中增加一个User属性,用于表示这个Account属于哪个User。

  • 同时还要修改对应的映射配置文件,如下修改

    <!-- 建立对应关系 --> 
    <resultMap type="account" id="accountMap"> 
        <id column="aid" property="id"/>
        <result column="uid" property="uid"/>
        <result column="money" property="money"/>
        <!-- 它是用于指定从表方的引用实体属性的 --> 
        <association property="user" javaType="user"> 
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="birthday" property="birthday"/>
            <result column="address" property="address"/>
        </association>
    </resultMap>

一对多查询

  • 需求:

    • 查询所有用户信息及用户关联的账户信息。
  • 分析:
    用户信息和他的账户信息为一对多关系,并且查询过程中如果用户没有账户信息,此时也要将用户信息查询出来,我们想到了左外连接查询比较合适。

  • 可以在User类中增加一个Account是Set集合。

  • 映射文件配置:

    <resultMap type="user" id="userMap"> <id column="id" property="id">
        </id> <result column="username" property="username"/>
        <result column="address" property="address"/>
        <result column="sex" property="sex"/>
        <result column="birthday" property="birthday"/>
        <!-- collection 是用于建立一对多中集合属性的对应关系
        ofType 用于指定集合元素的数据类型 --> 
    
        /* property="accList":关联查询的结果集存储在 User 对象的上哪个属性。
        ofType="account":指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名 */
    
        <collection property="accounts" ofType="account">
            <id column="aid" property="id"/>
            <result column="uid" property="uid"/>
            <result column="money" property="money"/>
        </collection>
    </resultMap>
  • SQL语句:SELECT u.*, acc.id id, acc.uid, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.uid


Mybatis 多表查询之多对多

  • 多对多关系其实我们看成是双向的一对多关系。

  • 拿用户与角色举例,一个用户可以有多个角色,一个角色又可以有多个用户。
    tTb2E8.png

  • 需求:

    • 实现查询所有对象并且加载它所分配的用户信息。
  • 分析:

    • 查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中间表(USER_ROLE 表)才能关联到用户信息。
  • SQL语句:

    SELECT
    r.*,u.id uid,
    u.username username,
    u.birthday birthday,
    u.sex sex,
    u.address address
    FROM 
    ROLE r
    INNER JOIN 
    USER_ROLE ur
    ON ( r.id = ur.rid)
    INNER JOIN
    USER u
    ON (ur.uid = u.id);
  1. 实现角色到用户的一对多

    • 在角色实体类中加入一个用户的Set集合
    • 编写dao接口
    • 配置映射文件
  2. 实现用户到角色的一对多

    • 在用户实体类中加入一个角色的Set集合
    • 编写dao接口
    • 配置映射文件
  3. 最终完成多对多查询。

MyBatis二

MyBatis二

自定义MyBatis

tR2AoQ.png

用户在映射配置文件中配置的相关属性

  • resultType 属性:用于指定结果集的类型。

  • parameterType 属性:用于指定传入参数的类型。

  • sql 语句中使用#{}字符: 它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。具体的数据是由#{}里面的内容决定的。

    <select id="findById" resultType="com.itheima.domain.User" parameterType="int">
        select * from user where id = #{uid}
    </select>

ognl 表达式:

  • 它是 apache 提供的一种表达式语言,全称是:Object Graphic Navigation Language 对象图导航语言
  • 它是按照一定的语法格式来获取数据的。
  • 语法格式就是使用 #{对象.对象}的方式
    • #{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写 username。

新增用户的id的返回值:

<!-- 配置保存时获取插入的 id --> 
<selectKey keyColumn="id" keyProperty="id" resultType="int">
    select last_insert_id();
</selectKey>

#{}与${}的区别

  1. #{}表示一个占位符号:

    • 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
    • #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}括号中可以是 value 或其它名称。
  2. ${}表示拼接 sql 串

    • 通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。

定义resultMap

为什么要定义?

  • resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类
    型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

  • 定义

    <!-- 建立 User 实体和数据库表的对应关系
        type 属性:指定实体类的全限定类名
        id 属性:给定一个唯一标识,是给查询 select 标签引用用的。
    --> 
    <resultMap type="com.itheima.domain.User" id="userMap"> 
        <id column="id" property="userId"/>
        <result column="username" property="userName"/>
        <result column="sex" property="userSex"/>
        <result column="address" property="userAddress"/>
        <result column="birthday" property="userBirthday"/>
    </resultMap>
    
    id 标签:用于指定主键字段
    result 标签:用于指定非主键字段
    column 属性:用于指定数据库列名
    property 属性:用于指定实体类属性名称
  • 映射配置使用

    <!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap">
        select * from user
    </select>

properties(属性)

  • 第一种

    //properties标签内容
    <properties> 
        <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="1234"/>
    </properties>
    
    //datasource内容
    <dataSource type="POOLED"> 
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </dataSource>
  • 第二种

    //properties标签内容
    <properties url=file:///D:/IdeaProjects/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties">
    </properties>
    
    //datasource内容
    <dataSource type="POOLED"> 
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </dataSource>
    • 此时外部文件种的内容
      jdbc.driver=com.mysql.jdbc.Driver
      jdbc.url=jdbc:mysql://localhost:3306/eesy
      jdbc.username=root
      jdbc.password=1234

typeAliases(类型别名)

  • 在 SqlMapConfig.xml 中配置:

    <typeAliases>
    <!-- 单个别名定义 --> 
        <typeAlias alias="user" type="com.itheima.domain.User"/>
    <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> 
        <package name="com.itheima.domain"/>
        <package name="其它包"/>
    </typeAliases>

mappers标签下定义package

注册指定包下的所有 mapper 接口
如:<package name="cn.itcast.mybatis.mapper"/>
  • 使用这种方法就不需要再写mapper以及class或者resource了,因为它会自己找到包下的dao接口以及它所对应的映射配置文件。

MyBatis一

MyBatis一

什么是框架?

  • 它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。
  • 使用框架的好处:
    • 框架封装了很多的细节,使开发者可以使用极简单的方式实现功能。大大提高开发效率。

MyBatis

  • 概述:

    • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作,只关注sql语句本身。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。使用了ORM思想。
  • ORM:

    • Object Relational Mapping 对象关系映射
    • 简单的说,就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作表

入门

环境搭建

  • MyBatis的dependcy依赖:

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>x.x.x</version>
    </dependency>
  • MyBatis主配置文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!-- 配置环境 -->
        <environments default="mysql">
            <!-- 配置mysql的环境 -->
            <environment id="mysql">
                <!-- 配置事务的类型 -->
                <transactionManager type="JDBC"></transactionManager>
                <!-- 配置连接池 -->
                <dataSource type="POOLED">
                    <!-- 配置连接数据库的四个基本信息 -->
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="uri" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT"/>
                    <property name="username" value="root"/>
                    <property name="password" value="000000"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 指定映射配置文件的位置,指的是每个dao独立的配置文件位置 -->
        <mappers>
            <mapper resource="MyBatis1/dao/UserDao.xml"></mapper>
        </mappers>
    </configuration>
  • MyBatis的映射配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="MyBatis1.dao.UserDao">
        <!-- 配置查询所有 -->
        <select id="findAll" resultType="MyBatis1.domain.User">
            select * from user ;
        </select>
    </mapper>
  • 步骤

    1. 创建maven工程并导入坐标
    2. 创建实体类和dao的接口
    3. 创建MyBatis的主配置文件
    4. 创建映射配置文件

注意事项:

  1. 创建映射文件xml位置必须要和对应的dao接口的包结构相同。

  2. 在idea中创建目录时,和包不一样,用点连着创建的目录是一个一级目录,不是多级目录

  3. 映射配置文件的mapper标签的namespace属性的取值必须是dao接口的全限定类名

  4. 映射配置文件的操作配置(select…)id属性的取值必须是dao接口的方法名

  • 好处: 当我们这么写了以后,就不用再写实现类了,MyBatis会帮我们实现。

使用注解配置

  1. 把映射配置文件移除,在dao接口的方法上使用@Select注解,并且指定sql语句
  2. 把主配置文件中mapper中,使用class属性指定dao接口的全限定类名

请我喝杯咖啡吧~

支付宝
微信