admin管理员组

文章数量:1568307

1、Junit单元测试

1.1、测试概述

测试分类:

  1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
  2. 白盒测试:需要写代码的。关注程序具体的执行流程

1.2、Junit使用:白盒测试

  • 步骤:
    1. 定义一个测试类(测试用例)
      • 建议:
        • 测试类名:被测试的类名Test CalculatorTest
        • 包名:xxx.xxx.xx.test cn.itcast.test
    2. 定义测试方法:可以独立运行
      • 建议:
        • 方法名:test测试的方法名 testAdd()
        • 返回值:void
        • 参数列表:空参
    3. 给方法加@Test
    4. 导入junit依赖环境
package com.xh.test;

import org.junit.Test;

public class AddTest {
    @Test
    public void Test(){
        System.out.println("我被执行了");
    }
}
  • 结果判定
    • 红色:失败
    • 绿色:成功
    • 一般我们会使用断言操作来处理结果
      • Assert.assertEquals(期望的结果,运算的结果)
  • 补充:
    • @Before:
      • 修饰的方法会在测试方法之前被自定执行
    • @After:
      • 修饰的方法会在测试方法执行之后自动被执行

2、反射

2.1、反射:框架设计的灵魂

  • 框架:半成品软件。可以在框架的基础上进行软件,简化编码

  • 反射:将类的各个组成部分封装为其他对象,这就是反射机制

    • 好处:
      1. 可以在程序运行过程中,操作这些对象。
      2. 可以解藕,提高程序的可扩展性。

2.2、获取class对象的方式

  1. Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
    • 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  2. 类名.class:通过类名的属性class获取
    • 多用于参数的传递
  3. 对象.getClass():getClass()方法在Object类中定义着
    • 多用于对象的获取字节码的方式
  • 结论:

    同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。

2.3、Class对象功能

  • 获取功能:

    1. 获取成员变量们

      • Field[] getFields():获取所有public修饰的成员变量

      • Field getField(String name):获取指定名称的 public修饰的成员变量

      • Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符

      • Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符

        package com.xh.reflect;
        
        import java.lang.reflect.Field;
        
        /*
            Class对象功能
                获取功能
                    获取成员变量们
                        `Field[] getFields()`:获取所有public修饰的成员变量
                        `Field[] getField(String name)`:获取指定名称的 public修饰的成员变量
                        `Field[] getDeclaredFields()`:获取所有的成员变量,不考虑修饰符
                        `Field[] getDeclaredField(String name)`:获取指定名称的成员变量,不考虑修饰符
         */
        public class reflectDome {
            public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
                //获取person的class对象
                Class<Person> personClass = Person.class;
                //获取成员变量Field[] getFields():获取所有public修饰的成员变量
                Field[] fields = personClass.getFields();
                for (Field fd:fields){
                    System.out.println(fd);
                }
                System.out.println("-------------");
                //Field[] getField(String name)`:获取指定名称的 public修饰的成员变量
                Field a = personClass.getField("a");
                System.out.println(a);
                System.out.println("-------------");
                //Field[] getDeclaredFields()`:获取所有的成员变量,不考虑修饰符
                Field[] declaredFields = personClass.getDeclaredFields();
                for (Field ff : declaredFields){
                    System.out.println(ff);
                }
                System.out.println("-------------");
                //Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
                Field name = personClass.getDeclaredField("name");
                System.out.println(name);
                System.out.println("-----------");
            }
        }
        
    2. 获取构造方法们

      • Constructor<?>[] getConstructors():获取所有public修饰的构造方法

      • Constructor<?> getConstructor(类<?>... parameterTypes):获取指定参数(可变参数可以传也可以不传)public修饰的有参/无参构造方法

      • Constructor<?>[] getDeclaredConstructors():获取所有的构造方法,不考虑修饰符

      • Constructor<?> getDeclaredConstructor(类<?>... parameterTypes):获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符

        package com.xh.reflect;
        
        import java.lang.reflect.Constructor;
        import java.lang.reflect.Field;
        import java.lang.reflect.InvocationTargetException;
        
        /*
            获取构造方法们
                `Constructor<?>[] getConstructors()`:获取所有public修饰的构造方法
                `Constructor<?> getConstructor(类<?>... parameterTypes)`:
                 获取指定参数(可变参数可以传也可以不传)public修饰的有参/无参构造方法
                `Constructor<?>[] getDeclaredConstructors()`:获取所有的构造方法,不考虑修饰符
                `Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)`:
                获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符
         */
        public class reflectDome2 {
            public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
                //获取person的class对象
                Class<Person> personClass = Person.class;
                //`Constructor<?>[] getConstructors()`:获取所有public修饰的构造方法
                Constructor<?>[] constructors = personClass.getConstructors();
                for (Constructor constructor : constructors){
                    System.out.println(constructor);
                }
                System.out.println("------------");
                //Constructor<?> getConstructor(类<?>... parameterTypes)`:
                //获取指定参数(可变参数可以传也可以不传)public修饰的有参/无参构造方法
                //Constructor<?> constructors1 = personClass.getConstructor(String.class, int.class);
                Constructor<?> constructors2 = personClass.getConstructor();
                //System.out.println(constructors1);
                System.out.println(constructors2);
                System.out.println("------------");
                //`Constructor<?>[] getDeclaredConstructors()`:获取所有的构造方法,不考虑修饰符
                Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
                for (Constructor constructor : declaredConstructors){
                    System.out.println(constructor);
                }
                System.out.println("-----------");
                //`Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)`:
                //获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符
                Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor();
                Constructor<Person> declaredConstructor2 = personClass.getDeclaredConstructor(String.class,int.class);
                System.out.println(declaredConstructor);
                System.out.println(declaredConstructor2);
                System.out.println("----------");
            }
        }
        
    3. 获取成员方法们

      • Method[] getMethods():获取所有被public修饰的方法(包含父类中的方法)

      • Method getMethods(String name,类<?>... parameterTypes):获取指定名称被public修饰的方法可以根据方法选择传参或不传参

      • Method[] getDeclaredMethods():获取所有的方法,不考虑修饰符(不包含父类中的方法)

      • Method getDeclaredMethods(String name,类<?>... parameterTypes):获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符

        package com.xh.reflect;
        
        import java.lang.reflect.Constructor;
        import java.lang.reflect.InvocationTargetException;
        import java.lang.reflect.Method;
        
        /*
            获取成员方法们
                `Method[] getMethods()`:获取所有被public修饰的方法(包含父类中的方法)
                 `Method getMethods(String name,类<?>... parameterTypes)`:
                获取指定名称被public修饰的方法可以根据方法选择传参或不传参
                `Method[] getDeclaredMethods()`:获取所有的方法,不考虑修饰符(不包含父类中的方法)
                `Method getDeclaredMethod(String name,类<?>... parameterTypes)`:
                获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符
         */
        public class reflectDome3 {
            public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
                //获取person的class对象
                Class<Person> personClass = Person.class;
                //Method[] getMethods()`:获取所有被public修饰的方法(包含父类中的方法)
                Method[] methods = personClass.getMethods();
                for (Method method:methods){
                    System.out.println(method);
                }
                System.out.println("------------");
                //Method getMethods(String name,类<?>... parameterTypes)`:
                //获取指定名称被public修饰的方法可以根据方法选择传参或不传参
                Method eat = personClass.getMethod("eat");
                Method eat2 = personClass.getMethod("eat",String.class);
                System.out.println(eat);
                System.out.println(eat2);
                System.out.println("----------");
                //`Method[] getDeclaredMethods()`:获取所有的方法,不考虑修饰符(包含父类中的方法)
                Method[] declaredMethods = personClass.getDeclaredMethods();
                for (Method method:declaredMethods){
                    System.out.println(method);
                }
                System.out.println("---------------");
                //`Method getDeclaredMethod(String name,类<?>... parameterTypes)`:
                //获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符
                Method happy = personClass.getDeclaredMethod("happy");
                Method happy1 = personClass.getDeclaredMethod("happy",String.class);
                System.out.println(happy);
                System.out.println(happy1);
                
            }
        }
        
    4. 获取类名

      • String getName():全包名

        		//获取person的class对象
                Class<Person> personClass = Person.class;
        		//获取类名
                //String getName()
                String name = personClass.getName();
                System.out.println(name);//com.xh.reflect.Person
        
  • Field:成员变量

    • 操作:

      1. 给成员变量设置值

        • void set(Object obj,Object value)
      2. 获取成员变量的值

        • get(Object obj)
      3. 忽略访问权限修饰符的安全检查

        • setAccessible(true):暴力反射

          package com.xh.reflect;
          
          import java.lang.reflect.Field;
          
          /*
                  Field:成员变量
                      给成员变量设置值:
                          void set(Object obj,Object value)
                      获取成员变量的值:
                          get(Object obj)
                      忽略访问权限修饰符的安全检查
                          setAccessible(true):暴力反射
           */
          public class reflectDome {
              public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
                  //获取person的class对象
                  Class<Person> personClass = Person.class;
          //        给成员变量设置值:
          //        void set(Object obj,Object value)
                  //创建需要赋值的对象
                  Person person = new Person();
                  //获取成员变量的对象Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
                  //IllegalAccessException:表示没有访问权限的异常
                  // 需要使用暴力反射才能设置值\获取值
                  Field name1 = personClass.getDeclaredField("name");
                  name1.setAccessible(true);
                  name1.set(person,"出家里");
                  System.out.println(person);
                  System.out.println("------------");
                  //获取指定对象中的指定属性的值
                  Object o = name1.get(person);
                  System.out.println(o);
              }
          }
          
  • Constructor:构造方法

    • 创建对象:

      • T newInstance(Object… initargs)

      • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法

        package com.xh.reflect;
        
        import java.lang.reflect.Constructor;
        import java.lang.reflect.Field;
        import java.lang.reflect.InvocationTargetException;
        
        /*
            Constructor:构造方法
                创建对象
                    T newInstance(Object... initargs)
                    如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
         */
        public class reflectDome2 {
            public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
                //获取person的class对象
                Class<Person> personClass = Person.class;
        //        Constructor:构造方法
        //                创建对象
        //        T newInstance(Object... initargs)
                //`Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)`:
                //获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符
                Constructor<Person> declaredConstructor3 = personClass.getDeclaredConstructor(String.class,int.class);
                //暴力反射
                declaredConstructor3.setAccessible(true);
                Person person = declaredConstructor3.newInstance("zhangsna",18);
                System.out.println(person);
                System.out.println("--------------");
                //如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
                Person person1 = personClass.newInstance();
                System.out.println(person1);
            }
        }
        
  • Method:方法对象

    • 执行方法:

      • object invoke(Object obj,Object... args)
    • 获取方法名称:

      • String getName:获取方法名

        package com.xh.reflect;
        
        import java.lang.reflect.Constructor;
        import java.lang.reflect.InvocationTargetException;
        import java.lang.reflect.Method;
        
        /*
            Method:方法对象
                执行方法:
                    object invoke(Object obj,Object... args)
                获取方法名称:
                    String getName:获取方法名
         */
        public class reflectDome3 {
            public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
                //获取person的class对象
                Class<Person> personClass = Person.class;
                //`Method getDeclaredMethod(String name,类<?>... parameterTypes)`:
                //获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符
                Method happy = personClass.getDeclaredMethod("happy");
                Method happy1 = personClass.getDeclaredMethod("happy",String.class);
                //创建一个真实对象
                Person person = new Person();
                happy.setAccessible(true);
                happy.invoke(person);
                //String getName:获取方法名
                String name1 = happy.getName();
                System.out.println(name1);
                System.out.println("-----------");
                happy1.setAccessible(true);
                happy1.invoke(person,"足球");
                String name2 = happy1.getName();
                System.out.println(name2);
            }
        }
        
  • 案例:

    • 需求:写一个”框架“,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法

      • 实现:

        1. 配置文件
        2. 反射
      • 步骤:

        1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中

        2. 在程序中加载读取配置文件

        3. 使用反射技术来加载类文件进内存

        4. 创建对象

        5. 执行方法

          className=com.xh.reflect.Person
          methodName=eat
          
          package com.xh.reflect;
          
          import java.io.IOException;
          import java.io.InputStream;
          import java.lang.reflect.InvocationTargetException;
          import java.lang.reflect.Method;
          import java.util.Properties;
          
          /*
              需求:写一个”框架“,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
                  实现:
                      1. 配置文件
                      2. 反射
                  步骤:
                      1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
                      2. 在程序中加载读取配置文件
                      3. 使用反射技术来加载类文件进内存
                      4. 创建对象
                      5. 执行方法
           */
          public class reflectDome4 {
              public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
                  //在程序中加载读取配置文件
                  Properties properties = new Properties();
                  //创建类加载器对象获取文件的位置或者字节输出流
                  ClassLoader classLoader = reflectDome4.class.getClassLoader();
                  InputStream is = classLoader.getResourceAsStream("pro.properties");
                  //通过Properties对象中的方法读取配置文件
                  properties.load(is);
                  //获取Properties对象中读取文件的数据
                  String className = properties.getProperty("className");
                  String methodName = properties.getProperty("methodName");
                  //通过读取配置文件中的className数据获取class对象
                  Class aClass = Class.forName(className);
                  //通过class对象获取真实的对象
                  Object o = aClass.newInstance();
                  //通过class对象获取要执行的方法
                  Method method = aClass.getMethod(methodName);
                  //执行方法
                  method.invoke(o);
              }
          }
          

