JDBC技术学习

目录

一.前言

二.全新的JDBC技术概述

2.1.jdbc概念总结

2.2.jdbc核心api和使用路线

jdbc技术组成

2.3.mysql软件介绍

1.mysql软件知名版本迭代时间

2.mysql 8.x版本数据库性能提升介绍

三.全新JDBC核心API

3.1.引入mysql-jdbc驱动jar

3.2.jdbc基本使用步骤(6步)

3.3.基于statement的案例展示:

3.4.JDBC核心API的参数详解:

3.5.基于statement方式的问题:

1.案例演示:

2.statement方式存在的问题:

3.如何解决statement方式的问题?

3.6.基于preparedStatement方式优化

3.7.基于preparedStatement方式演示增删改查

3.8.preparedStatement使用方法总结

四.全新的JDBC扩展提升

4.1.自增主键Id的回显实现

4.2.批量数据插入性能提升

4.3.jdbc中数据库事务的实现

五.数据库连接池技术----Druid

5.1.连接性能消耗问题分析

5.2.数据库连接池的作用

5.3.市面常见的连接池产品对比

5.4.Druid的使用

六.JDBC优化

6.1.jdbc工具类封装1.0

6.2.jdbc工具类封装2.0

6.3.Dao层封装


一.前言

1.课程需要哪些软件、jar包、前置技术?

2.为什么学习JDBC技术?

二.全新的JDBC技术概述

2.1.jdbc概念总结

1.jdbc是(Java Database Connectivity)单词的缩写,翻译为java连接数据库

2.jdbc是java程序连接数据库的技术统称

3.jdbc由java语言的规范(接口)和各个数据库厂商的实现驱动(jar)组成

4.jdbc是一种典型的面向接口编程

5.jdbc的优势:

​ a.只需要学习jdbc规范接口的方法,即可操作所有的数据库软件

​ b.项目中期切换数据库软件,只需要更换对应的数据库驱动jar包,不需要更改代码

2.2.jdbc核心api和使用路线

jdbc技术组成

1.jdk下jdbc规范接口, 存储在java.sql和javax.sql包中的api

​ 为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。

2.各个数据库厂商提供的驱动jar包

​ 因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

3.jar包是什么?

​ java程序打成的一种压缩包格式,你可以将这些jar包引入你的项目中,然后你可以使用这个java程序中类和方法以及属性了!

2.3.mysql软件介绍

1.mysql软件知名版本迭代时间

2.mysql 8.x版本数据库性能提升介绍

三.全新JDBC核心API

3.1.引入mysql-jdbc驱动jar

1.驱动jar的版本选择

2.java工程导入依赖

第一步:创建一个普通的java工程(注意:不是maven工程)

idea怎样创建一个java项目?_二十一世紀難民的博客-优快云博客

第二步:准备jar包

jdbc的源码包下载地址:

MySQL :: Download Connector/J/

jdbc的jar包下载地址:

https://mvnrepository.com/artifact/mysql/mysql-connector-java

第三步:将jdbc的jar包导入当前项目

1.1.创建lib目录,并将jdbc的jar包复制到lib目录下,之后按照如下操作:

1.2.确认

1.3.添加成功

3.2.jdbc基本使用步骤(6步)

1.注册驱动
2.获取连接
3.创建statement对象
4.1.准备SQL语句
4.2.发送SQL语句并获取返回结果
5.进行结果集解析
6.关闭资源

3.3.基于statement的案例展示:

package jdbc.statement;

import com.mysql.jdbc.Driver;

import java.sql.*;

