Java14- Junit、反射、注解、JDBC、JDBCtemplate

1.Junit

       测试一般分为黑盒测试和白盒测试,其中白盒测试需要进行代码编写,黑盒测试不需要代码编写,直接给输入值看结果,白盒需要看过程,本次Junit是白盒测试中的一种。

image

1.1.使用Junit


早期测试

image

使用步骤:方法名:TestXX不需要返回值,因此void-;参数不需要,因为独立运行不需要调用测试类,所有空参;独立运行可以加@test注解。导入junit依赖环境

image

image

由于肉眼看测试结果有时候判断不出成功,因此可以用断言:我期望与真实

import cn.itcast.travel.junit.Calculator;
import org.junit.Assert;
import org.junit.Test;

public class CalculatorTest {
    /**
     * 测试add方法
     */
    @Test
    public void testAdd(){
        Calculator calculator=new Calculator();
        int c=calculator.add(3,4);
        //断言 期望3,实际是7
        Assert.assertEquals(3,c);
        System.out.println(c);
    }

结果:

image

1.2 测试Test的补充-before和after一些需要申请资源和释放资源的例如file流

  •       此处用到一个初始化方法,用到@before注解,
  •       而测试后,执行结束方法,用到@after注解
    @Before
    public void init(){
        //初始化方法,所有测试方法从此处获取初始化
    }
    @After
    public void close(){
        //结束后方法,所有测试方法从此处获取结束后执行操作

    }
即使测试方法出现异常,该释放还是释放

2.反射-框架设计的灵魂

       框架是一个半成品软件,未来使用框架开发中不需要用到反射,但是设计一款框架需要用到反射。反射机制是:将类的各个组成部分封装为其他对象,这就是反射机制

       此前我们的java代码需要用代码实现或者创建类对象来进行相关的方法执行。

image

反射好处:程序运行中,获取和操作这些对象和解耦(方便扩展)

       比方说此前我们定义String str=“abc”。然后用str.各类方法,那么这些方法从哪来的,其实就是反射的类加载器进行各类成员方法的汇总整理进入method数组中,各类方法变身对象,然后我们使用其中数组的成员(方法或构造器或变量)从而可以进行直接操作。

说到这,我们就需要进行class类对象进行获取(获取才能进行操作类对象中保存的各类方法,切记它已经在内存中)。

2.1 class类对象获取方式(3种,分别对应java代码的三种阶段)

(1)第一个阶段方式:对应刚编写好的java代码,通过class.forName(“全限定类名”):将字节码文件加载进内存,返回class对象,这个方法是静态方法。Class<?> aClass = Class.forName("cn.itcast.travel.domain.Person");

(2)第二个阶段方式:通过类名点.属性class获取(进入class类对象阶段已封装各个对象,总对象的类名就是开始编写的代码类名)Class personClass = Person.class;

(3)第三个阶段方式:对象.getClass(),因为已经进入内存,形成对象了,因此直接用对象.getClass()即可,因为getClass方法封装在object类中,因此各个类和对象都可以使用。如下图:getClass()方法返回的是class对象Person p=new Person(); Class pClass = p.getClass();

1609380275(1)

结论:

  • 其实一个字节码文件(*.class)在内存中只加载一次,也就是说三种方式获取的class类对象都是一个地址。
  • 应用场景:第一种方式传递是参数,一般用于配置文件;第二种有参数,一般用于参数传递;第三种方式用于对象获取字节码的方式
    public static void main(String[] args) throws Exception {
        //(1)第一个阶段方式:对应刚编写好的java代码,通过class.forName(“全限定类名”):将字节码文件加载进内存,返回class对象
        Class<?> aClass = Class.forName("cn.itcast.travel.domain.Person");
        System.out.println(aClass);

        //(2)第二个阶段方式:通过类名的属性class获取(进入class类对象阶段已封装各个对象,总对象的类名就是开始编写的代码类名)
        Class personClass = Person.class;
        System.out.println(personClass);
        //(3)第三个阶段方式:对象.getClass(),因为已经进入内存,形成对象了,因此直接用对象.getClass()即可,因为getClass方法封装在object类中,因此各个类和对象都可以使用。如下图:getClass()方法返回的是class对象
        Person p=new Person();
        System.out.println(p);//Person{name='null', age=0, bb='null'}
        Class pClass = p.getClass();
        System.out.println(pClass);
    }
}
class cn.itcast.travel.domain.Person
class cn.itcast.travel.domain.Person
Person{name='null', age=0, bb='null'}
class cn.itcast.travel.domain.Person



2.2.Class对象的功能与使用方法

      其实class对象的方法大部分是get方式,也就说获取功能(里面class对象封装了各类成员变量们getFields、构造方法们、成员方法们还可以获取类名称等等)

Class对象功能: * 获取功能:

1. 获取成员变量们

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

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

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

* Field getDeclaredField(String name)

2. 获取构造方法们

* Constructor<?>[] getConstructors()

* Constructor<T> getConstructor(类<?>... parameterTypes)

* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)

* Constructor<?>[] getDeclaredConstructors()

3. 获取成员方法们: 方法名、参数、返回值类型

* Method[] getMethods() (返回父类和自己,以及Object,因为object是全部的类的父类,例如wait方法 equals方法,notify方法)

* Method getMethod(String name, 类<?>... parameterTypes)

* Method[] getDeclaredMethods()

* Method getDeclaredMethod(String name, 类<?>... parameterTypes)

4. 获取全类名 * String getName()

        Class<Person> personClass = Person.class;
        String name = personClass.getName();
        System.out.println(name);//cn.itcast.travel.domain.Person