3、注解

3.1、注解概述

  • 概念:说明程序的。给计算机看的
  • 注释:用文字描述程序的。给程序员看的
  • 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
  • 概念描述:
    • JDK1.5之后的新特性
    • 说明程序的
    • 使用注解:@注解名称
  • 作用分类:
    1. 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】 可以使用cmd中的javadoc命令根据java文件中的注解生成文档
    2. 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
    3. 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】

3.2、JDK内置注解

  • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
  • @Deprecated:该注解标注的内容,表示已过时
  • @SuppressWarnings:压制警告
    • 一般传递参数all @SuppressWarnings("all")

3.3、自定义注解

  • 格式:

    ​ 元注解

    public @interface 注解名称{}

  • 本质:注解本质上就是接口,该接口默认继承Annotation接口

    • public interface MyAnno extends java.lang.annotation.Annotation{}
  • 属性:接口中的抽象方法

    • 要求:
      1. 属性的返回值类型有下列取值
        • 基本数据类型
        • String
        • 枚举
        • 注解
        • 以上类型的数组
      2. 定义了属性,在使用时需要给属性赋值
        1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
        2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
        3. 枚举赋值时,值使用枚举类.属性名
        4. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}省略
  • 元注解:用于描述注解的注解

    • @Target:描述注解能够作用的位置
      • ElementType取值:
        • TYPE:可以作用于类上
        • METHOD:可以作用于方法上
        • FIELD:可以作用于成员变量上
    • @Retention:描述注解被保留的阶段
      • @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
    • @Documented:描述注解是否被抽取到api文档中
    • @Inherited:描述注解是否被子类继承

3.4、解析注解

  • 在程序使用(解析)注解:获取注解中定义的属性值

    1. 获取注解定义的位置的对象 (Class,Method,Field)

    2. 获取指定的注解

      • getAnntation(Class)

        //其实就是在内存中生成一个该注解接口的子类实现对象
        public class ProImpl implements Pro{
            public String className(){
                return "cn.itcast.annotation.Demo1";
            }
            public String methodName(){
                return "show";
            }
        }
        
    3. 调用注解中的抽象方法获取配置的属性值

3.5、小结

  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用?
    1. 编译器
    2. 给解析程序用
  3. 注解不是程序的一部分,可以理解为注解就是一个标签

4、数据库