public class one {
    public static void main(String[] args) throws SQLException {
        /*1.注册驱动
        依赖选择:
            如果驱动版本8+,就导入com.mysql.cj.jdbc.Driver这个包
            如果驱动版本5+,就导入com.mysql.jdbc.Driver这个包
         */
        DriverManager.registerDriver(new Driver());
        /*2.获取连接
            java程序要和数据库建立连接
            java程序,连接数据库时是调用的某个方法,该方法需要填入连接数据库的基本信息
                数据库ip地址:127.0.0.1
                数据库端口号:3306
                账户:root
                密码:123456
                连接数据库的名称:crm
         */
        //java.sql 接口 = 实现类
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        //3.创建statement对象
        Statement statement = connection.createStatement();
        //4.1.准备SQL语句
        String sql = "select * from user";
        //4.2.发送SQL语句并获取返回结果
        ResultSet resultSet = statement.executeQuery(sql);
        //5.进行结果集解析
        while (resultSet.next()){
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            String name = resultSet.getString("name");
            System.out.println("id:"+id+"\t用户名:"+username+"\t昵称:"+name);
        }
        //6.关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

测试结果:

【附】数据库字段信息:

3.4.JDBC核心API的参数详解:

代码案例展示:

package jdbc.statement;



import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;

/**
 * TODO:
 *  输入账号和密码,进入数据查询信息(t_user),反馈登录成功还是登录失败
 *
 * TODO:
 *  1.键盘输入事件,收集账号和密码信息
 *  2.注册驱动
 *  3.获取连接
 *  4.创建statement
 *  5.发送查询SQL语句,并获取返回结果
 *  6.结果判断,显示登录成功还是失败
 *  7.关闭资源
 */
public class two {
    public static void main(String[] args) throws Exception {
        //1.键盘输入事件,收集账号和密码信息
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入账户名:");
        String username = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();

        //2.注册驱动
        /**
         * 方案一弊端详解:
         *  DriverManager.registerDriver(new Driver())----这句代码会注册两次驱动:
         *      DriverManager.registerDriver()----这是静态方法,会注册一次驱动;
         *      Driver.static{ DriverManager.registerDriver() }----又注册一次驱动;
         *  开启两次驱动会带来JVM性能的消耗,需要想办法解决----即只注册一次驱动,只触发一次静态代码块
         *  触发静态代码块:
         *      类加载机制:即类加载大的时刻,会触发静态代码块!
         *      类的三个时机:1.加载【class文件被加载,成为JVM的class对象】
         *                   2.连接【验证(检查文件类型)->准备(静态变量默认值)->解析(触发静态代码块)】
         *                   3.初始化(静态属性赋真实值)
         *      如何触发类的加载?
         *          1.new 关键字
         *          2.调用静态方法
         *          3.调用静态属性
         *          4.接口
         *          5.反射
         *          6.子类触发父类
         *          7.程序的入口main
         *
         *
         */
        //方案一:淘汰
        //DriverManager.registerDriver(new Driver());
        //方案二:反射
        //当前驱动版本为5.0.8,写法如下:
        Class.forName("com.mysql.jdbc.Driver");
        //如果选择的驱动版本8+,那么就该写为:
        //Class.forName("com.mysql.jdbc.cj.Driver");

        //3.获取连接(3种方法)
        /**
         * 核心参数:
         *     1.数据库软件所在的主机的ip地址:127.0.0.1或localhost
         *     2.数据库软件所在的主机的端口号:3306
         *     3.连接的具体数据库库名:crm
         *     4.连接的账号:root
         *     5.连接的密码:123456
         *     6.可选信息:无
         *
         * 参数:String url  数据库软件所在的信息,连接具体的数据库,以及其他可选信息!
         *                  语法:jdbc:数据库管理软件名称[mysql或oracle]://ip地址或主机名:port端口号/数据库名?key=value
         *                        & key=value & key=value 可选信息!
         *                  具体案例:
         *                      案例一:jdbc:mysql://127.0.0.1:3306/crm
         *                      案例二:jdbc:mysql://localhost:3306/crm
         *                      案例三:jdbc:oracle://localhost:3306/crm
         *                  如果数据库在本机且端口号为3306,那么可以简写:
         *                      jdbc:mysql://127.0.0.1:3306/crm 简写为 jdbc:mysql:///crm
         */

        //获取连接方法一:
        Connection connection1 =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");

        //获取连接方法二:
        /**
         * Properties info:存储账号和密码
         *                  Properties类似于Map,只不过key和value都是字符串形式
         */
        Properties info = new Properties();
        info.put("user","root");
        info.put("password","123456");
        Connection connection2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/crm",info);

        //获取连接方法三:
        /**
         * String url --> jdbc:数据库软件名://ip:port/具体数据库库名?key=value & key=value 可选信息
         *                案例展示:
         *                    jdbc:mysql://127.0.0.1:3306/crm?user=root&password=123456
         * url的路径属性可选信息:
         *     user=账号&password=密码
         *     8.0.25版本以后,自动识别时区,不用添加【serverTimezone=Asia/Shanghai】
         *     8.0版本以后,默认使用utf-8格式,不用添加【useUnicode=true&characterEncoding=utf8&useSSL=true】
         */
        Connection connection3 =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm?user=root&password=123456" +
                        "&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true");
        Statement statement = connection3.createStatement();

        //4.发送SQL语句(1.编写Sql语句,2.发送Sql语句)
        String sql  = "select * from t_user where username ='"+username+"' and password ='"+password+"';";
        /**
         * SQL分类:DDL(容器创建、修改、删除)、DML(插入、删除、修改)、DQL(查询)、DCL(权限控制)、TPL(事务控制语言)
         * statement.executeUpdate(sql)与statement.executeQuery(sql)的区别:
         *     statement.executeUpdate(sql)--适用于非DQL语句,返回值为受影响的行数
         *     statement.executeQuery(sql)--适用于DQL语句,返回值为结果集,结果集中封装了所有符合查询条件的对象
         */
        //发送sql语句
        //int i = statement.executeUpdate(sql);
        //发送sql语句
        ResultSet resultSet = statement.executeQuery(sql);

        //6.结果判断,显示登录成功还是失败,只要有数据存在,就证明密码和账号正确
        if(resultSet.next()){
            System.out.println("登录成功");
        }else {
            System.out.println("登录失败");
        }

        //7.关闭资源
        resultSet.close();
        statement.close();
        connection3.close();
        connection2.close();
        connection1.close();
    }
}