2.2.1.使用:成员变量field的设置值和获取值

  • 成员变量field.get(对象,也就说person创建对象) Field bb1 = personClassThree.getDeclaredField("name");Object o1 = bb1.get(person);
  • 成员变量field.set(对象,value值)bb1.set(person,"chexiang");
    public static void main(String[] args) throws Exception {
        // 获取class的对象
        Class personClassThree = Person.class;
        // getFields 获取public修饰的成员变量
        Field[] fields = personClassThree.getFields();
        for (Field field : fields) {
            System.out.println(field+"非指定public");//只有bb这个public的public java.lang.String cn.itcast.travel.domain.Person.bb----------
        }
        System.out.println("=======================");
        // getField 获取指定名称的public修饰的成员变量
        Field bb = personClassThree.getField("bb");
        System.out.println(bb+"指定名称");//public java.lang.String cn.itcast.travel.domain.Person.bb
        System.out.println("========================");
        // field对象的获取值,成员变量在对象中存储的,我们创建person对象,使用它,以前我们用person对象.即可
        Person person =new Person();
        Object o = bb.get(person);

        System.out.println(o+"field对象的获取值");//nullfield对象的获取值
        bb.set(person,"zhangsan");
        o = bb.get(person);
        System.out.println(person);//Person{name='null', age=0, bb='zhangsan'} 打印这对象person的表现形式

        System.out.println(o);//zhangsan

    }
}

当然,当我们使用getDeclaredFields获取成员变量、方法、构造等对象数组时,会忽略权限修饰符的约束,但是我们知道私有方法无法在外面使用,那么是否会出错呢

        System.out.println("=========declared=========");
        Field bb1 = personClassThree.getDeclaredField("name");//这一步是成功的
        System.out.println(bb1);
//        bb1.set(person,"chexiang");错误的,需要用暴力反射
//        Object o1 = bb1.get(person);
//        System.out.println(bb1);
//        System.out.println(o1);
        bb1.setAccessible(true);
        bb1.set(person,"chexiang");
        Object o1 = bb1.get(person);
        System.out.println(bb1);//private java.lang.String cn.itcast.travel.domain.Person.name
        System.out.println(o1);//chexiang

暴力反射:忽略安全权限修饰符

        bb1.setAccessible(true);


构造方法contructor(构造器)对象获取后,可以用来创建对象,里面有newInstance方法

image

也就说:person字节码文件,对应着person的class类对象,创建一个person对象(newInstance)

        System.out.println(person.getClass().getName());//cn.itcast.travel.domain.Person
        System.out.println(person.getClass());//class cn.itcast.travel.domain.Person
        Class personClass = Person.class;
        Constructor[] constructors = personClass.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);//public cn.itcast.travel.domain.Person(java.lang.String,int)   public cn.itcast.travel.domain.Person()
        }
        System.out.println("==============");
        //下面的是单个的获取,也就是给定参数的class对象
        Constructor constructor = personClass.getConstructor(String.class, int.class);//public cn.itcast.travel.domain.Person(java.lang.String,int)
        System.out.println(constructor);//有参构造:public cn.itcast.travel.domain.Person(java.lang.String,int)
        System.out.println("=======创建对象1=======");

        Person p=new Person();
        Field name = personClass.getDeclaredField("name");
        name.setAccessible(true);
        name.set(p,"whwhwhwhw");
        Object o = name.get(p);
        System.out.println(o);
        o = name.get(p);
        System.out.println("=======创建对象2=======");
        Object person = constructor.newInstance("王五", 33);
        System.out.println(person.getClass().getName());//cn.itcast.travel.domain.Person
        System.out.println(person.getClass());//class cn.itcast.travel.domain.Person

        System.out.println(person);//Person{name='王五', age=33, bb='null'}
        Constructor<?> constructor1 = person.getClass().getConstructor();//Person{name='王五', age=33, bb='null'}
        System.out.println(constructor1);

        System.out.println(o);
        constructor = personClass.getConstructor();
        System.out.println(constructor);//无参构造:public cn.itcast.travel.domain.Person()
        Object person2 = constructor.newInstance();
        System.out.println(person2);//Person{name='null', age=0, bb='null'}

获取方法们和执行 invoke(真实对象,参数列表),无参后面空即可

image

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class<Person> personClass = Person.class;
        Method eat = personClass.getMethod("eat");
//        Object invoke = eat.invoke(personClass);错误
        Person person = new Person();
        Object invoke = eat.invoke(person);//eat....无参方法,不用给其他值
        System.out.println("---------");
        System.out.println(eat);//public void cn.itcast.travel.domain.Person.eat()
        System.out.println("---------");
        System.out.println(invoke);//null
        System.out.println("有参方法");
        Method run = personClass.getMethod("run", String.class);
        Object invoke1 = run.invoke(person, "30");//我以30速度飞奔而来

    }

2.3 反射案例-写个“框架”,在不改变任何代码的情况下,可以帮助我们创建任意类的对象,并且执行其中任意的方法。

做法:配置文件+反射技术

步骤:

  • 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  • 在程序中加载读取配置文件(未来只改配置文件)
  • 使用反射技术来加载类文件进内存
  • 创建对象
  • 执行方法
public class ReflectTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //加载配置文件,Properties里面有properties对象,将配置文件以properties以load方式读取到内存中
        //1.1创建对象
        Properties properties=new Properties();
        //1.2.1加载文件,load中给个字符流或字节流,其中获取class目录下的配置文件,ReflectTest.class通过getClassLoader()获得类加载器,使得字节码ReflectTest.class文件加载
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        //1.2.2 类加载器返回对象classLoader能找到person,也就能找到properties文件
        InputStream rs = classLoader.getResourceAsStream("pro.properties");
        properties.load(rs);
        // 2.获取配置文件中的信息
        String className = properties.getProperty("className");
        System.out.println(className);//cn.itcast.travel.domain.Person
        String methodName = properties.getProperty("methodName");
        System.out.println(methodName);//eat
        //3.反射:加载该类进内存
        Class cls = forName(className);
        //4. 创建对象
        Object person = cls.newInstance();
        //5. 获取方法对象
        Method method = cls.getMethod(methodName);
        Object invoke = method.invoke(person);//eat...
    }
}

className=cn.itcast.travel.domain.Person
methodName=eat


3. 注解-框架中极其重要的

ava 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

作用分类:

  • 编写文档:通过代码里标识的注解生成文档(代码所在文件夹 cmd javadoc 文件名称,即可生成)
  • 代码分析:通过代码里标识的注解对代码进行分析(使用反射技术抽取注解)
  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(override)

3.1JDK预定义的注解