4.1、数据库的基本概念

  1. 数据库的英文单词:DataBase 简称 :DB

  2. 什么是数据库?

    • 用于存储和管理数据的仓库。
  3. 数据库的特点:

    1. 持久化存储数据的。其实数据库就是一个文件系统
    2. 方便存储和管理数据
    3. 使用了统一的方式操作数据库 – SQL
  4. 常见的数据库

    • MySQL:开源免费的数据库,小型的数据库,已经被Oracle收购了MySQL6.x版本也开始收费。
    • Oracle:收费的大型数据库,Oracle产品。Oracle收购SUN公司,收购MySQL
    • DB2:IBM公司的数据库产品,收费的。常应用在银行系统中
    • SQLServer:MicroSoft公司收费的中型的数据库。C#、等语言常使用
    • ByBase:已经淡出历史舞台。提供了一个非常专业数据建模的工具PowerDesigner。
    • SQLite:嵌入式的小型数据库,应用在手机端。
    • 常用数据库:MySQLOracle

    在web应用中,使用的最多的就是MySQL数据库,原因如下:

    1. 开源、免费
    2. 功能足够强大,足以应付web应用开发(最高支持千万级别的并发访问)

4.2、MySQL数据库软件

4.2.1、安装

  1. 打开下载的 mysql 安装文件双击解压缩,运行“mysql-5.5.40-win32.msi”。


  1. 选择安装类型,有“Typical(默认)”、“Complete(完全)”、“Custom(用户自定义)”三个选项,选择“Custom”,
    按“next”键继续。

  2. 点选“Browse”,手动指定安装目录。

  3. 填上安装目录,我的是“d:\Program Files (x86)\MySQL\MySQL Server 5.0”,按“OK”继续。

    1. 确认一下先前的设置,如果有误,按“Back”返回重做。按“Install”开始安装。

    2. 正在安装中,请稍候,直到出现下面的界面, 则完成 MYSQL 的安装

    数据库安装好了还需要对数据库进行配置才能使用 MYSQL 的配置

    1. 安装完成了,出现如下界面将进入 mysql 配置向导。

    2. 选择配置方式,“Detailed Configuration(手动精确配置)”、“Standard Configuration(标准配置)”,我
      们选择“Detailed Configuration”,方便熟悉配置过程

    3. 选择服务器类型,“Developer Machine(开发测试类,mysql 占用很少资源)”、“Server Machine(服务
      器类型,mysql 占用较多资源)”、“Dedicated MySQL Server Machine(专门的数据库服务器,mysql 占
      用所有可用资源)”

    4. 选择 mysql 数据库的大致用途,“Multifunctional Database(通用多功能型,好)”、“Transactional
      Database Only(服务器类型,专注于事务处理,一般)”、“Non-Transactional Database Only(非事务
      处理型,较简单,主要做一些监控、记数用,对 MyISAM 数据类型的支持仅限于 non-transactional),按“Next”
      继续。

    1. 选择网站并发连接数,同时连接的数目,“Decision Support(DSS)/OLAP(20个左右)”、“Online Transaction
      Processing(OLTP)(500 个左右)”、“Manual Setting(手动设置,自己输一个数)”

    2. 是否启用 TCP/IP 连接,设定端口,如果不启用,就只能在自己的机器上访问 mysql 数据库了,在这个页
      面上,您还可以选择“启用标准模式”(Enable Strict Mode),这样 MySQL 就不会允许细小的语法错误。
      如果是新手,建议您取消标准模式以减少麻烦。但熟悉 MySQL 以后,尽量使用标准模式,因为它可以降
      低有害数据进入数据库的可能性。按“Next”继续

    3. 就是对 mysql 默认数据库语言编码进行设置(重要),一般选 UTF-8,按 “Next”继续

    4. 选择是否将 mysql 安装为 windows 服务,还可以指定 Service Name(服务标识名称),是否将 mysql 的 bin
      目录加入到 Windows PATH(加入后,就可以直接使用 bin 下的文件,而不用指出目录名,比如连接,
      “mysql.exe -uusername -ppassword;”就可以了,不用指出 mysql.exe 的完整地址,很方便),我这里全部
      打上了勾,Service Name 不变。按“Next”继续。

    5. 询问是否要修改默认 root 用户(超级管理)的密码。“Enable root access from remote machines(是否允
      许 root 用户在其它的机器上登陆,如果要安全,就不要勾上,如果要方便,就勾上它)”。最后“Create
      An Anonymous Account(新建一个匿名用户,匿名用户可以连接数据库,不能操作数据,包括查询)”,
      一般就不用勾了,设置完毕,按“Next”继续。

    6. 确认设置无误,按“Execute”使设置生效,即完成 MYSQL 的安装和配置

注意:设置完毕,按“Finish”后有一个比较常见的错误,就是不能“Start service”,一般出现在以前有安装 mysql
的服务器上,解决的办法,先保证以前安装的 mysql 服务器彻底卸载掉了;不行的话,检查是否按上面一步所说,
之前的密码是否有修改,照上面的操作;如果依然不行,将 mysql 安装目录下的 data 文件夹备份,然后删除,在
安装完成后,将安装生成的 data 文件夹删除,备份的 data 文件夹移回来,再重启 mysql 服务就可以了,这种情况
下,可能需要将数据库检查一下,然后修复一次,防止数据出错。

解决办法:卸载MySQL重装MySQL

4.2.2、卸载

  1. 停止 window 的 MySQL 服务。 找到“控制面板”-> “管理工具”-> “服务”,停止 MySQL 后台服务。

  2. 卸载 MySQL 安装程序。找到“控制面板”-> “程序和功能”,卸载 MySQL 程序。

  3. 删除 MySQL 安装目录下的所有文件。

  4. 删除 c 盘 ProgramDate 目录中关于 MySQL 的目录。路径为:C:\ProgramData\MySQL(是隐藏文件,需要显示
    出来)

4.2.3、配置

  • MySQL服务启动
    1. 手动
    2. cmd–> services.msc 打开服务的窗口
    3. 使用管理员打开cmd
      • net start mysql:启动MySQL的服务
      • net stop mysql:关闭MySQL服务
  • MySQL登录
    1. mysql -uroot -p密码
    2. mysql -hip -uroot -p连接目标的密码
    3. mysql --host=ip --user=root --password=连接目标的密码
  • MySQL退出
    1. exit
    2. quit
  • MySQL目录结构
    1. MySQL安装目录
      • 配置文件 my.ini
    2. MySQL数据目录
      • 几个概念
        • 数据库:文件夹
        • 表:文件
        • 数据

4.3、SQL

  1. 什么是SQL?

    Structured Query Language:结构化查询语言

    其实就是定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为”方言“

  2. SQL通用语法:

    1. SQL语句可以单行或多行书写,以分号结尾。
    2. 可使用空格和缩进来增强语句的可读性
    3. MySQL 数据库的SQL语句不区分大小写,关键字建议使用大写
    4. 3种注释
      • 单行注释:-- 注释内容 或 # 注释内容(MySQL特有)
      • 多行注释:/* 注释 */
  3. SQL分类

    1. DDL(Data Definition Language)数据定义语言

      用来定义数据库对象:数据库,表,列等。关键字:create,drop,alter等

    2. DML(Data Manipulation Language)数据操作语言

      用来对数据库中表的数据进行增删改。关键字:insert,delete,update等

    3. DQL(Data Query Language)数据查询语言

      用来查询数据库中表的记录(数据)。关键字:select,where等

    4. DCL(Data Control Language)数据控制语言(了解)

      用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANT,

      REVOKE等