 数据库字段与数据展示:

测试结果:

3.5.基于statement方式的问题:

1.案例演示:

在3.4的案例中输入一个表中不存在的账户:

sql语句变成了这样:

表中不存在的账户也能登录,这就是statement的sql注入问题。

2.statement方式存在的问题:

1.SQL语句需要字符串拼接,比较麻烦

2.只能拼接字符串类型,其他的数据库类型无法处理

3.可能发生注入攻击----动态值充当了SQL语句结构,影响了原有的查询结果。

3.如何解决statement方式的问题?

用preparedStatement方式优化。

3.6.基于preparedStatement方式优化

用preparedStatement方式优化statement方式存在的问题。

代码案例:

package jdbc.preparedStatement;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;

/**
 * TODO:
 *  1.使用preparedStatement演示防止注入攻击
 *  2.演示preparedStatement的使用流程
 */
public class one {
    public static void main(String[] args) throws Exception {
        //1.键盘输入事件,收集账号和密码信息
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入账户名:");
        String username = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();
        //2.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //3.获取连接
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm?user=root&password=123456" +
                        "&serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true");

        /**
         * TODO:Statement方式与prepareStatement方式比较
         *  Statement方式
         *      1.创建statement
         *      2.拼接SQL语句
         *      3.发送SQL语句,并且获取返回结果
         *  prepareStatement方式
         *      1.编写SQL语句结果,SQL语句中不包含动态值部分,动态值部分用占位符"?"代替
         *      2.创建prepareStatement,并且传入动态值
         *      3.给占位符"?"赋值
         *      4.发送SQL语句即可,并获取返回结果
         *
         */
        //4.编写SQL语句并用prepareStatement发送SQL
        //4.1编写SQL语句结果
        String sql  = "select * from t_user where username = ? and password = ?";
        //4.2.创建预编译prepareStatement并设置SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //4.3.给占位符"?"赋值
        /**
         * TODO:参数的意义
         *  参数1:parameterIndex---->给SQL语句中的第几个占位符赋值,下标值是从1开始的
         *  参数2:object---->给SQL语句中的第几个占位符具体赋什么值
         */
        preparedStatement.setObject(1,username);
        preparedStatement.setObject(2,password);
        //4.4.发送SQL语句即可,并获取返回结果
        /**
         * preparedStatement与statement的区别在于,前者不用再传入sql语句参数了
         *  preparedStatement.executeQuery();
         *  statement.executeQuery(sql);
         */
        ResultSet resultSet = preparedStatement.executeQuery();
        //5.结果判断,显示登录成功还是失败,只要有数据存在,就证明密码和账号正确
        if(resultSet.next()){
            System.out.println("登录成功");
        }else {
            System.out.println("登录失败");
        }
        //7.关闭资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

 测试结果:

5

3.7.基于preparedStatement方式演示增删改查

增删改

package jdbc.preparedStatement;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class PScrud {
    @Test
    public void PSinsert() throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        //3.编写SQL语句结果,动态值用"?"号代替
        String sql = "INSERT INTO t_user (id,username,`password`) VALUES (?,?,?)";
        //4.创建preparedStatement,并传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.给占位符赋值
        preparedStatement.setObject(1,5);
        preparedStatement.setObject(2,"长江");
        preparedStatement.setObject(3,123456);
        //6.发送SQL语句,i代表受影响的行数
        int i = preparedStatement.executeUpdate();
        //7.输出结果
        if(i>0){
            System.out.println("添加成功");
        }else {
            System.out.println("添加失败");
        }
        //8.关闭资源
        preparedStatement.close();
        connection.close();
    }

    @Test
    public void PSdelete() throws Exception{
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        //3.编写SQL语句结果,动态值用"?"号代替
        String sql = "DELETE FROM t_user WHERE id = ?";
        //4.创建preparedStatement,并传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.给占位符赋值
        preparedStatement.setObject(1,5);
        //6.发送SQL语句,i代表受影响的行数
        int i = preparedStatement.executeUpdate();
        //7.输出结果
        if(i>0){
            System.out.println("删除成功");
        }else {
            System.out.println("删除失败");
        }
        //8.关闭资源
        preparedStatement.close();
        connection.close();
    }