*JDK中预定义的一些注解 *

  • @Override :检测被该注解标注的方法是否是继承自父类(接口)的,否则方法名有错,会报错
  • @Deprecated:该注解标注的内容,表示已过时 ,就是那些横杠,享用还是可以用。
  • @SuppressWarnings:压制警告(压制编译器警告)

        * 一般传递参数all @SuppressWarnings("all") ,压制类下面的所有警告

作用在其他注解的注解(或者说 元注解)是:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Retention(RetentionPolicy.RUNTIME(运行时阶段))
                              ,class(类对象阶段)
                              .resource(源代码阶段)
  • @Documented - 标记这些注解是否包含在用户文档中。 也就是可以在cmd命令中,将本文件夹的java代码(需要删除包路径)javadoc 文件名输出api文档。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次



3.2 自定义注解

* 格式: 元注解

       public @interface 注解名称{ 属性列表; }

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

编译:javac

反编译:javab

* public interface MyAnno extends java.lang.annotation.Annotation {

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

}

     * 注解的属性:也就是本质上的接口,比如接口中可以定义的抽象方法 ;这些抽象方法称之为注解属性。

     * 要求:

       1. 属性的返回值类型有下列取值要求

            * 基本数据类型(四类八种)

            * String

            * 枚举

            * 注解

            * 以上类型的数组 ,不允许void

public @interface MyAnno {
     int show();//int类型,可以
     String show1();//字符串类型,可以
     Person show2();//枚举类型,可以

     MyAnno2 show5();//注解类型 ,可以
     Student show6();//class 类型,不可以
}

2. 为什么要属性,因为我们在定义了属性,在使用时(使用者)需要给属性赋值(因此建议方法名了最好取属性名,如age,这样好看耗材写)