4.3.1、DDL:操作数据库、表

  1. 操作数据库:CRUD

    1. C(Create):创建
      • 创建数据库:
        • create database 数据库名称;
      • 创建数据库,判断不存在,再创建:
        • create database if not exists 数据库名称;
      • 创建数据库,并制定字符集
        • create database 数据库名称 character set 字符集名;
      • 练习:创建db4数据库,判断是否存在,并制定字符集为gbk
        • create database if not exists db4 character set gbk;
    2. R(Retrieve):查询
      • 查询所有数据库的名称:
        • show databases;
      • 查询某个数据库的字符集:查询某个数据库的创建语句
        • show create database 数据库名称;
    3. U(Update):修改
      • 修改数据库的字符集
        • alter database 数据库名称 character set 字符集名称;
    4. D(Delete):删除
      • 删除数据库
        • drop database 数据库名称;
      • 判断数据库存在,存在再删除
        • drop database if exists 数据库名称;
    5. 使用数据库
      • 查询当前正在使用的数据库名称
        • select database();
      • 使用数据库
        • use 数据库名称;
  2. 操作表

    1. C(Create):创建

      1. 语法:

        create table 表名{
        	列名1 数据类型1,
        	列名2 数据类型2,
        	....
        	列名n 数据类型n
        };
        
        • 注意:最后一列,不需要加逗号(,)
        • 数据库类型:
          1. int:整数类型
            • age int
          2. double:小数类型
            • score double(5,2)
          3. data:日期,只包含年月日,yyyy-MM-dd
          4. datetime:日期,包含年月日时分秒 yyyy-MM-dd HH:mm:ss
          5. timestamp:时间戳类型 包含年月日时分秒 yyyy-MM-dd HH:mm:ss
            • 如果将来不给这个字段赋值,或赋值为null,则默认使用当前的系统时间,来自动赋值
          6. varchar:字符串
            • name varchar(20):姓名最大20个字符
            • zhangsan 8个字符 张三 2个字符
      • 创建表
      create table student(
          id int,
          name varchar(32),
          age  int,
          score double(4,1),
          birthday date,
          insert_time timestamp
      )
      
      • 复制表:
        • create table 表名 like 被复制的表名
    2. R(Retrieve):查询

      • 查询某个数据库中所有的表名称
        • show tables;
      • 查询表结构
        • desc 表名
    3. U(Update):修改

      1. 修改表名

        alter table 表名 rename to 新表名;

      2. 修改表的字符集

        alter table 表名 character set 字符集名称;

      3. 添加一列

        alter table 表名 add 列名 数据类型;

      4. 修改列名称 类型

        列名称和列类型全部修改

        alter table 表名 change 列名 新列名 新数据类型;

        只修改列的类型

        alter table 表名 modify 列名 新数据类型;

      5. 删除列

        alter table 表名 drop 列名;

    4. D(Delete):删除

      • drop table 表名
      • drop table if exists 表名

4.3.2、DML:增删改表中数据

  1. 添加数据:

    • 语法:

      • insert into 表名(列名1,列名2,...列名n) values(值1,值2,...值n);
    • 注意:

      1. 列名和值要一一对应。

      2. 如果表名后,不定义列名,则默认给所有列添加值

        insert into 表名 values(值1,值2,...值n);

      3. 除了数字类型,其他类型需要使用引号(单双都可以)引起来

  2. 删除数据

    • 语法:
      • delete from 表名 [where 条件]
    • 注意:
      1. 如果不加条件,则删除表中所有记录
      2. 如果要删除所有记录
        1. delete from 表名; – 不推荐使用。有多少条记录就会执行多少次删除操作
        2. TRUNCATE TABLE 表名;– 推荐使用,效率更高,先删除表,然后再创建一张一样的表。
  3. 修改数据

    • 语法:
      • update 表名 set 列名1 = 值1, 列名2 = 值2,...[where 条件]
    • 注意:
      • 如果不加任何条件,则会将表中所有记录全部修改

4.3.3、DQL:查询表中的记录

  • select * from 表名;

    1. 语法:
  `select`

     ​	` 字段列表` 

     `from`

     ​	` 表名列表`

     ` where`

     ​	` 条件列表`

     ` group by`

     ​	` 分组字段`

     ` having`

     ​	` 分组之后的条件`

     ` order by`

     ​	` 排序` 

     ` limit` 

     ​	` 分页限定`
  1. 基础查询

    1. 多个字段的查询

      select 字段名1,字段名2... from 表名;

      • 注意:
        • 如果查询所有字段,则使用*来替代字段列表。
    2. 去除重复

      • distinct
    3. 计算列

      • 一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算)
      • ifnull(表达式1,表达式2):null参与的运算,计算结果都为null
        • 表达式1:哪个字段需要判断是否为null
        • 如果该字段为null后的替换值
    4. 起别名:

      • as:as也可以省略
  2. 条件查询

    1. where 子句后跟条件

    2. 运算符

      • >、<、<=、>=、=、<>

      • BETWEEN...AND

      • IN(集合)

      • LIKE:模糊查询

        • 占位符:
          • _:单个任意字符
          • %:多个任意字符
      • IS NULL

      • and 或 &&

      • or 或 ||

      • not 或 !

        -- 查询年龄等于20岁
        select * from student where age = 20;
        
        -- 查询年龄不等于20岁
        select * from student where age != 20;
        select * from student where age <> 20;
        
        -- 查询年龄大于等于20小于等于30
        select * from student where age>=20 && age<=30;
        select * from student where age>=20 and age<=30;
        select * from student where age between 20 and 30;
        
        -- 查询年龄20岁,18岁,25岁的信息
        select * from student where age = 20 or age = 18 or age = 25;
        select * from student where age in(20,18,25);
        
        -- 查询英语成绩为null
        select * from student where english = null;-- 不对的
        -- null值不能使用 = (!=)判断
        select * from student where english is null;
        
        -- 查询英语成绩不为null
        select * from student where english is not null;
        
        -- 查询姓马的有哪些?like
        select * from student where name like '马%';
        -- 查询姓名第二个字是化的人
        
        select * from student where name like "_化%";
        
        -- 查询名字是3个字的人
        select * from student where name like "___";
        
        -- 查询姓名中包含德的人
        select * from student where name like "%德%";
        

4.3.4、DQL:查询语句

4.3.4.1、排序查询
  • 语法:order by子句
    • order by 排序字段1 排序方式1, 排序字段2 排序方式2…
  • 排序方式:
    • ASC:升序,默认的
    • DESC:降序
  • 注意:
    • 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件
4.3.4.2、聚合函数
  • 聚合函数:将一列数据作为一个整体,进行纵向的计算。

    1. count:计算个数
      1. 一般选择没有null值的列:主键
      2. count(*)
    2. max:计算最大值
    3. min:计算最小值
    4. sum:计算和
    5. avg:计算平均值
    • 注意:聚合函数的计算,排除null值。

      解决方案:

      1. 选择没有null值的列进行计算
      2. IFNULL函数
4.3.4.3、分组查询
  1. 语法:grop by 分组字段;
  2. 注意:
    1. 分组之后查询的字段:分组字段、聚合函数
    2. where 和 having 的区别?
      1. where 在分组之前进行限定,如果不满足条件,则不参与分组。having在分组之后进行限定,如果不满足结果,则不会被查询出来
      2. where后不可以跟聚合函数,having可以进行聚合函数的判断。
4.3.4.4、分页查询
  1. 语法:limit开始的索引,每页查询的条数;

  2. 公式:开始的索引 = (当前的页码 - 1) * 每页显示的条数

    -- 每页显示3条记录
    select * from student limit 0,3; -- 第一页
    select * from student limit 3,3; -- 第二页
    select * from student limit 6,3; -- 第三页
    
  3. limit 是一个MySQL"方言"