    @Test
    public void PSupdate() throws Exception{
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        //3.编写SQL语句结果,动态值用"?"号代替
        String sql = "UPDATE t_user SET username = ? WHERE id = ? ";
        //4.创建preparedStatement,并传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.给占位符赋值
        preparedStatement.setObject(1,"山海经");
        preparedStatement.setObject(2,3);
        //6.发送SQL语句,i代表受影响的行数
        int i = preparedStatement.executeUpdate();
        //7.输出结果
        if(i>0){
            System.out.println("修改成功");
        }else {
            System.out.println("修改失败");
        }
        //8.关闭资源
        preparedStatement.close();
        connection.close();
    }
}

 查询案例:

package jdbc.preparedStatement;

import org.junit.Test;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class PScrud {

    //查询数据并将查询到的数据先存到Map中,再将Map添加到集合中
    @Test
    public void PSselect() throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        //3.编写SQL语句结果,动态值用"?"号代替
        String sql  = "select * from t_user";
        //4.创建preparedStatement,并传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.发送SQL并获取查询到的数据结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //获取返回的结果集中列的信息对象
        ResultSetMetaData metaData = resultSet.getMetaData();
        //得到返回结果集有多少列----即数据库中一行完整的数据拥有的字段个数
        int columnCount = metaData.getColumnCount();
        ArrayList<Map> list = new ArrayList<>();
        while (resultSet.next()){
            HashMap map = new HashMap();
            
            //自动遍历结果集的列数,注意要从1开始且小于等于总列数
            for (int i = 1;i <= columnCount;i++){
                //获取指定列下角标的值!
                Object value = resultSet.getObject(i);
                //获取指定列下角标的列的名称
                String columnLabel = metaData.getColumnLabel(i);
                map.put(columnLabel,value);
            }
            //一行完整数据的所有内容均被存到了map中
            //将map存储到集合中即可
            list.add(map);
        }
        System.out.println("list="+list);
        //7.关闭资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
 }

测试结果:

3.8.preparedStatement使用方法总结

1.使用步骤总结

//1.注册驱动
//2.获取连接
//3.编写SQL语句
//4.创建preparedStatement对象并传入SQL语句结构
//5.占位符赋值
//6.发送查询SQL语句,并获取返回结果
//7.解析返回的结果集
//8.关闭资源

 2.使用API总结

//1.注册驱动
方案1:调用静态方法,但是会注册2次驱动,降低系统性能
DriverManager.registerDriver(new Driver());
方案2:反射触发(推荐)
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection connection =DriverManager.getConnection();
3个参数:public static Connection getConnection(String url,String user, String password)
2个参数:public static Connection getConnection(String url,java.util.Properties info)
1个参数:public static Connection getConnection(String url)
//3.编写SQL语句
//4.创建preparedStatement对象并传入SQL语句结构
静态:
    Statement statement = connection.createStatement();
预编译:
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.发送查询SQL语句,并获取返回结果
int i = preparedStatement.executeUpdate();//处理非DQL语句,i代表受影响的行数
ResultSet resultSet = preparedStatement.executeQuery();//处理DQL语句
//6.解析返回的结果集
while (resultSet.next()){}
//7.关闭资源
close();

四.全新的JDBC扩展提升

4.1.自增主键Id的回显实现

作用:在多表关联插入数据的业务场景下,一般主表的主键都是自动生成的,所以在插入数据之前无法知道这条数据的主键值,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术。

案例展示:

package jdbc.preparedStatement;

import org.junit.Test;

import java.sql.*;

public class PSInsertReturnId {
    /**
     * TODO:向t_user表中插入一条数据,并返回自增主键Id的值
     * 
     * TODO:使用总结:
     *  1.创建preparedStatement对象的时候,告知preparedStatement对象携带回数据库自增的主键Id
     *     PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); 
     *  2.获取PreparedStatement对象装载主键值的结果集对象,一行一列,获取对应的数据即可
     *     ResultSet resultSet = preparedStatement.getGeneratedKeys();
     * @throws Exception
     */
    @Test
    public void PSselect() throws Exception {
        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        //3.编写SQL语句结果,动态值用"?"号代替
        String sql = "INSERT INTO t_user (username,`password`) VALUES (?,?)";
        //4.创建preparedStatement,并传入SQL语句结果,同时要求preparedStatement对象将本次插入的数据对应的Id的值带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        //5.给占位符赋值
        preparedStatement.setObject(1,"长江黄河");
        preparedStatement.setObject(2,123456);
        //6.发送SQL语句并返回结果,i代表受影响的行数
        int i = preparedStatement.executeUpdate();
        //7.输出结果
        if(i>0){
            System.out.println("添加成功");
            //可以获取回显的主键
            //获取preparedStatement对象装载的结果集对象,结果集对象的结构为一行一列
            ResultSet resultSet = preparedStatement.getGeneratedKeys();
            resultSet.next();//移动指针到有效数据位置
            int id = resultSet.getInt(1);
            System.out.println("本次插入的数据在表中对应的Id值为:"+id);
        }else {
            System.out.println("添加失败");
        }
        //8.关闭资源
        preparedStatement.close();
        connection.close();
    }
}

测试结果:

4.2.批量数据插入性能提升

代码案例:

@Test
public void PSInsertAll() throws Exception {
    //1.注册驱动
    Class.forName("com.mysql.jdbc.Driver");
    //2.获取连接
    Connection connection = DriverManager.getConnection(
            "jdbc:mysql:///crm?rewriteBatchedStatements=true","root","123456");
    //3.编写SQL语句结果,动态值用"?"号代替
    String sql = "INSERT INTO t_user (username,`password`) VALUES (?,?)";
    //4.创建preparedStatement,并传入SQL语句结果,同时要求preparedStatement对象将本次插入的数据对应的Id的值带回来
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //5.给占位符赋值
    for (int j = 0;j < 20;j++){
        preparedStatement.setObject(1,"长江黄河"+j);
        preparedStatement.setObject(2,123+j);
        /**
         * 暂时先不执行,将值追加到values后面,此时sql为:
         * INSERT INTO t_user (username,`password`) VALUES (?,?),(?,?),(?,?),(?,?),(?,?)......
         */
        preparedStatement.addBatch();
    }
    //6.发送SQL语句,执行批量(插入)操作并返回结果,ints代表受影响的行数
    int[] ints = preparedStatement.executeBatch();
    //7.输出结果
    if(ints.length>0){
        System.out.println("添加成功,本次添加的数据为:"+ints.length+"条");
    }else {
        System.out.println("添加失败");
    }
    //8.关闭资源
    preparedStatement.close();
    connection.close();
}