@MyAnno(show1 = 1)//要给方法赋值,也就可以把show当成属性,1是给属性赋值
public class Worker {
}

       2.1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。 (在原来注解中

public @interface MyAnno {
     int show() default 1;//int类型,可以
     String show1() default "zhangsan";//字符串类型,可以

下面使用注解时,未赋值,没有报错

package cn.itcast.travel.annotation;
@MyAnno()//要给方法赋值,也就可以把show当成属性,1是给属性赋值
public class Worker {
}


2.2. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。

@MyAnno(value=11)//value也可以不要,直接就11即可
public class Worker {
}

括号里面遇到枚举赋值: per=person.P1; 注解赋值 anno2=@MyAnno2

      

2.3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略 * 元注解:用于描述注解的注解

str={“abc”,"bcd"}

元注解-也就是其他注解,来描述我们的注解具体主体  

* @Target:描述注解能够作用的位置 (类、方法、成员变量)* ElementType(它是一个枚举,有三个取值我们需要注意)取值:

  •          * TYPE:可以作用于类上
  •          * METHOD:可以作用于方法上
  •          * FIELD:可以作用于成员变量上

     @MyAnno(value={ElementType.TYPE})(因为它是返回枚举数组),当然value可以删除

* @Retention:描述注解被保留的阶段

  •       * @Retention(RetentionPolicy.RUNTIME):(RetentionPolicy是枚举)当前被描述的注解,会保留到class字节码文件中,并被JVM读取到,我们自己定义的一般是RUNTIME。

         RUNTIME/SOURCE(字节码文件都不会被保存)/CLASS(不会被JVM读取到)三个阶段,因此用RUNTIME多些。

  * @Documented:描述注解是否被取到api文档中

* @Inherited:描述注解是否被子类继承 (自动继承)



3.3程序中使用注解-获取注解定义的值

         简单的说,就是获取注解使用中赋值的一些值,也就是元注解中给予赋值的,value或其他配置值。

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

3.3.2. 获取指定的注解

* getAnnotation(Class)

//其实就是在内存中生成了一个该注解接口PRO的子类实现对象 (复写PRO方法,方法的值就是使用该注解时上面赋予的值)

public class ProImpl implements Pro{

     public String className(){

     return "cn.itcast.annotation.Demo1";

}

public String methodName(){

      return "show";

} }


@Pro(className = "cn.itcast.travel.annotation.Demo1",methodName = "show")
public class ReflectTest {
    public static void main(String[] args) throws Exception{

        //1 解析注解
        //1.1 获取该类的字节码对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //2 获取该类的注解信息
        Pro annotation = reflectTestClass.getAnnotation(Pro.class);//其中就是在内存中,生成了一个改注解接口的子类实现对象
        //3 调用注解对象中定义的抽象方法(属性),获取返回值
        String className = annotation.className();
        String show = annotation.methodName();
        System.out.println(className);//cn.itcast.travel.annotation.Demo1
        System.out.println(show);//show
        //3.反射:加载该类进内存
        Class cls = forName(className);
        //4. 创建对象
        Object person = cls.newInstance();
        //5. 获取方法对象
        Method method = cls.getMethod(show);
        Object invoke = method.invoke(person);//show========


    }
}

     调用注解中的抽象方法获取配置的属性值 ,不光可以获取类,也可以获取方法。

TestCheck注解:此前反射的代码进行修改


import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 简单的测试框架,当主方法执行后,会自动执行被监测的所有方法,是否有异常,并记录到文件中
 */
public class TestCheck {

    public static void main(String[] args) throws Exception {
        //1. 创建计算器的对象
        Calculator c=new Calculator();
        //2. 获取它的字节码文件对象,因为我们要获取它所有的对象
        Class calculatorClass = c.getClass();
        //2. 获取所有的方法
        //2.0 统计出现的异常次数和异常记录位置
        int number=0;
        BufferedWriter bw=new BufferedWriter(new FileWriter("bug.txt"));
        Method[] methods = calculatorClass.getMethods();
        for (Method method : methods) {
            //2.1 判断方法上是否有Check注解,isAnnotationPresent()方法有无配置的注解
            if(method.isAnnotationPresent(Check.class)){
                //2.2 有注解,执行方法
                try {
                    method.invoke(c);
                } catch (Exception e) {
                    //3.捕捉异常,记录到文件中
                    number++;
                    bw.write(method.getName()+"XXX方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称"+e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因"+e.getCause().getMessage());
                    bw.newLine();
                    bw.write("-----------------------");
                }
            }
        }
        bw.write("本次一共出现"+number+"次异常");
        bw.flush();
        bw.close();
    }



}
Calculator计算器

public class Calculator {
    //加法
    @Check
    public void add(){
        System.out.println("1+0="+(1+0));
    }
    //减法
    @Check
    public void sub(){
        System.out.println("1-0="+(1-0));
    }
    //乘法
    @Check
    public void mul(){
        System.out.println("1*0="+(1*0));
    }
    //除法
    @Check
    public void div(){
        System.out.println("1/0="+(1/0));
    }
    public void show(){
        System.out.println("永无bug");
    }
}
Check.java注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {

}

divXXX方法出异常了
异常的名称ArithmeticException
异常的原因/ by zero
-----------------------本次一共出现1次异常

我们的总结

  • 以后大多数时候,我们使用注解,而不是自己定义注解
  • 注解给谁用:编译器和给解析程序使用
  • 注解不是程序的一部分,可以理解为注解就是一个标签。

4. JDBC

Java DataBase Connectivty也就是java操作数据库规范,

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

    这个实现类也就是数据库驱动,由不同的厂商来提供

image

    例如我们通常定义Person()接口,实现类worker()

Person p= new Worker();p.eat();执行的是Person接口在实现类中的重写方法eat();

4.2. 快速入门:
     * 步骤:
         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(Connection不能直接执行
         6. 执行sql,接受返回结果(方法调用
         7. 处理结果
         8. 释放资源(类似流操作

		  	//1. 导入驱动jar包
	        //2.注册驱动
	        Class.forName("com.mysql.jdbc.Driver");
	        //3.获取数据库连接对象
	        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
	        //4.定义sql语句
	        String sql = "update account set balance = 500 where id = 1";
	        //5.获取执行sql的对象 Statement
	        Statement stmt = conn.createStatement();
	        //6.执行sql
	        int count = stmt.executeUpdate(sql);
	        //7.处理结果
	        System.out.println(count);
	        //8.释放资源
	        stmt.close();
	        conn.close();

mysql-connector-java-5.1.37-bin.jar 这个jar包是一些字节码文件

public class JdbcDemo {
    public static void main(String[] args) throws Exception {
        //1. 导入驱动jar包
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取数据库连接对象
        Connection conn = DriverManager.getConnection("jdbc://localhost:3306/db3", "root", "root");
        //3.获取执行sql的对象
        Statement st = conn.createStatement();
        //4,定义sql语句
        String sql="update account set balance = 500 where id= 1";
        //5.执行sql,获得结果
        int count = st.executeUpdate(sql);
        //6.处理结果
        System.out.println(count);
        //7.释放资源
        st.close();
        conn.close();
    }
}
4.2 JDBC中类对象(接口)的讲解
  • 1.DriverManager:驱动管理对象(是个类)

           注册驱动(内含静态de/registerDriver方法)+获取数据库连接对象

  • 2. Connection:数据库连接对象
  • 3. Statement:(接口):执行sql的对象(执行静态sql)
  • 4. ResultSet:结果集对象,封装查询结果
  • 5. PreparedStatement:执行sql的对象(比Statement更强大)(预编译sql,也称之为动态sql)

4.2. 1.DriverManager:驱动管理对象(是个类)

           注册驱动(内含静态de/registerDriver方法)+获取数据库连接对象

那么de/registerDriver方法和Class.forName("com.mysql.jdbc.driver")有什么关联,其实在driver中有个静态代码块,当forName加载内存后自动执行

image

截图说明其实还是driverMannager在执行注册驱动,只是forName比较简单

实际上在jdk1.5之后,不用注册驱动,因为jar包里面service包含了

image

静态方法: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:密码


4.2.2. Connection:数据库连接对象

    获取执行sql语句的方法

1609753064(1)1609753078(1)

  • PreparedStatement prepareStatement(String sql)
  • Statement createStatement()
  • 管理事务(开启:setAutoCommit(boolean autoCommit)、提交commit() 、回滚rollback())
  • 3. Statement(接口):执行sql的对象(执行静态sql)
int executeUpdate(String sql) 执行dml语句(insert update delete)
                                  ddl语句(create,alter,drop)
 返回值是int的,也就是影响的行数
ResultSet executeQuery(String sql) :执行DQL(select)语句
image
package cn.itcast.travel.jdbc;

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

public class JdbcDemoFour {
    public static void main(String[] args) {
        Connection connection =null;
        Statement statement =null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.创建数据库连接对象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
            //3.创建sql语句执行的对象
            statement = connection.createStatement();
            //4. 定义数据库sql语句
            String sql="delete from account where id=5";
            //5. 执行sql语句
            int count = statement.executeUpdate(sql);
            if (count>0){
                System.out.println("修改成功");
            }else {
                System.out.println("操作失败");
            }

        } 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:结果集对象,封装查询结果-statement.executeQuery()结果

        既然是结果集,封装结果数据,那么我们如何获取这个数据集里面的数据,我们需要了解一个知识点:

next()方法:游标向前移动一行(一次获取某一行的某一猎数据)。

getXxx(参数)方法:Xxx是数据类型,int double varchar/string Array

            参数若是int:表达某一个编号(不是索引)他从1开始,不是0,特殊。

             参数若是string:表达列名 :getDouble("balance")

这里有方法重载

import java.sql.*;

public class JdbcDemoFive {
    public static void main(String[] args) {
        Connection connection =null;
        Statement statement =null;
        ResultSet resultSet =null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.创建数据库连接对象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
            //3.创建sql语句执行的对象
            statement = connection.createStatement();
            //4. 定义数据库sql语句
            String sql="select * from account";
            //5. 执行sql语句
            resultSet = statement.executeQuery(sql);
            //6.1 获取结果
            resultSet.next();
            int id = resultSet.getInt(1);
            String name = resultSet.getString("name");
            double balance = resultSet.getDouble(3);
            System.out.println("id: "+id+"-"+"name: "+name+"-"+"balance: "+balance);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        finally {
            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();
                }
            }
        }
    }
}
上面的存在一个问题,就是next()要不听的进行,才能获取数据,而且我们得想办法判读有无数据,否则会报错

实际上resultset.next()返回布尔值,true代表有数据,false代表无数据

            while(resultSet.next()){
                int id = resultSet.getInt(1);
                String name = resultSet.getString("name");
                double balance = resultSet.getDouble(3);
                System.out.println("id: "+id+"-"+"name: "+name+"-"+"balance: "+balance);
            }

案例:查询emp表,将其结果封装为对象(装载在集合中),然后打印

思路:我们的表格,定义的各个字段类似于类的成员变量。而对象就像是表格里面的每一行数据,因此我们可以根据表的结构封装成一个类,然后我们查询出一条数据,就可以封装成这个类的对象。如此看来,一个表格数据封装成对象,那么必然存在多个对象。而我们此前学习,专用来封装对象的数据的容器是集合。

步骤:

  • 定义一个Emp类
  • 定义查询方法: public list<Emp> findAll(){};返回list集合,泛型是Emp
  • 实现方法:select * from emp;用的statement方法是excutaquery,用的结果集对象是ResultSet

定义Emp类

public class Emp {
    private int id;
    private String name;
    private String gender;
    private double salary;
    private Date join_date;
    private int dept_id;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Date getJoin_date() {
        return join_date;
    }

    public void setJoin_date(Date join_date) {
        this.join_date = join_date;
    }

    public int getDept_id() {
        return dept_id;
    }

    public void setDept_id(int dept_id) {
        this.dept_id = dept_id;
    }

    public Emp(int id, String name, String gender, double salary, Date join_date, int dept_id) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.salary = salary;
        this.join_date = join_date;
        this.dept_id = dept_id;
    }

    public Emp() {
    }

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", salary=" + salary +
                ", join_date=" + join_date +
                ", dept_id=" + dept_id +
                '}';
    }
}

定义查询方法和实现sql及主类测试

public class JdbcDemoE {

    public static void main(String[] args) {
        // 由于findAll我们定义的是非静态方法,需要用到创建对象
        JdbcDemoE jdbcDemoE = new JdbcDemoE();
        List<Emp> empList = jdbcDemoE.findAll();
        //此处打印emplist,调用的是emp里面的toString方法;
        System.out.println(empList);

    }

    /**
     * 查询所有emp表对象
     * @return
     */
    public List<Emp> findAll(){
        Connection connection =null;
        Statement statement =null;
        ResultSet resultSet =null;
        List<Emp> list =null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取数据库连接
           connection = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root");
            //3.获取执行sql的对象
            statement = connection.createStatement();
            //4.定义sql
            String sql="select * from emp";
            //5.执行sql
            resultSet = statement.executeQuery(sql);
            //6.遍历结果集,封装对象
            Emp emp=null;
            list = new ArrayList<Emp>();
            while (resultSet.next()){
                //这里面的参数id等名称,要与数据库一致,和emp类没有关系
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String gender = resultSet.getString("gender");
                double salary = resultSet.getDouble("salary");
                //这里注意:我们emp定义date是util包下,而此处返回的date是sql包下,sql包是util包子类,因此此处涉及的是
                Date join_date = resultSet.getDate("join_date");
                int dept_id = resultSet.getInt("dept_id");
                //创建emp对象,并赋值
                emp=new Emp();

                emp.setId(id);
                emp.setName(name);
                emp.setGender(gender);
                emp.setJoin_date(join_date);
                emp.setDept_id(dept_id);
                //装载集合
                list.add(emp);


            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            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();
                }
            }
        }
        return list;
    }
}

   此处遇到一个问题:jdbc操作和释放资源的操作重复度较高,我们抽取一个方法,获取连接对象,注册驱动和释放资源的方法;做法如下:

1)建立一个utils包