4.4、约束

  • 概念:对表中的数据进行限定,保证数据的正确性、有效性和完整性

  • 分类:

    1. 主键约束:primary key
    2. 非空约束:not null
    3. 唯一约束:unique
    4. 外键约束:foreign key
  • 非空约束:not null

    1. 创建表时添加约束

      create table stu(
      	id int,
          name varchar(20) not null -- name为非空
      );
      
    2. 创建表完后,添加非空约束

      alter table stu modify name varchar(20) not null;
      
    3. 删除name的非空约束

      alter table stu modify name varchar(20);
      
  • 唯一约束:unique,某一列的值不能重复

    1. 注意:

      • 唯一约束可以有null值,但是只能有一条记录为null
    2. 在创建表时,条件唯一约束

      create table stu(
          id int,
          phone_number varchar(20) unique -- 手机号
      );
      
    3. 删除唯一约束

      alter table stu drop index phone_number;
      
    4. 在表创建完后,添加唯一约束

      alter table stu modify phone_number varchar(20) unique;
      
  • 主键约束:primary key。

    1. 注意:

      1. 含义:非空且唯一
      2. 一张表只能有一个字段为主键
      3. 主键就是表中记录的唯一标识
    2. 在创建表时,添加主键约束

      create table stu(
      	id int primary key,--给id添加主键约束
          name varchar(20)
      )
      
    3. 删除主键

      -- 错误 alter table stu modify id int;
      alter table stu drop primary key;
      
    4. 创建完表后,添加主键

      alter table stu modify id int primary key
      
    5. 自动增长:

      1. 概念:如果某一列是数值类型的,使用auto_increment可以来完成值得自动增长

      2. 在创建表时,添加主键约束,并且完成主键自增长

        create table stu(
        	id int primary key auto_increment,-- 给id添加主键约束
            name varchar(20)
        )
        
      3. 删除自动增长

        alter table stu modify id int;
        
      4. 添加自动增长

        alter table stu modify id int auto_increment;
        
  • 外键约束:foreign key,让表于表产生关系,从而保证数据的正确性

    1. 在创建表时,可以添加外键

      • 语法:

        create table 表名(
        	...
            外键列
            constraint 外键名称 foreign key (外键列名称) references 主表名称(主表列名称)
        )
        
    2. 删除外键

      alter table 表名 drop foreign key 外键名称;
      
    3. 创建表之后,添加外键

      alter table 表名 add constraint 外键名称 foreign key (外键字段名称) references 主表名称(主表列名称);
      
    4. 级联操作

      1. 添加级联操作

        语法:

        alter table 表名 add constraint 外键名称
        foreign key (外键字段名称) references 主表名称(主表列名称) on update cascade on delete cascade;
        
      2. 分类:

        1. 级联更新:on update cascade当主表的主键发生更新时所关联的外键也会发生更新
        2. 级联删除:on delete cascade当主表的主键被删除时所关联的外键也被删除

4.5、数据库的设计