 测试结果:

4.3.jdbc中数据库事务的实现

案例展示:

必须保证数据库的存储引擎为InnoDB,因为InnoDB是支持事务的。

一个事务的最基本要求,必须是处在同一个Connection(连接对象)。

package jdbc.preparedStatement;

import org.junit.Test;

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

class transactionDao{
    public void addMoney(Double money,String username,Connection connection) throws Exception {
        String sql = "UPDATE t_user set money = money + ? where username = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,username);
        //int a = 2/0;
        preparedStatement.executeUpdate();
        preparedStatement.close();
        System.out.println("加钱成功");
    }

    public void reduceMoney(Double money,String username,Connection connection) throws Exception {
        String sql = "UPDATE t_user set money = money - ? where username = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,username);
        preparedStatement.executeUpdate();
        preparedStatement.close();
        System.out.println("扣钱成功");
        //int a = 2/0;
    }
}

public class transactionTest {

    @Test
    public void zhuanzhangTest() throws Exception {
        Double money = new Double(50);
        zhuanzhang("ls","zs",money);
    }


    public void zhuanzhang(String addAccount,String reduceAccount,Double money) throws Exception {
        transactionDao dao = new transactionDao();
        //1.注册驱动

        Class.forName("com.mysql.jdbc.Driver");
        //2.获取连接
        Connection connection=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/crm","root","123456");
        try{
            connection.setAutoCommit(false);
            dao.addMoney(money,addAccount,connection);
            System.out.println("====================");
            dao.reduceMoney(money,reduceAccount,connection);
            connection.commit();
            System.out.println("转账成功");
        }catch (Exception e){
            System.out.println("转账失败");
            e.printStackTrace();
            //回滚数据
            try {
                connection.rollback();
                System.out.println("事务回滚成功");
            } catch (SQLException e1) {
                System.out.println("事务回滚失败");
                e1.printStackTrace();
            }
        }finally {
            connection.close();
        }
    }
}