2)建立一个jdbcUtils工具类(内含获取连接对象【不想传递参数,考虑到配置文件做法这样通用些,而读取配置文件只需要执行一次,静态代码块实现它】和释放资源方法,建议静态方法)

public class Jdbc {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;

    static {
        //读取资源文件,获取值,bufferreader filereader都可以但是很麻烦,不如Properties集合类(它有load方法)
        Properties properties =new Properties();
        //load 加载进内存,传一个字符流或字节流,new的方法
        try {
            //此处异常不能抛出去,因为抛出异常必须借助方法
            properties.load(new FileReader("src/main/java/cn/itcast/travel/jdbc.properties"));
            //获取属性进行赋值
            url = properties.getProperty("url");
            user=properties.getProperty("user");
            password=properties.getProperty("password");
            driver=properties.getProperty("driver");
            //注册驱动
            Class.forName(driver);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }


    }
    /**
     * 获取数据库连接对象
     * @return连接对象
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url,user,password);
    }


    /**
     * 释放连接的方法,我们需要考虑到update和query两类资源释放,因此释放方法的参数不同,进行一种重载的机制
     * executeUpdate方法的释放操作
     * @param statement
     * @param 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();
            }
        }

    }

    /**
     * 释放连接的方法,我们需要考虑到update和query两类资源释放,因此释放方法的参数不同,进行一种重载的机制
     * executeQuery方法的释放操作
     * @param resultSet
     * @param statement
     * @param connection
     */
    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();
            }
        }

    }
}