4.5.1、多表之间的关系

  1. 分类:

    1. 一对一(了解):

      • 如:人和身份证
      • 分析:一个人只有一个身份证,一个身份证只能对应一个人
    2. 一对多(多对一):

      • 如:部门和员工

      • 分析:一个部门有多个员工,一个员工只能对应一个部门

    3. 多对多:

      • 如:学生和课程

      • 分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择

  2. 实现关系:

    1. 一对多(多对一):

      • 如:部门和员工

      • 实现方式:在多的一方建立外键,指向一的一方的主键。

    2. 多对多:

      • 如:学生和课程

      • 实现方式:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键

    3. 一对一(了解):

      • 如:人和身份证

      • 实现方式:一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键

  3. 案例:

    -- 创建旅游线路分类表 tab_category
    -- cid 旅游线路分类主键,自动增长
    -- cname 旅游线路分类名称非空,唯一,字符串 100
    create table tab_category (
    cid int primary key auto_increment,
    cname varchar(100) not null unique
    )
    -- 添加旅游线路分类数据:
    insert into tab_category (cname) values ('周边游'), ('出境游'), ('国内游'), ('港澳游');
    select * from tab_category;
    -- 创建旅游线路表 tab_route
    /*
    rid 旅游线路主键,自动增长
    rname 旅游线路名称非空,唯一,字符串 100
    price 价格
    rdate 上架时间,日期类型
    cid 外键,所属分类
    */
    create table tab_route(
    rid int primary key auto_increment,
    rname varchar(100) not null unique,
    price double,
    rdate date,
    cid int,
    foreign key (cid) references tab_category(cid)
    )
    -- 添加旅游线路数据
    INSERT INTO tab_route VALUES
    (NULL, '【厦门+鼓浪屿+南普陀寺+曾厝垵 高铁 3 天 惠贵团】尝味友鸭面线 住 1 晚鼓浪屿', 1499,
    '2018-01-27', 1),
    (NULL, '【浪漫桂林 阳朔西街高铁 3 天纯玩 高级团】城徽象鼻山 兴坪漓江 西山公园', 699, '2018-02-
    22', 3),
    (NULL, '【爆款¥1699 秒杀】泰国 曼谷 芭堤雅 金沙岛 杜拉拉水上市场 双飞六天【含送签费 泰风情 广州
    往返 特价团】', 1699, '2018-01-27', 2),
    23 / 26
    (NULL, '【经典•狮航 ¥2399 秒杀】巴厘岛双飞五天 抵玩【广州往返 特价团】', 2399, '2017-12-23',
    2),
    (NULL, '香港迪士尼乐园自由行 2 天【永东跨境巴士广东至迪士尼去程交通+迪士尼一日门票+香港如心海景酒店
    暨会议中心标准房 1 晚住宿】', 799, '2018-04-10', 4);
    select * from tab_route;
    
    /*
    创建用户表 tab_user
    uid 用户主键,自增长
    username 用户名长度 100,唯一,非空
    password 密码长度 30,非空
    name 真实姓名长度 100
    birthday 生日
    sex 性别,定长字符串 1
    telephone 手机号,字符串 11
    email 邮箱,字符串长度 100
    */
    create table tab_user (
    uid int primary key auto_increment,
    username varchar(100) unique not null,
    password varchar(30) not null,
    name varchar(100),
    birthday date,
    sex char(1) default '男',
    telephone varchar(11),
    email varchar(100)
    )
    -- 添加用户数据
    INSERT INTO tab_user VALUES
    (NULL, 'cz110', 123456, '老王', '1977-07-07', '男', '13888888888', '66666@qq'),
    (NULL, 'cz119', 654321, '小王', '1999-09-09', '男', '13999999999', '99999@qq');
    select * from tab_user;
    /*
    创建收藏表 tab_favorite
    rid 旅游线路 id,外键
    date 收藏时间
    uid 用户 id,外键
    rid 和 uid 不能重复,设置复合主键,同一个用户不能收藏同一个线路两次
    */
    create table tab_favorite (
    rid int,
    date datetime,
    uid int,
    -- 创建复合主键
    primary key(rid,uid),
    foreign key (rid) references tab_route(rid),
    foreign key(uid) references tab_user(uid)
    )
    -- 增加收藏表数据
    INSERT INTO tab_favorite VALUES
    (1, '2018-01-01', 1), -- 老王选择厦门
    (2, '2018-02-11', 1), -- 老王选择桂林
    (3, '2018-03-21', 1), -- 老王选择泰国
    (2, '2018-04-21', 2), -- 小王选择桂林
    (3, '2018-05-08', 2), -- 小王选择泰国
    (5, '2018-06-02', 2); -- 小王选择迪士尼
    select * from tab_favorite
    

4.5.2、数据库设计的范式

  • 概念:设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须先遵循前边的所有范式要求

    ​ 设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次和规范,越高的范式数据库冗余越小。

    ​ 目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称为完美范式)。

  • 分类:

    1. 第一范式(1NF):每一列都是不可分割的原子数据项

    2. 第二范式(2NF):在1NF的基础上,非码属性必须完成依赖于码(在1NF基础上消除非主属性对主码的部分函数依赖)

      • 几个概念:

        1. 函数依赖:A–>B,如果通过A属性(属性组)的值,可以确定唯一B属性的值。则称B依赖于A

          ​ 例如:学号–>姓名。 (学号,课程名称) --> 分数

        2. 完全函数依赖:A–>B,如果A是一个属性组,则B属性值得确定需要依赖于A属性组中所有的属性值

          ​ 例如:(学号,课程名称) --> 分数

        3. 部分函数依赖:A–>B,如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可

          ​ 例如:(学号,课程名称)–>姓名

        4. 传递函数依赖:A–>B,B–>C 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称C传递函数依赖于A

          ​ 例如:学号–>系名,系名–>系主任

        5. 码:如果在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码

          ​ 例如:该表中码为:(学号,课程名称)

          • 主属性:码属性组中的所有属性
          • 非主属性:除过码属性组的属性
    3. 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其他非属性(在2NF基础消除传递依赖)

4.6、数据库的备份和还原

  1. 命令行:
    • 语法:
      • 备份:mysqldump -u用户名 -p密码 数据库名称 > 保存的路径
      • 还原:
        1. 登录数据库
        2. 创建数据库
        3. 使用数据库
        4. 执行文件。 source 文件路径
  2. 图形化工具:

4.7、多表查询

  • 查询语句:

    select

    列名列表

    from

    表名列表

    where ....

  • 准备sql

    # 创建部门表
    create table dept(
    id int primary key auto_increment,
    name varchar(20)
    )
    insert into dept (name) values ('开发部'),('市场部'),('财务部');
    # 创建员工表
    create table emp (
    id int primary key auto_increment,
    name varchar(10),
    gender char(1), -- 性别
    salary double, -- 工资
    join_date date, -- 入职日期
    dept_id int,
    foreign key (dept_id) references dept(id) -- 外键,关联部门表(部门表的主键)
    )
    insert into emp(name,gender,salary,join_date,dept_id) values('孙悟空','男
    ',7200,'2013-02-24',1);
    insert into emp(name,gender,salary,join_date,dept_id) values('猪八戒','男
    ',3600,'2010-12-02',2);
    insert into emp(name,gender,salary,join_date,dept_id) values('唐僧','男',9000,'2008-
    08-08',2);
    insert into emp(name,gender,salary,join_date,dept_id) values('白骨精','女
    ',5000,'2015-10-07',3);
    insert into emp(name,gender,salary,join_date,dept_id) values('蜘蛛精','女
    ',4500,'2011-03-14',1)
    
  • 笛卡尔积:

    • 有两个集合A,B取这两个集合的所有组成情况
    • 要完成多表查询,需要消除无用的数据
  • 多表查询的分类:

    1. 内连接查询:

      1. 隐式内连接:使用where条件消除无用数据

        -- 查询所有员工信息和对应的部门信息
        select * from emp,dept where emp.dept_id = dept.id;
        -- 查询员工的名称,性别。部门表的名称
        select emp.name,emp.gender,dept.name from emp,dept where emp.dept_id = dept.id;
        select 
        	t1.name,-- 员工表的姓名
        	t1.gender,-- 员工表的性别
        	t2.name -- 部门表的名称
        from
        	emp t1,
        	dept t2
        where 
        	t1.dept_id = t2.id;
        
      2. 显式内连接:

        语法:select 字段列表 from 表名1 [inner] join 表名2 on 条件
        例如:
        	select * from emp inner join dept on emp.dept_id = dept.id;
        	select * from emp join dept on emp.dept_id = dept.id;
        
      3. 内连接查询:

        1. 从哪些表中查询数据
        2. 条件是什么
        3. 查询哪些字段
    2. 外连接查询:

      1. 左外连接:

        • 语法:select 字段列表 from 表1 left [outer] join 表2 on 条件;

        • 查询的是左表所有数据以及其交集部分

          -- 查询所有员工信息,如果员工有部门,则查询部门名称,没有部门,则不显示部门名称
          select t1.*,t2.name from emp t1 left join deft t2 on t1.dept_id = t2.id
          
      2. 右外连接:

        • 语法:select 字段列表 from 表1 rigth [outer] join 表2 on 条件;

        • 查询的是右表所有数据以及其交集部分

          select * from dept t2 right join emp t1 on t1.dept_id = t2.id;
          
    3. 子查询:

      • 概述:查询中嵌套查询,称嵌套查询为子查询

        -- 1 查询最高的工资是多少 9000
        select max(salary) from emp;
        -- 2 查询员工信息,并且工资等于9000的
        select * from emp where emp.salary = 9000;
        -- 写成一条sql就完成这个操作。子查询
        select * from emp.salary = (select * from emp where emp.salary = 9000);
        
      • 子查询不同情况

        1. 子查询的结果是单行单列的:

          • 子查询可以作为条件,使用运算符去判断。运算符:> >= < <= =
          -- 查询员工工资下雨平均工资的人
          select * from emp where emp.salary < (select avg(salary) from emp);
          
        2. 子查询的结果是多行单列的:

          • 子查询可以作为条件,使用运算符in来判断
          -- 查询‘财务部’和‘市场部’所有的员工信息
          select id from dept where name in ('财务部','市场部');
          select * from emp where dept_id in (2,3);
          -- 子查询
          select * from emp where dept_id in (select id from dept where name in ('财务部','市场部'));
          
        3. 子查询的结果是多行多列的:

          • 子查询可以作为一张虚拟表参与查询
          -- 查询员工入职日期是2011-11-11日之后的员工信息和部门信息
          -- 子查询
          select * from dept t1,(select * from emp where emp.join_date > '2011-11-11') t2 where t1.id = t2.dept_id;
          
          -- 普通内连接
          select * from emp t1,dept t2 where t1.dept_id = t2.id and t1.join_date > '2011-11-11' 
          

4.8、事务

4.8.1、事务的基本介绍

  1. 概念:

    • 如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。

  2. 操作:

    1. 开启事务:statrt transaction;
    2. 回滚:rollback;
    3. 提交:commit;
    create table account(
    	id int primary key auto_increment,
        name varchar(10),
        balabce double
    );
    -- 添加数据
    insert into account (name,balance) values ('zhangsan',1000),('lisi',1000);
    
    select * from account;
    update account set balance = 1000;
    -- 张三给李四转账500元
    -- 0.开启事务
    start transaction;
    -- 1.张三账户 -500
    update account set balance = balance - 500 where name = 'zhangsan';
    -- 2.李四账户 +500
    -- 出错了....
    update account set balance = balance + 500 where name = 'lisi';
    -- 发现执行没有问题,提交事务
    commit;
    -- 发现出问题了,回滚事务
    rollback;
    
  3. MySQL数据库中事务默认自动提交

    • 事务提交的两种方式:
      • 自动提交:
        • MySQL就是自动提交的
        • 一条DML(增删改)语句会自动提交一次事务
      • 手动提交
        • Oracle数据库默认是手动提交事务
        • 需要先开启事务,再提交
    • 修改事务的默认提交方式
      • 查看事务的默认提交方式:select @@autocommit; -- 1代表自动提交 0代表手动提交
      • 修改默认提交方式:set @@autocommit = 0;

4.8.2、事务的四大特征

  1. 原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败。
  2. 持久性:当事务提交或回滚后,数据库会持久化的保存数据
  3. 隔离性:多个事务之间,相互独立
  4. 一致性:事务操作前后,数据总量不变

4.8.3、事务的隔离级别(了解)

  • 概念:多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题

  • 存在问题:

    1. 脏读:一个事务,读取到另一个事务中没有提交的数据
    2. 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样
    3. 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改
  • 隔离级别:

    1. read uncommitted:读未提交
      • 产生的问题:脏读、不可重复读、幻读
    2. read committed:读已提交(Oracle)
      • 产生的问题:不可重复读、幻读
    3. repeatable read:可重复读(MySQL默认)
      • 产生的问题:幻读
    4. serializable:串行化
      • 可以解决所有的问题
    • 注意:隔离级别从小到大安全性越来越高,但是效率越来越低

    • 数据库查询隔离级别:

      select @@tx_isolation;
      
    • 数据库设置隔离级别:

      set global transaction isolation level 级别字符串;
      
    • 设置事务隔离级别,需要退出 MySQL 再重新登录才能看到隔离级别的变化

  • 脏读的演示

    将数据进行恢复:UPDATE account SET balance = 1000;

    1. 打开 A 窗口登录 MySQL,设置全局的隔离级别为最低

      mysql -uroot -proot
      set global transaction isolation level read uncommitted;
      

    2. 打开B窗口,AB窗口都开启事务

      use day23;
      start transaction;
      
    3. A窗口更新两个人的账户数据,未提交

      update account set balance=balance-500 where id=1;
      update account set balance=balance+500 where id=2;
      

    4. B窗口查询账户

      select * from account;
      

    5. A窗口回滚

      rollback;
      

    6. B窗口查询账户,钱没了

      ​ 脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入500块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四再查看钱没了

      解决脏读的问题:将全局的隔离级别进行提高

      ​ 将数据进行恢复:

      UPDATE account SET balance = 1000;
      
    7. 在A窗口设置全局的隔离级别为read committed

      set global transaction isolation level read committed;
      

      B窗口退出MySQL,B窗口再进入MySQL

      AB窗口有同时开启事务

    8. A更新2个人的账户,未提交

      update account set balance=balance-500 where id=1;
      update account set balance=balance+500 where id=2
      

    9. B窗口查询账户

      A窗口commit提交事务

    10. B窗口查看账户

      结论:read committed 的方式可以避免脏读的发生

  • 不可重复读的演示

    将数据进行恢复:UPDATE account SET balance = 1000;

    1. 开启A窗口

      set global transaction isolation level read committed;
      

    2. 开启B窗口,在B窗口开启事务

      start transaction;
      select * from account
      

    3. 在A窗口开启事务,并更新数据

      start transaction;
      update account set balance=balance+500 where id=1;
      commit;
      

    4. B窗口查询

      select * from account
      

      ​ 两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。很多人认为这种情况就对了,无须困惑,

      当然是后面为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客

      户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作

      人员就不知道以哪个为准了。

      解决不可重复读的问题:

      ​ 将全局的隔离级别进行提升为:repeatable read

      ​ 将数据进行恢复:

      UPDATE account SET balance = 1000;
      
    5. A 窗口设置隔离级别为:repeatable read

      set global transaction isolation level repeatable read;
      

    6. B 窗口退出 MySQL,B 窗口再进入 MySQL

      start transaction;
      select * from account;
      

    7. A 窗口更新数据

      start transaction;
      update account set balance=balance+500 where id=1;
      commit
      

    8. B 窗口查询

      select * from account
      

      结论:同一个事务中为了保证多次查询数据一致,必须使用 repeatable read 隔离级别

  • 幻读的演示

    在 MySQL 中无法看到幻读的效果

    但我们可以将事务隔离级别设置到最高,以挡住幻读的发生 将数据进行恢复:

    UPDATE account SET balance = 1000;
    
    1. 开启A窗口

      set global transaction isolation level serializable; -- 设置隔离级别为最高
      

    2. A 窗口退出 MySQL,A 窗口重新登录 MySQL

      start transaction;
      select count(*) from account
      

    3. 再开启 B 窗口,登录 MySQL

    4. 在 B 窗口中开启事务,添加一条记录

      start transaction; -- 开启事务
      insert into account (name,balance) values ('LaoWang', 500);
      

    5. 在 A 窗口中 commit 提交事务,B 窗口中 insert 语句会在 A 窗口事务提交后立马运行

    6. 在 A 窗口中接着查询,发现数据不变

      select count(*) from account
      

    7. B 窗口中 commit 提交当前事务

    8. A 窗口就能看到最新的数据

      ​ 结论:使用 serializable 隔离级别,一个事务没有执行完,其他事务的 SQL 执行不了,可以挡住幻读

4.9、DCL

  • SQL分类

    1. DDL:操作数据库和表

    2. DML:增删改表中的数据

    3. DQL:查询表中数据

    4. DCL:管理用户,授权

  • DBA:数据库管理员

  • DCL:管理用户,授权

    1. 管理用户

      1. 添加用户:

        语法: create user '用户名'@'主机名' identified by '密码';
        
      2. 删除用户:

        语法:drop user '用户名'@'主机名';
        
      3. 修改用户密码:

        update user set password = password('新密码') where user = '用户名';
        update user set password = password('abc') where user = 'lisi';
        set password for '用户名'@'主机名' = password('新密码');
        set password for 'root'@'localhost' = password('123');
        
        • mysql中忘记了root用户的密码?
          1. cmd --> net stop mysql 停止mysql服务
            • 需要管理员运行该cmd
          2. 使用无验证方式启动mysql服务
          3. 打开新的cmd窗口,直接输入mysql命令,敲回车。就可以登录成功
          4. use mysql;
          5. update user set password = password ('你的新密码') where user = 'root';
          6. 关闭两个窗口
          7. 打开任何管理器,手动结束mysqld.exe的进程
          8. 启动mysql服务
          9. 使用新密码登录。
      4. 查询用户:

        -- 1.切换到MySQL数据库
        use mysql;
        -- 2.查询user表
        select * from user;
        
        • 通配符:% 表示可以在任意主机使用用户登录数据库
    2. 权限管理:

      1. 查询权限:

        -- 查询权限
        show grants for '用户名'@'主机名';
        show grants for 'lisi'@'%';
        
      2. 授予权限:

        -- 授予权限
        grant 权限列表 on 数据库名.表名 to '用户名'@'主机名';
        -- 给张三用户授予所有权限,在任意数据库任意表上
        grant all on *.* to 'zhangsan'@'localhost';
        
      3. 撤销权限:

        -- 撤销权限:
        revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名';
        revoke update on db3.account from 'lisi'@'%';
        

5、JDBC

5.1、JDBC基本概念

  • 概念:Java DataBase Connectivity Java 数据库连接,Java语言操作数据库
  • JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类

5.2、JDBC快速入门

  • 步骤:
    1. 导入驱动jar包 mysql - connector-java-5.1.37-bin.jar
      1. 组织mysql - connector-java-5.1.37-bin.jar到项目的libs目录下
      2. 右键–>Add As Library
    2. 注册驱动
    3. 获取数据库连接对象 Connection
    4. 定义sql
    5. 获取执行sql语句的对象 Statement
    6. 执行sql,接受返回结果
    7. 处理结果
    8. 释放资源
public class JDBCDome {
    public static void main(String[] args) throws Exception {
        //注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //创建连接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd_2", "root", "root");
        //创建执行sql语句对象
        Statement statement = connection.createStatement();
        //创建sql语句
        String sql = "insert into student values(null,'王五',1)";
        //执行sql语句
        int execute = statement.executeUpdate(sql);
        System.out.println(execute);
        //释放资源
        statement.close();
        connection.close();
    }
}

5.3、详解各个对象

  1. DriverManager:驱动管理对象

    • 功能:

      1. 注册驱动:告诉程序该使用哪一个数据库驱动jar

        static void registerDriver(Driver driver) //注册与给定的驱动程序DriverManager
        //写代码使用:Class.forName("com.mysql.jdbc.Driver");
        //通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块
        static {
            try{
                java.sql.DriverManager.registerDriver(new Driver());
            } catch(SQLException E){
                throw new RuntimeException("Can't register driver!");
            }
        }
        

        注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。

      2. 获取数据库连接

        • 方法:static Connection getConnection(String url,String user,String password)
        • 参数:
          • url:指定链接的路径
            • 语法:jdbc:mysql://IP地址(域名):端口号/数据库名称
            • 例子:jdbc:mysql://localhost:3306/db3
            • 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
          • user:用户名
          • password:密码
  2. Connection:数据库连接对象

    1. 功能:
      1. 获取执行sql的对象
        • Statement createStatement()
        • PreparedStatement prepareStatement(String sql)
      2. 管理事务
        • 开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务
        • 提交事务:commit()
        • 回滚事务:rollback()
  3. Statement:执行sql的对象

    1. 执行sql

      1. boolean execute(String sql):可以执行任意的sql(了解)

      2. int executeUpdate(String sql):执行DML(insert,update、delete)语句、DDL(create、alter、drop)语句

        • 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败
      3. ResultSet executeQuery(String sql):执行DQL(select)语句

      4. 练习:

        package com.xh.JDBC;
        
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;
        import java.sql.Statement;
        
        public class JDBCDome2 {
            public static void main(String[] args)  {
                Connection connection = null;
                Statement statement = null;
                try {
                    //注册驱动
                    Class.forName("com.mysql.jdbc.Driver");
                    //创建连接
                    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd_2", "root", "root");
                    //创建执行sql对象
                    statement = connection.createStatement();
                    //定义sql语句
                    String sql = "insert into student values(2,'李四',1)";
                    //执行sql
                    int i = statement.executeUpdate(sql);
                    System.out.println(i);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }catch (SQLException throwables) {
                throwables.printStackTrace();
                }finally {
                    //释放资源
                    if (statement!=null){
                        try {
                            statement.close();
                        } catch (SQLException throwables) {
                            throwables.printStackTrace();
                        }
                    }
                    if (connection!=null){
                        try {
                            connection.close();
                        } catch (SQLException throwables) {
                            throwables.printStackTrace();
                        }
                    }
                }
            }
        }
        
  4. ResultSet:结果集对象,封装查询结果

    • next():游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true

    • getXxx(参数):获取数据

      • Xxx:代表数据类型 如:int getInt()String getString()
      • 参数:
        1. int:代表列的编号,从1开始 如:getString(1)
        2. String:代表列名称。如:getDouble("balance")
    • 注意:

      • 使用步骤:

        1. 游标向下移动一行
        2. 判断是否有数据
        3. 获取数据
        //循环判断游标是否是最后一行
        while(rs.next()){
            //获取数据
            //6.2获取数据
            int id = rs.getInt(1);
            String name = rs.getString("name");
            double balance = rs.getDouble(3);
            
            System.out.println(id + "---" + name + "---" + balance)
        }
        
      • 练习:

        • 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回
          1. 定义Emp类
          2. 定义方法 public List<Emp> findAll(){}
          3. 实现方法 select * from emp;
  5. PreparedStatement:执行sql的对象

    1. SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
      1. 输入用户随便,输入密码:a' or 'a' = 'a
      2. sql:select * from user where username = "faijd" and password = 'a' or 'a' = 'a'
    2. 解决sql注入问题:使用PreparedStatement对象来解决
    3. 预编译的SQL:参数使用?作为占位符
    4. 步骤:
      1. 导入驱动jar包 mysql - connector - java - 5.1.37 - bin.jar
      2. 注册驱动
      3. 获取数据库连接对象 Connection
      4. 定义sql
        • 注意:sql的参数使用?作为占位符。如:select * from user where username = ? and password = ?;
      5. 获取执行sql语句的对象 PreparedStatement Connectin.prepareStatement(String sql)
      6. 给?赋值:
        • 方法:setXxx(参数1,参数2)
          • 参数1: ?的位置编号 从1 开始
          • 参数2: ?的值
      7. 执行sql,接受返回结果,不需要传递sql语句
      8. 处理结果
      9. 释放资源
    5. 注意:后期都会使用PreparedStatement来完成增删改查的所有操作
      1. 可以防止SQL注入
      2. 效率更高

5.4、抽取JDBC工具类:JDBCUtils

  • 目的:简化书写

  • 分析:

    1. 注册驱动也抽取

    2. 抽取一个方法获取连接对象

      • 需求:不想传递参数(麻烦),还得保证工具类的通用性

      • 解决:配置文件

        url = jdbc:mysql://localhost:3306/bd_2
        user = root
        password = root
        driver = com.mysql.jdbc.Driver
        
    3. 抽取一个方法释放资源

package com.xh.Utils;

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

/*
    JDBC工具类
 */
public class JdbcUtils {
    //定义变量
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    //静态代码块初始
    static {
        //创建Properties对象
        Properties pro = new Properties();
        //读取配置文件
        ClassLoader classLoader = JdbcUtils.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("jdbc.properties");
        try {
            pro.load(is);
            //获取集合中的值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");
            System.out.println(url);
            //注册驱动
            try {
                Class.forName(driver);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //创建方法获取连接对象
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //创建方法释放资源
    public static void close(Statement statement,Connection connection){
        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    //释放资源方法
    public static void close(ResultSet resultSet,Statement statement, Connection connection){
        if (resultSet!=null){
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}
  • 练习:

    • 需求:

      1. 通过键盘录入用户名和密码

      2. 判断用户是否登录成功

        select * from user where username = "" and password = "";
        -- 如果这个sql有查询结果,则成功,反之,则失败
        
    • 步骤:

      1. 创建数据库表 user

        create table user(
        	id int primary key auto_increment,
            username varchar(32),
            password varchar(32)
        );
        insert into user values(null,'zhangsan','123');
        insert into user values(null,'lisi','234');
        

5.5、JDBC控制事务

  1. 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
  2. 操作:
    1. 开启事务
    2. 提交事务
    3. 回滚事务
  3. 使用Connection对象来管理事务
    • 开启事务:setAutoCommit(boolean autoCommit):调用该方法设置参数为false,即开启事务
      • 在执行sql之前开启事务
    • 提交事务:commit()
      • 当所有sql都执行完提交事务
    • 回滚事务:rollback()
      • 在catch(出现异常)中回滚事务

6、数据库连接池

6.1、数据库连接池概述

  • 概念:其实就是一个容器(集合),存放数据库连接的容器。

    ​ 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。

  • 好吃:

    1. 节约资源
    2. 用户访问高效

6.2、连接池实现

实现:

  1. 标准接口:DataSource javax.sql包下的
    1. 方法:
      • 获取连接:getConnection()
        • 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
    2. 一般我们不去实现它,有数据库厂商来实现
      1. C3P0:数据库连接池技术
      2. Druid:数据库连接池实现技术,由阿里巴巴提供的

6.3、C3P0连接实现

  • 步骤
    1. 导入jar包(两个)c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar
      • 不要忘记导入数据库驱动jar包
    2. 定义配置文件
      • 名称:c3p0.properties 或者 c3po-config.xml
      • 路径:直接将文件放在src目录下即可
    3. 创建核心对象 数据库连接池对象ComboPooledDataSource
    4. 获取连接:getConnection

6.4、Druid实现

Druid:数据库连接池实现技术,由阿里巴巴提供的

  1. 步骤:

    1. 导入jar包 druid-1.0.9.jar

    2. 定义配置文件:

      • 是properties形式的

      • 可以叫任意名称,可以放在任意目录下

    3. 加载配置文件。Properties

    4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory

    5. 获取连接:getConnection

  2. 定义工具类

    1. 定义一个类 JDBCUtils
    2. 提供静态代码块加载配置文件,初始化连接池对象
    3. 提供方法
      1. 获取连接方法:通过数据库连接池获取连接
      2. 释放资源
      3. 获取连接池的方法
driverClassName=com.mysql.jdbc.Driver
#URL连接数据库的URL,其中travel(以下面例子来说)为连接的数据库,后面的参数可不改但不删
url=jdbc:mysql://localhost:3306/bd_1
characterEncoding=utf-8
#安装mysql时候设置的用户与密码
username=root
password=root
#初始化物理连接的个数
initialSize=5
#最大连接池数量
maxActive=10
#获取连接时最大等待时间
maxWait=3000
package com.xh.druid;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DruidUtils {
    private static DataSource ds;
    static {
        //创建集合对象
        Properties properties = new Properties();
        //读取配置文件
        InputStream resourceAsStream = DruidUtils.class.getClassLoader().getResourceAsStream("Druid.properties");
        try {
            properties.load(resourceAsStream);
            try {
                ds = DruidDataSourceFactory.createDataSource(properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //获取连接方法
    public static Connection getConnection()  {
        Connection connection = null;
        try {
            connection = ds.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //释放资源方法
    public static void close(Statement statement,Connection connection){
        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    public static void close(ResultSet rs,Statement statement, Connection connection){
        if (rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    //获取连接池对象
    public static DataSource getDataSource(){
        return ds;
    }
}

7、Spring JDBC:JDBC Template

7.1、JDBC Template介绍

  • Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
  • 步骤:
    1. 导入jar包
    2. 创建JdbcTemplate对象。依赖于数据源DataSource
      • JdbcTemplate template = new JdbcTemplate(ds);
    3. 调用JdbcTemplate的方法来完成CRUD的操作
      • update():执行DML语句。增、删、改语句
      • queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合
        • 注意:这个方法查询的结果集长度只能是1
      • queryForList():查询结果将结果集封装为list集合
        • 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
      • query():查询结果将结果集封装为JavaBean对象
        • query的参数:RowMapper
          • 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装
          • new BeanPropertyRowMapper<类型>(类型.class)
      • queryForObject():查询结果,将结果封装为对象
        • 一般用于聚合函数的查询
JdbcTemplate jdbcTemplate = new JdbcTemplate(DruidUtils.getDataSource());
            String sql = "select * from user where username = ? and password = ?";
            User user1 = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), user.getUsername(), user.getPassword());
System.out.println(user1);

本文标签: 数据库JavaWeb