 测试结果:

五.数据库连接池技术----Druid

5.1.连接性能消耗问题分析

连接的生命周期:创建连接、使用连接、销毁连接。如果创建连接花费时间和销毁连接的时间远大于使用连接的时间,这个程序的效率就极低。改进方案:数据库连接池技术----Druid。

5.2.数据库连接池的作用

总结缺点:
(1)不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃断开,
连接的利用率太低,太浪费。
(2)对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制
,很容易导致数据库服务器崩溃。

我们就希望能管理连接。
- 我们可以建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始,
  我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,
  不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。
- 可以提高连接的使用率。当池中的现有的连接都用完了,那么连接池可以向服务器申
  请新的连接放到池中。
- 直到池中的连接达到“最大连接数”,就不能在申请新的连接了,如果没有拿到连接的用户只能等待。

5.3.市面常见的连接池产品对比

JDBC 的数据库连接池使用 javax.sql.DataSource接口进行规范,所有的第三方连接池
     都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收
     连接方法都一样,不同的只有性能和扩展功能!
    - DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG
    - C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
    - Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,
       稳定性较c3p0差一点
    - Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身
      的数据库连接池,妥妥国货之光!!!!

参考链接:

尚硅谷全新8.x版本JDBC数据库连接技术

5.4.Druid的使用

1.导入Druid的jar包。

2.Druid的2种使用方式

2.1.硬编码方式

代码展示:

/**
 * 创建druid连接池对象,使用硬编码进行核心参数设置!
 *   必须参数: 账号
 *             密码
 *             url
 *             driverClass
 *   非必须参数:
 *           初始化个数
 *           最大数量等等  不推荐设置
 * TODO:直接使用代码设置连接池的连接参数方式
 *      *  1.创建一个druid连接池对象
 *      *  2.设置连接池参数
 *      *      参数分为必须参数和非必循参数
 *      *  3.获取连接[这是通用方法,所有连接池都一样]
 *      *  4.回收连接[这是通用方法,所有连接池都一样]
 */
    @Test
    public void druidHard() throws SQLException {
        //连接池对象
        DruidDataSource dataSource = new DruidDataSource();

        //设置四个必须参数:1.注册驱动;2.url地址;3.user;4.密码
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/crm");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        //设置非必须参数
        dataSource.setInitialSize(5);//初始化连接数
        dataSource.setMaxActive(10);//最大的数量

        //获取连接
        Connection connection = dataSource.getConnection();

        // 数据库CRUD
        //3.编写SQL语句结果,动态值用"?"号代替
        String sql = "INSERT INTO t_user (username,`password`,money) VALUES (?,?,?)";
        //4.创建preparedStatement,并传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //5.给占位符赋值
        preparedStatement.setObject(1,"长江");
        preparedStatement.setObject(2,123456);
        preparedStatement.setObject(3,500.0);
        //6.发送SQL语句,i代表受影响的行数
        int i = preparedStatement.executeUpdate();
        //7.输出结果
        if(i>0){
            System.out.println("添加成功");
        }else {
            System.out.println("添加失败");
        }
        //连接池提供的连接,此时close()方法是回收连接的意思
        connection.close();
    }

测试结果:

2.2.软编码方式

代码案例

1.配置文件内容:

2.代码

package jdbc.druid;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Properties;

public class DruidDemo {
/**
 * 不直接在java代码编写配置文件!
 * 利用工厂模式,传入配置文件对象,创建连接池!
 *
 * @throws Exception
 */
@Test
public void druidSoft() throws Exception {
    //1.读取外部配置文件Properties
    Properties properties = new Properties();
    //2.src下的文件可以用类加载器的方法加载,要填写配置文件的相对于src目录的相对路径
    InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("jdbc/druid/druid.properties");
    properties.load(ips);
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

    Connection connection = dataSource.getConnection();
    // 数据库CRUD
    //3.编写SQL语句结果,动态值用"?"号代替
    String sql = "INSERT INTO t_user (username,`password`,money) VALUES (?,?,?)";
    //4.创建preparedStatement,并传入SQL语句结果
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    //5.给占位符赋值
    preparedStatement.setObject(1,"长江与黄河");
    preparedStatement.setObject(2,123456);
    preparedStatement.setObject(3,1000.0);
    //6.发送SQL语句,i代表受影响的行数
    int i = preparedStatement.executeUpdate();
    //7.输出结果
    if(i>0){
        System.out.println("添加成功");
    }else {
        System.out.println("添加失败");
    }
    preparedStatement.close();
    connection.close();
  }
}