此处涉及到读取配置文件的方法,我们可以用ClassLoader来获取src路径下的资源文件

 try {
            //读取资源文件,获取值,bufferreader filereader都可以但是很麻烦,不如Properties集合类(它有load方法)
            Properties properties =new Properties();
            //load 加载进内存,传一个字符流或字节流,new的方法
            //此处异常不能抛出去,因为抛出异常必须借助方法,classloader可以加载字节码文件进入内存。还可以获取src下资源路径
            ClassLoader classLoader = Jdbc.class.getClassLoader();
            System.out.println(classLoader);//jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc

            URL resource = classLoader.getResource("jdbc.properties");//传一个文件名,返回一个src相对路径,url表示一个统一资源符


            System.out.println(resource+"1");//file:/D:/Code/IDEA/travel/target/classes/jdbc.properties1
            //resource定位了一个文件的绝对路径,要获取它的字符串路径,用getPath方法,返回path
            String path = resource.getPath();
            System.out.println(path);///D:/Code/IDEA/travel/target/classes/jdbc.properties

//            properties.load(new FileReader("src/main/java/cn/itcast/travel/jdbc.properties"));
            properties.load(new FileReader(path));

            //获取属性进行赋值
            url = properties.getProperty("url");
            user=properties.getProperty("user");
            password=properties.getProperty("password");
            driver=properties.getProperty("driver");
            Class.forName(driver);


练习:

通过键盘输入用户名和密码,判断用户是否登录成功

步骤:

  • 创建user表,包含正确的用户名和密码
  • 创建一个类,登录与查询的方法
public class JdbcDemoTan {

    public static void main(String[] args) {
        // 1. 键盘录入
        Scanner scanner =new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = scanner.nextLine();
        System.out.println("请输入密码");
        String password = scanner.nextLine();

        //2. 调用方法
        boolean loginFlag = new JdbcDemoTan().login(username, password);

        //3.判断结果,输出不同语句
        if (loginFlag){
            System.out.println("登录成功");
        }else {
            System.out.println("用户名或密码错误");
        }
    }

    /**
     *
     * @param username
     * @param password
     * @return
     */
    public boolean login(String username,String password){

        if (username==null || password ==null){
            return false;
        }
        Connection connection =null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            // 连接数据库判断是否登录成功
            connection = Jdbc.getConnection();
            // 定义sql
            String sql="select * from user where username= '"+username+"' and password= '"+password+"' ";
            statement = connection.createStatement();
            resultSet = statement.executeQuery(sql);
            return resultSet.next();

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        Jdbc.close(resultSet,statement,connection);
        return false;
    }
}
  • 5. PreparedStatement:执行sql的对象(比Statement更强大)(预编译sql,也称之为动态sql)

    常规Statement容易出现sql注入问题。用户名随便输入,而密码未a' or 'a' = 'a则出现登录成功

select * from user where username= 'zhangsan' and password= 'a' or 'a' = 'a'

      简单一句话:Sql注入问题是在拼接sql时,有些sql的特殊关键字参与字符串的拼接,造成安全性异常问题

     解决方案: 使用PreparedStatement来处理,是statement子接口实现对象。PreparedStatement使用预编译动态执行。sql传参用占位符替代,传参即可。


image

定义sql

select * from user where username=? and password= ?

获取执行sql语句的对象:

Connection.prepareStatement(String sql);

image

赋值setXXX(问号位置,问号值)

image

执行sqlexecuteQuery()不需要传sql;

public class JdbcDemoTT {

    public static void main(String[] args) {
        // 1. 键盘录入
        Scanner scanner =new Scanner(System.in);
        System.out.println("请输入用户名");
        String username = scanner.nextLine();
        System.out.println("请输入密码");
        String password = scanner.nextLine();

        //2. 调用方法
        boolean loginFlag = new JdbcDemoTT().login(username, password);

        //3.判断结果,输出不同语句
        if (loginFlag){
            System.out.println("登录成功");
        }else {
            System.out.println("用户名或密码错误");
        }
    }

    /**
     *
     * @param username
     * @param password
     * @return
     */
    public boolean login(String username,String password){

        if (username==null || password ==null){
            return false;
        }
        Connection connection =null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 连接数据库判断是否登录成功
            connection = Jdbc.getConnection();
            // 定义sql
            String sql="select * from user where username= ? and password= ? ";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,username);
            preparedStatement.setString(2,password);
            resultSet = preparedStatement.executeQuery();

            return resultSet.next();

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            Jdbc.close(resultSet,preparedStatement,connection);

        }
        return false;
    }
}

4.3 JDBC管理事务

4.3.1 事务定义:一个包含多个步骤的业务操作,如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。

操作步骤:开启事务     提交事务     回滚事务

JDBC管理事务本质:使用的是Connection对象来管理事务(因为connection对象中包含这些方法)

  • 开启事务:setAutoCommit(boolean autoCommit):设置false为开启 
  • 提交事务:commit();
  • 回滚事务:rollback()

练习:我们通过银行转账业务代码来感受jdbc中转账操作的事务管理

常规无事务做法

public class JdbcDemoEleven {

    public static void main(String[] args) {
        Connection connection =null;
        PreparedStatement preparedStatementOne =null;
        PreparedStatement preparedStatementTwo =null;

        try {
            //1.获取连接
            connection = Jdbc.getConnection();
            //2. 定义sql
            String sqlOne="update account set balance=balance-? where id=?";
            String sqlTwo="update account set balance=balance+? where id=?";
            //3. 获取
            preparedStatementOne = connection.prepareStatement(sqlOne);
            preparedStatementTwo = connection.prepareStatement(sqlTwo);
            //4. 设置参数
            preparedStatementOne.setDouble(1,500);
            preparedStatementOne.setInt(2,1);
            preparedStatementTwo.setDouble(1,500);
            preparedStatementTwo.setInt(2,2);
            //5.执行sql
            preparedStatementOne.executeUpdate();
            preparedStatementTwo.executeUpdate();


        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            Jdbc.close(preparedStatementOne,connection);
            Jdbc.close(preparedStatementTwo,null);
        }

    }

}
添加:事务管理
public class JdbcDemoEleven {