 项目架构:

测试结果:

六.JDBC优化

6.1.jdbc工具类封装1.0

无法保证事务的管理的方式:

package jdbc.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * jdbc连接池工具类中包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 * 
 * 建议:
 *  工具类的方法,携程静态方法,外部方便调用
 *  
 * 实现:
 *  属性:连接池对象(实例化一次)
 *  方法:
 *      1.对外提供连接的方法
 *      2.回收外部传入连接方法
 */
public class jdbcUtils {
    //准备连接池对象
    private static DataSource ds;
    //静态代码块
    static{
        try {
            //初始化连接池对象
            Properties properties = new Properties();
            InputStream ips =
                    jdbcUtils.class.getClassLoader().getResourceAsStream("jdbc/druid/druid.properties");
            properties.load(ips);
            ds = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        //这么写,不能保证同一个线程,两次getConnection()得到的是同一个Connection对象
        //如果不能保证是同一个连接对象,就无法保证事务的管理
        return ds.getConnection();
    }

    public static void free(Connection conn) throws SQLException {
        conn.close();//还给连接池
    }
}

1

6.2.jdbc工具类封装2.0

能保证事务的管理的方式:

这个工具类的作用就是用来给所有的SQL操作提供“连接”,和释放连接。 这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。 这里使用DataSource的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等

工具类:

package jdbc.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * jdbc连接池工具类中包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 *
 * 建议:
 *  工具类的方法,携程静态方法,外部方便调用
 *
 * 实现:
 *  属性:连接池对象(实例化一次)
 *  方法:
 *      1.对外提供连接的方法
 *      2.回收外部传入连接方法
 *
 *TODO:
 *  利用线程本地变量,存储连接对对象(Connection对象)信息!确保一个线程中的多个方法可以获取同一个Connection对象
 *  优势:
 *      事务操作的时候,service和dao属于同一个线程中,不用再传递Connection对象了
 *      各个方法间可以调用getConnection自动获取的是同一个连接池
 */
public class jdbcUtils {
    //准备连接池对象
    private static DataSource dataSource = null;
    //创建线程本地变量:
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();
    //静态代码块
    static{
        try {
            //初始化连接池对象
            Properties properties = new Properties();
            InputStream ips =
                    jdbcUtils.class.getClassLoader().getResourceAsStream("jdbc/druid/druid.properties");
            properties.load(ips);
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 这是工具类对外提供的获取连接的方法
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        //校验线程本地变量中是否存在Connection
        Connection connection = tl.get();
        //第一次线程本地变量中是没有Connection对象的
        if(connection == null){
            connection = dataSource.getConnection();
            System.out.println(connection);
            //将connection存入线程本地变量
            tl.set(connection);
        }
        return connection;
    }

    /**
     * 这是工具类对外提供的回收连接的方法
     * @throws SQLException
     */
    public static void free() throws SQLException {
        Connection connection = tl.get();
        if(connection != null){
            //清空线程本地数据变量
            tl.remove();
            //事务状态回顾
            /**
             * connection.setAutoCommit(false)代表将自动提交事务改为手动提交事务----即开启手动提交事务
             * 此处写connection.setAutoCommit(true)的目的在于:
             *      如果当前线程A获取的本次连接在执行SQL语句时开启了手动提交事务,如不将状态值回归为true,
             *      那么这个连接归还给连接池后,下一个拿到这个连接的线程B,拿到的会是一个需要手动提交事务
             *      的连接,但是线程B中的业务有可能并不需要手动提交事务,因此要将状态值回归为true
             */
            connection.setAutoCommit(true);
            //将连接回收到连接池
            connection.close();
        }
    }
}

配置文件

# properties文件里面的内容是以"key=value"这种形式的键值对格式存在
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://127.0.0.1:3306/crm
initialSize=5

用转账案例测试jdbc连接池工具类代码

package jdbc.utils;

import org.junit.Test;

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

class transactionDao{
    /**
     * 给账户加钱的方法
     * @param money
     * @param username
     * @throws Exception
     */
    public void addMoney(Double money,String username) throws Exception {
        Connection connection = jdbcUtils.getConnection();

        String sql = "UPDATE t_user set money = money + ? where username = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,username);
        //int a = 2/0;
        preparedStatement.executeUpdate();
        preparedStatement.close();
        System.out.println("加钱成功");
        System.out.println(connection);
    }

    /**
     * 给账户扣钱的方法
     *
     * @param money
     * @param username
     * @throws Exception
     */
    public void reduceMoney(Double money,String username) throws Exception {
        Connection connection = jdbcUtils.getConnection();
        String sql = "UPDATE t_user set money = money - ? where username = ?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        preparedStatement.setObject(1,money);
        preparedStatement.setObject(2,username);
        preparedStatement.executeUpdate();
        preparedStatement.close();
        System.out.println("扣钱成功");
        //int a = 2/0;
    }
}

public class transactionTest {

    @Test
    public void zhuanzhangTest() throws Exception {
        Double money = new Double(50);
        zhuanzhang("ls","zs",money);
    }

    /**
     * TODO:一个事务的最基本要求,必须是处在同一个Connection(连接对象)
     *
     * TODO:事务总结:
     *          1.添加事务的地方是在业务方法中
     *          2.在try代码块中开启事务和提交事务,在catch代码块中回滚事务
     *          3.将connection传入dao层即可!dao层只负责使用,不要做connection.close()处理
     *
     * @param addAccount
     * @param reduceAccount
     * @param money
     * @throws Exception
     */
    public void zhuanzhang(String addAccount,String reduceAccount,Double money) throws Exception {
        transactionDao dao = new transactionDao();
        Connection connection = jdbcUtils.getConnection();
        try{
            //将事务从自动提交改为手动提交----即开启手动提交事务
            //一个事务的最基本要求,必须是处在同一个Connection(连接对象)
            connection.setAutoCommit(false);
            //执行数据库动作
            dao.addMoney(money,addAccount);
            System.out.println("====================");
            dao.reduceMoney(money,reduceAccount);
            //事务提交
            connection.commit();
            System.out.println("转账成功");
        }catch (Exception e){
            System.out.println("转账失败");
            e.printStackTrace();
            try {
                //事务回滚
                connection.rollback();
                System.out.println("事务回滚成功");
            } catch (SQLException e1) {
                System.out.println("事务回滚失败");
                e1.printStackTrace();
            }
        }finally {
            jdbcUtils.free();
        }
    }
}

5测试

6.3.Dao层封装

package jdbc.utils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * TODO:封装dao层数据重复代码,封装2个方法
 *  1.简化非DQL语句的方法
 *  2.简化DQL语句的方法
 */
public abstract class BaseDao {
    /**
     * 简化非DQL语句的方法
     *
     * @param sql   带占位符的SQL语句
     * @param parms 占位符赋值
     * @return 执行SQL语句影响的行数
     * @throws SQLException
     */
    public int executUpdate(String sql, Object... parms) throws SQLException {
        //获取连接
        Connection connection = jdbcUtils.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        //占位符赋值
        //可变参数可以当作数组使用
        for (int i = 1; i <= parms.length; i++) {
            preparedStatement.setObject(i, parms[i - 1]);
        }
        //发送SQL语句----DML类型
        int rows = preparedStatement.executeUpdate();

        preparedStatement.close();
        //是否回收连接,需要考虑当前连接对象有没有开启手动提交事务
        if (connection.getAutoCommit()) {
            //没有开启手动提交事务,正常回收连接
            jdbcUtils.free();
            //如果开启了手动提交事务,那么这里不做处理,在业务层去处理事务
        }
        return rows;
    }

    /**
     * 简化DQL语句的方法
     *
     * @param clazz:要接收值的实体类集合的模板对象
     * @param sql:sql查询语句,要求列名或者别名等于实体类的属性名
     * @param parms:占位符的值
     * @param <T>:声明结果的类型!
     * @return :查询的实体类集合
     * @throws SQLException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchFieldException
     *
     * <T>在不确定类型的情况下,可以声明一个泛型
     *      1.确定泛型:User.class T = User;
     *      2.要使用反射技术赋值
     *      public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... parms)
     */
    public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... parms) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        Connection connection = jdbcUtils.getConnection();
        //创建preparedStatement,并传入SQL语句结果
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        if (parms != null && parms.length != 0) {
            //占位符赋值
            //可变参数可以当作数组使用
            for (int i = 1; i <= parms.length; i++) {
                preparedStatement.setObject(i, parms[i - 1]);
            }
        }
        //发送SQL并获取查询到的数据结果集
        ResultSet resultSet = preparedStatement.executeQuery();
        //获取返回的结果集中列的信息对象
        ResultSetMetaData metaData = resultSet.getMetaData();
        //得到返回结果集有多少列----即数据库中一行完整的数据拥有的字段个数
        int columnCount = metaData.getColumnCount();
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            //调用类的无参构造函数实例化对象
            T t = clazz.newInstance();
            for (int i = 1; i <= columnCount; i++) {
                //获取指定列下角标的值!
                Object value = resultSet.getObject(i);
                //获取指定列下角标的列的名称
                String columnLabel = metaData.getColumnLabel(i);
                //反射,给对象的属性值赋值
                Field field = clazz.getDeclaredField(columnLabel);
                //属性设置为true,就可以用反射打破private的修饰限制
                field.setAccessible(true);
                /**
                 * 赋值:
                 *   参数一:要赋值的对象,如果属性为静态的,可以为null
                 *   参数二:具体的属性值
                 */
                field.set(t, value);
            }
            list.add(t);
        }
        //关闭资源
        resultSet.close();
        preparedStatement.close();
        //连接中没有事务,可以关闭连接
        if(connection.getAutoCommit()){
            jdbcUtils.free();
        }
        return list;
    }
}

 工具类优化前后比较

【补充】请问List<Map>与List<T>有什么区别?

List<Map>:

        1.Map中的key和value是自定义的,不用预先设计好

        2.没有数据校验机制

        3.不支持反射操作

List<T>:形如List<User>

        1.User中的数据类型和变量名要预先设计好,且必须和数据库中的字段一一对应

        2.有数据校验机制

        3.支持反射操作

总结:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值