    public static void main(String[] args) {
        Connection connection =null;
        PreparedStatement preparedStatementOne =null;
        PreparedStatement preparedStatementTwo =null;

        try {
            //1.获取连接
            connection = Jdbc.getConnection();
            //         开启事务
            connection.setAutoCommit(false);
            //2. 定义sql
            String sqlOne="update account set balance=balance-? where id=?";
            String sqlTwo="update account set balance=balance+? where id=?";
            //3. 获取
            preparedStatementOne = connection.prepareStatement(sqlOne);
            preparedStatementTwo = connection.prepareStatement(sqlTwo);
            //4. 设置参数
            preparedStatementOne.setDouble(1,500);
            preparedStatementOne.setInt(2,1);
            preparedStatementTwo.setDouble(1,500);
            preparedStatementTwo.setInt(2,2);
            //5.执行sql
            preparedStatementOne.executeUpdate();
            preparedStatementTwo.executeUpdate();
            System.out.println(4/0);
            connection.commit();


        } catch (Exception throwables) {
            try {
                if (connection!=null){
                    connection.rollback();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            throwables.printStackTrace();
        }finally {
            Jdbc.close(preparedStatementOne,connection);
            Jdbc.close(preparedStatementTwo,null);
        }

    }

}

事务代码编写注意:

  • 开启事务:在执行sql语句之前
  • 提交事务:执行sql语句之后
  • 回滚事务:在catch中执行回滚事务

5.数据库连接池JDBCtemplate

5.1 数据库连接池-(早期每次向底层获取数据库connection连接和释放)

早期每次数据库操作,都要向系统底层申请connection资源释放,非常消耗资源和浪费,而数据库连接池是一个容器,容器会申请一些连接对象,当用户访问数据库时,从容器中获取connection数据库连接对象,这样快速而有效。

        实现数据库连接池方式-SUN公司设置了DataSource接口,代表数据源的工厂,由数据库驱动商提供实现:三种方式:

  • 基本实现:生成标准的Connection对象
  • 连接池实现:生成将自动参与连接池的Connection对象,此实现与中间层连接池管理器配合使用。数据库驱动商提供了C3P0(比较老),Druid(阿里巴巴实现)

获取连接:getConnection();

归还连接:如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会关闭连接,而是归还,可以说是阿里或其他驱动供应商一致的选择,这样方便开发者习惯和记忆。

1610013980(1)

  • 分布式事务实现-生成可用于分布式事务的connection对象,并且几乎总是参与连接池,此实现与中间事务管理器一起关注,并且几乎总是使用连接池管理器


Druid数据库连接池使用步骤:

  • 导入jar包 -druid-1.0.9.jar
  • 定义配置文件(proprties形式,可以叫任意名称,可以放在任意目录下
  • 获取数据库连接池对象:通过工厂类来获取DruidDataSourceFactory
public class DruidDemoOne {
    public static void main(String[] args) {

        try {
            //1.导入jar包  放在libs
            //2.定义配置文件  放在resources
            //2.1加载配置文件
            Properties properties =new Properties();
            InputStream resourceAsStream = DruidDemoOne.class.getClassLoader().getResourceAsStream("d.properties");
            properties.load(resourceAsStream);
            //3.获取连接池对象
            DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
            //4.获取执行sql对象
            Connection connection = dataSource.getConnection();
            Statement statement = connection.createStatement();
            System.out.println(connection);//com.mysql.jdbc.JDBC4Connection@21b2e768
            System.out.println(statement);//com.mysql.jdbc.StatementImpl@175b9425
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


以上就是一个Druid数据库连接池的使用步骤,可以理解为他们其实也挺复杂,我们也可以抽取出来一个工具类:jdbcUtils,与上面传统方法不同之处在于:

  • 定义一个类JdbcUtils
  • 提供静态代码块加载配置文件,初始化连接池对象
  • 提供方法(获取连接方法-通过数据库连接池获取连接;释放资源;获取连接池方法)其中获取连接池方法在有的小框架中不需要连接池对象只需要连接池。

public class DruidUtil {

    // 1. 定义成员变量DataSource
    private static DataSource dataSource;
    //
    static {

        try {
            // 1.加载配置文件
            Properties properties =new Properties();
            properties.load(DruidUtil.class.getClassLoader().getResourceAsStream("d.properties"));
            System.out.println(properties);//{password=root, initialSize=5, driverClassName=com.mysql.jdbc.Driver, maxWait=3000, url=jdbc:mysql:///db3, username=root, maxActive=10}
//            properties.load(DruidUtil.class.getResourceAsStream("d.properties"));//{}

            //2. 获取DataSource初始化值
            dataSource= DruidDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //3.获取连接对象的方法
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    //4.释放资源 重载
    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();
            }
        }

    }
    public static void close(Connection connection,Statement statement){
        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    //5. 获取连接池的方法
    public static DataSource getDataSource(){
        return dataSource;
    }
}

练习:查询数据库emp表,封装成对象集合

public class DruidDemoTwo {
    //
    public static void main(String[] args) {
        Connection connection =null;
        Statement statement = null;
        ResultSet resultSet =null;
        try {
            connection = DruidUtil.getConnection();
            statement = connection.createStatement();
            String sql="select * from emp";
            resultSet = statement.executeQuery(sql);
            Emp emp =new Emp();
            List<Emp> list=new ArrayList<Emp>();
            while (resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String gender = resultSet.getString("gender");
                double salary = resultSet.getDouble("salary");
                Date join_date = resultSet.getDate("join_date");
                int dept_id = resultSet.getInt("dept_id");
                emp.setId(id);
                emp.setName(name);
                emp.setGender(gender);
                emp.setSalary(salary);
                emp.setJoin_date(join_date);
                emp.setDept_id(dept_id);
                list.add(emp);
            }
            System.out.println(list);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        DruidUtil.close(resultSet,statement,connection);

    }
}

应将DruidUtil.close(resultSet,statement,connection); 放在finally中执行


5.2 Spring JDBC封装:JDBC template-简化jdbc的编写流程(早期连接,执行sql,处理结果(例如getInt,setId)等等)

* Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发

* 步骤:

1. 导入jar包

2. 创建JdbcTemplate对象。依赖于数据源DataSource ,意思就是传参ds

      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) ,此处的类型和类型.class,将字节码文件与数据库,它会自动将类型(例如Emp类与数据库emp字段名称是否一致)

   * queryForObject:查询结果,将结果封装为对象 ;; 一般用于聚合函数的查询
对比之前得操作,查询后还得取值,封装

while (resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String gender = resultSet.getString("gender");
                double salary = resultSet.getDouble("salary");
                Date join_date = resultSet.getDate("join_date");
                int dept_id = resultSet.getInt("dept_id");
                emp.setId(id);
                emp.setName(name);
                emp.setGender(gender);
                emp.setSalary(salary);
                emp.setJoin_date(join_date);
                emp.setDept_id(dept_id);
                list.add(emp);
            }

现在

public class JDBCtemplateDemo {
    public static void main(String[] args) {
        //1.导入jar包

        //2.创建jdbcTemplate对象
        JdbcTemplate template = new JdbcTemplate(DruidUtil.getDataSource());
        //2.2 定义sql
        String sql="update account set balance=5000 where id=?";
        //3.调用template对象得方法
        int updateCount = template.update(sql, 3);
        System.out.println(updateCount);

    }
}

当然离不开druidUtil

public class DruidUtil {

    // 1. 定义成员变量DataSource
    private static DataSource dataSource;
    //
    static {

        try {
            // 1.加载配置文件
            Properties properties =new Properties();
            properties.load(DruidUtil.class.getClassLoader().getResourceAsStream("d.properties"));
            System.out.println(properties);//{password=root, initialSize=5, driverClassName=com.mysql.jdbc.Driver, maxWait=3000, url=jdbc:mysql:///db3, username=root, maxActive=10}
            //2. 获取DataSource初始化值
            dataSource= DruidDataSourceFactory.createDataSource(properties);
            System.out.println(dataSource);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //3.获取连接对象的方法
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
    //4.释放资源 重载
    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();
            }
        }

    }
    public static void close(Connection connection,Statement statement){
        if (statement!=null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (connection!=null){
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    //5. 获取连接池的方法
    public static DataSource getDataSource(){
        return dataSource;
    }
}

比较jdbctemplate /druid/jdbcutil和传统jdbc链接方式,参考如下:

https://www.cnblogs.com/noteless/p/10319296.html

5.3 JdbcTemplate的练习

* 需求:

1. 修改1号数据的 salary 为 10000

2. 添加一条记录

3. 删除刚才添加的记录

4. 查询id为1的记录,将其封装为Map集合

5. 查询所有记录,将其封装为List

6. 查询所有记录,将其封装为Emp对象的List集合 7. 查询总记录数

  public class JdbcTemplateDemo2 {

                //Junit单元测试,可以让方法独立执行


                //1. 获取JDBCTemplate对象
                private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
                /**
                 * 1. 修改1号数据的 salary 为 10000
                 */
                @Test
                public void test1(){

                    //2. 定义sql
                    String sql = "update emp set salary = 10000 where id = 1001";
                    //3. 执行sql
                    int count = template.update(sql);
                    System.out.println(count);
                }

                /**
                 * 2. 添加一条记录
                 */
                @Test
                public void test2(){
                    String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
                    int count = template.update(sql, 1015, "郭靖", 10);
                    System.out.println(count);

                }

                /**
                 * 3.删除刚才添加的记录
                 */
                @Test
                public void test3(){
                    String sql = "delete from emp where id = ?";
                    int count = template.update(sql, 1015);
                    System.out.println(count);
                }

                /**
                 * 4.查询id为1001的记录,将其封装为Map集合
                 * 注意:这个方法查询的结果集长度只能是1
                 */
                @Test
                public void test4(){
                    String sql = "select * from emp where id = ? or id = ?";
                    Map<String, Object> map = template.queryForMap(sql, 1001,1002);
                    System.out.println(map);
                    //{id=1001, ename=孙悟空, job_id=4, mgr=1004, joindate=2000-12-17, salary=10000.00, bonus=null, dept_id=20}

                }

                /**
                 * 5. 查询所有记录,将其封装为List
                 */
                @Test
                public void test5(){
                    String sql = "select * from emp";
                    List<Map<String, Object>> list = template.queryForList(sql);

                    for (Map<String, Object> stringObjectMap : list) {
                        System.out.println(stringObjectMap);
                    }
                }

                /**
                 * 6. 查询所有记录,将其封装为Emp对象的List集合
                 */

                @Test
                public void test6(){
                    String sql = "select * from emp";
                    List<Emp> list = template.query(sql, new RowMapper<Emp>() {

                        @Override
                        public Emp mapRow(ResultSet rs, int i) throws SQLException {
                            Emp emp = new Emp();
                            int id = rs.getInt("id");
                            String ename = rs.getString("ename");
                            int job_id = rs.getInt("job_id");
                            int mgr = rs.getInt("mgr");
                            Date joindate = rs.getDate("joindate");
                            double salary = rs.getDouble("salary");
                            double bonus = rs.getDouble("bonus");
                            int dept_id = rs.getInt("dept_id");

                            emp.setId(id);
                            emp.setEname(ename);
                            emp.setJob_id(job_id);
                            emp.setMgr(mgr);
                            emp.setJoindate(joindate);
                            emp.setSalary(salary);
                            emp.setBonus(bonus);
                            emp.setDept_id(dept_id);

                            return emp;
                        }
                    });


                    for (Emp emp : list) {
                        System.out.println(emp);
                    }
                }

                /**
                 * 6. 查询所有记录,将其封装为Emp对象的List集合
                 */

                @Test
                public void test6_2(){
                    String sql = "select * from emp";
                    List<Emp> list = template.query(sql, new BeanPropertyRowMapper<Emp>(Emp.class));
                    for (Emp emp : list) {
                        System.out.println(emp);
                    }
                }

                /**
                 * 7. 查询总记录数
                 */

                @Test
                public void test7(){
                    String sql = "select count(id) from emp";
                    Long total = template.queryForObject(sql, Long.class);
                    System.out.println(total);
                }

            }

queryForObject 方法需要传入三个参数,第一个参数是该方法需要执行的SQL语句。

        数组params为执行动态SQL时需要传入的参数,参数顺序与SQL中的参数顺序相同。

        最后一个参数是返回值的类型(只能是基本数据类型的封装类,如Integer、String,例如Long.class)

        如果想使用自定义的类型的返回值:new BeanPropertyRowMapper(StoreDto.class) (当POJO对象和数据看表字段完全对应或驼峰式与下划线对应时BeanPropertyRowMapper类会根据构造函数中的class来自动填充数据。)

需要注意的是:queryForObject方法默认返回值不为空,如果可以肯定结果集不为空可以不做处理,否则需要用

        try……catch代码块进行异常的捕获与处理。














posted @ 2020-12-31 08:40  芒果侠  阅读(255)  评论(0编辑  收藏  举报