1. # 数据库阶段
  2. 1. sql入门: 2
  3. 2. jdbc(java程序访问mysql服务) : 1
  4. 1). 之前: 用可视化工具或dos作为客户端
  5. 2). 今天: 如何将java程序作为客户端
  6. 3. javase增强: 2
  7. 1). 反射 + 注解 + xml配置文件
  8. 2). 为框架mybatis铺垫
  9. 4. mybatis框架: 3
  10. 1). mybatisjdbc封装
  11. 2). jdbc是技术标准,mybatisjdbc标准实现方案的封装

第一章 JDBC概述

java程序访问关系型数据库(mysql)的技术

1.1 什么是JDBC


Java DataBase Connectivity:Java的数据库连接技术,它是一组Java语言编写的接口API,给多种数据库提供统一的访问接口,是Java访问数据库的标准规范

(JDBC理解成接口)

JDBC访问数据库需要驱动程序,利用驱动程序和数据库进行通信。驱动程序是Java语言编写的实现了JDBC接口的类,由第三方数据库厂商提供

(驱动理解成JDBC接口的实现类)

1.2 JDBC的特点


  1. 它是一组接口,我们的程序是面向接口开发
  2. 不需要关注具体的实现细节,驱动程序(JDBC接口的实现类)由数据库厂商提供

02-JDBC - 图1

# 面向接口编程(多态)
0. 多态公式: 父类引用指向子类对象,父类调用方法,执行的是子类重写的方法
    接口类型 变量 = 此接口的实现类对象
    Animal  a = new Dog();
1. 接口 : 统一的规范
//接口是给一类事物下定义(制定标准,规范)
interface Animal{
    void run(); // 可以跑
    void eat(); // 可以吃
}
// 实现类1(实现接口: 实现一套标准,规范)
class Dog implements Animal{
    void run(){ // 狗飞快的跑 };
    void eat(){ // 狗吃骨头}
}
// 实现类2(实现接口: 实现一套标准,规范)
class Cat implements Animal{
    void run(){ // 猫很慢的跑 };
    void eat(){ // 猫吃鱼}
}
2. 面向接口编程是面向对象中多态特性的一种体现
        // 接口类型 变量 = 接口实现类对象 (必然)
    Animal a = Animal实现类;
    a.eat(); // 动物在吃东西
    a.run(); // 动物在跑

    面向对象: 解决问题关注的对象本身,不关注实现的过程
    面向接口: 隐藏具体实现,暴露接口, 开发者只需要关注接口和其方法含义即可
                (为啥不需要关心具体实现: 具体实现太多了,关心不过来)

3. 面向接口编程的好处
    0). 场景
                                                         mysql  (mysql驱动: JDBC实现)
        java官方(JDBC接口: java程序如何访问数据库)             oracle  (oracle驱动)
                                                         sqlserver 
                                                          ....
        不同的数据库(底层不一样:解析器,引擎),具体的访问方法也不一样
        java官方无法逐一实现对应的访问方法,干脆不实现,只提供标准(接口),让数据库厂商去实现
        数据库厂商实现之后(实现类),提供驱动包
   1). 从java设计者(sun公司) 来说
           减少了工作量,只需要制定标准(接口),无需具体实现
   2). 从java开发者(我们:程序员)来说
           1). 只需要关注接口和方法含义, 无需关注接口的具体实现,降低学习成本
           2). 程序不易耦合,易于扩展(切换数据库,代码几乎无需改变,只需要切换对应数据库的驱动)
4. java开发者的工作
    1). 学习jdbc规范: 我们就知道如何让java程序访问关系型数据库
    2). 操作: 
            比如我们想要访问mysql,在项目中导入mysql驱动就可以了
            比如我们想要访问oracle,在项目中导入oracle驱动就可以了

1.3 面向接口开发的优势

  1. 程序不产生耦合
  2. 易于程序扩展
  3. 方便程序维护

第二章 JDBC的API

2.1 API介绍

JDBC开发使用到的包


会使用到的包 说明
java.sql JDBC的核心包
javax.sql 数据库连接池的包(extension扩展)
数据库的驱动 数据库厂商提供

JDBC的核心接口和类


接口或类 作用
DriverManager类 注册驱动(jdbc4.0默认,给JDBC接口类型的变量赋值)和创建数据库连接
Connection接口 表示一个数据库连接对象
Statement接口 表示一个发送给数据库执行的SQL语句
ResultSet接口 数据库返回的查询结果集

JDBC访问数据库的步骤

02-JDBC - 图2

  1. 利用DriverManager创建数据库连接
  2. 获取一个连接对象,客户端利用Statement对象发送SQL到数据库
  3. 数据库执行SQL语句
  4. 数据库返回查询的结果集ResultSet给客户端

入门案例

02-JDBC - 图3

/*
    JDBC入门案例
        1. 作用: 先让大家体会一下 java程序如何访问mysql数据库
        2. 实现思路:
            1). 理解jdbc
            2). 导入mysql驱动 (mysql数据库厂商提供的,mysql官网下载的)
        3. 做法:
            1). 找到jar包
                资料/mysql-connector-java-5.1.37-bin.jar
            2). 导入jar包
                I. 在工程根目录下创建一个lib目录
                II. 将mysql-connector-java-5.1.37-bin.jar放到lib目录下
                III. 右单击jar包 -> add as library -> ok
            3). 编写jdbc代码
 */
public class JdbcDemo {

    public static void main(String[] args) throws SQLException {
        //1. 注册驱动
            // 注意包: com.mysql.jdbc.Driver
        DriverManager.registerDriver(new Driver());
        //2. 获取连接
            // mysql -h 127.0.0.1 -P 3306 -u root -p
        String url = "jdbc:mysql://localhost:3306/day02_3";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 执行sql,返回结果
        String sql = "select * from user";
        Statement statement = conn.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        //4. 处理结果
        while(resultSet.next()){
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            int sex = resultSet.getInt("sex");
            System.out.println(id + "," + name +"," + age + "," + sex);
        }
        //5. 释放连接
        resultSet.close();
        statement.close();
        conn.close();
    }
}
# 数据准备
create database day02_3;
use day02_3;
CREATE TABLE `user` (
  `id` int(11) NOT NULL DEFAULT '0',
  `username` varchar(32) DEFAULT NULL,
  `password` varchar(32) DEFAULT NULL,
  `sex` varchar(6) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  KEY `username` (`username`)
);

2.2 DriverManager类

作用


  1. 注册驱动,JDK1.5之后自动注册
  2. 创建数据库连接

JDBC连接数据库的四个参数


JDBC连接数据库的四个参数
用户名 root
密码 root
驱动类全名 例如MySQL驱动:
com.mysql.jdbc.Driver (MyDQL5之前)
com.mysql.cj.jdbc.Driver (MySQL6开始)
连接字符串 jdbc:mysql://localhost:3306/itcast

02-JDBC - 图4

URL后面参数的作用


如果数据库出现乱码,可以指定参数 ?characterEncoding=utf8,表示让数据库以UTF-8编码来处理数据

jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8

如果服务器是本地,端口号是默认的3306,可以简写为

jdbc:mysql:///itcast?characterEncoding=utf8

DriverManager类中的方法


DriverManager类中的静态方法 描述
Connection getConnection (String url, String user, String password) 根据连接字符串url、用户名、密码获取一个数据库连接对象
Connection getConnection (String url, Properties info) 根据连接字符串url、属性集合获取一个数据库连接对象

应用

package com.itheima02.drivermanager;

import com.mysql.jdbc.Driver;

import java.sql.*;

/*
    TODO 类: DriverManager
         1. static synchronized void registerDriver(java.sql.Driver driver)
             1). 作用: 注册驱动
                让当前项目中的 mysql-connector-java.jar生效
             2). 参数:
                I. 形参: java.sql.Driver接口
                II. 实参: 对应驱动的实现类 (mysql : com.mysql.jdbc.Driver)
             3). 自动注册
                I. 驱动本身的自动注册
                    a. com.mysql.jdbc.Driver驱动类的静态代码块自己注册了自己
                        静态代码块随着类的加载而执行
                    b. 所以只要让com.mysql.jdbc.Driver类加载即可
                        Class.forName("com.mysql.jdbc.Driver"); //明天: 反射
                II. SPI机制自动注册
                    a.要求:  jdbc4.0版本以上 (JDK5以后)
                    b.实现: 程序会自动注册驱动,不需要编写代码
                    c. SPI机制: web阶段会学习
         2. static Connection getConnection (String url, String user, String password)
            1). 作用: 让java程序访问mysql数据库,产生连接 (比喻: 拨打电话并接通 或者 登录过程)
            2). 参数:
                    I. url : mysql服务的访问地址
                        格式-> 协议://ip:port/资源位置?参数
                        例子-> jdbc:mysql://localhost:3306/day02_3?characterEncoding=utf8
                            协议是两个程序通讯的规则: 通讯的方向必须要遵循统一协议 (jdbc:mysql -> 主协议:子协议)
                            ip: mysql服务所在的计算机ip
                            port:  mysql服务运行占用的端口
                            资源位置: java程序要访问的数据仓库在mysql中的位置
                            参数: 传输数据所使用的编码(如果你的java程序读到的数据是乱码,可以加个此参数试试)
                    II. user
                    III. password
                        mysql账户的用户名和密码
 */
public class JdbcDemo {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1. 注册驱动
            // 注意包: com.mysql.jdbc.Driver
//        DriverManager.registerDriver(new Driver()); // 这个代码没有必要了,因为Driver自己注册了
        Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
        //2. 获取连接
            // mysql -h 127.0.0.1 -P 3306 -u root -p
        String url = "jdbc:mysql://localhost:3306/day02_3";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 执行sql,返回结果
        String sql = "select * from user";
        Statement statement = conn.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        //4. 处理结果
        while(resultSet.next()){
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            int sex = resultSet.getInt("sex");
            System.out.println(id + "," + name +"," + age + "," + sex);
        }
        //5. 释放连接
        resultSet.close();
        statement.close();
        conn.close();
    }
}

2.3 Connection接口

作用


表示一个连接对象,获取的是它的子类对象

方法


Connection接口中的方法 描述
Statement createStatement() 创建一个Statement对象,用来发送SQL语句给数据库服务器执行

Connection可以获取预编译对象,以及事务操作

2.4 Statement接口

作用


表示一个数据库执行的SQL语句,获取的是它的子类对象

方法


Statement接口中的方法 描述
boolean execute(String sql) 作用:可以执行任意的SQL语句,常用来执行DDL
返回值:
true:返回的第一个结果是ResultSet对象
false:如果是更新计数或没有结果
int executeUpdate(String sql) 作用:执行增删改操作
返回值:返回影响的记录行数
ResultSet executeQuery(String sql) 作用:用于执行查询操作
返回值:数据库查询的结果集
/*
    # Connection接口 (连接)
        1. Statement statement = conn.createStatement()
            创建一个Statement对象,用来发送SQL语句给数据库服务器执行
        2. 其他作用: 获取预编译对象,还可以进行事务操作

    # TODO: Statement接口 (语句)
        1. 作用: 发送sql到mysql服务器,并返回执行结果
            比喻: 如果把connection看成是java程序客户端和mysql服务期之间的桥梁
                     那么可以把statement看成桥上运行的汽车
                     statement作用: 将java程序想要的内容(通过sql表达)运输给mysql服务器
                     mysql服务器执行sql之后,将结果给statement带回去
        2. 需要大家掌握几个方法
            1). ResultSet resultSet = statement.executeQuery(sql);
                执行DQL语句(查询), 返回结果集(看成一张表)
            2). int count = statement.executeUpdate(sql);
                执行DML语句(增删改 insert,delete,update),返回被影响的行数
 */
public class JdbcDemo {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
        //2. 获取连接
        String url = "jdbc:mysql://localhost:3306/day02_3";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 执行sql,返回结果
        String sql = "select * from user";
            //创建一个Statement对象,用来发送SQL语句给数据库服务器执行
        Statement statement = conn.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        //4. 处理结果
        while(resultSet.next()){
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            int sex = resultSet.getInt("sex");
            System.out.println(id + "," + name +"," + age + "," + sex);
        }
        //5. 释放连接
        resultSet.close();
        statement.close();
        conn.close();
    }
}
/*
    # 此demo演示增删改操作
 */
public class JdbcDemo2 {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
        //2. 获取连接
        String url = "jdbc:mysql://localhost:3306/day02_3";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 执行sql,返回结果
            //创建一个Statement对象,用来发送SQL语句给数据库服务器执行
        Statement statement = conn.createStatement();
//        String sql = "delete from user where id = 4";
//        String sql = "update user set sex=0";
        String sql = "inser into user values(null,'zs',10,1),(null,'ls',11,0)";
        int count = statement.executeUpdate(sql);
        //4. 处理结果
        System.out.println("被影响的行数:" + count);
        //5. 释放连接
        statement.close();
        conn.close();
    }
}

2.5 ResultSet接口

原理图


02-JDBC - 图5

方法


ResultSet接口中的方法 描述
boolean next() 将指针向下移动一行,如果当前行由数据,就返回true;否则,返回false
数据类型 get数据类型(参数) 1. 根据列名
2. 根据列号,从1开始编号

ResultSet接口中的getXXX()方法


方法签名
boolean getBoolean(String columnLabel);
byte getByte(String columnLabel);
short getShort(String columnLabel);
int getInt(String columnLabel);
long getLong(String columnLabel);
float getFloat(String columnLabel);
double getDouble(String columnLabel);
String getString(String columnLabel);

常用的数据类型转换表


02-JDBC - 图6

其中,下面的类都是 java.util.Date 的子类

java.sql.Date
java.sql.Time
java.sql.Timestamp

02-JDBC - 图7

/*
    # TODO: ResultSet接口 (结果集)
        1. 作用: 用来获取查询语句执行结果的入口
        2. 方法:
               1). boolean next()
                    获取下一行数据,如果有返回true,如果没有返回false
               2). String name = resultSet.getString("name");
                    获取当前行数据对应字段的值

                   String 列对应的值 = resultSet.getString("结果集的列名");
 */
public class JdbcDemo {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1. 注册驱动
        Class.forName("com.mysql.jdbc.Driver");// 反射技术: 让这个类加载(明天学习)
        //2. 获取连接
        String url = "jdbc:mysql://localhost:3306/day02_3";
        String username = "root";
        String password = "root";
        Connection conn = DriverManager.getConnection(url, username, password);
        //3. 执行sql,返回结果
        String sql = "select * from user";
            //创建一个Statement对象,用来发送SQL语句给数据库服务器执行
        Statement statement = conn.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        //4. 处理结果
        while(resultSet.next()){
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            int sex = resultSet.getInt("sex");
            System.out.println(id + "," + name +"," + age + "," + sex);
        }
        //5. 释放连接
        resultSet.close();
        statement.close();
        conn.close();
    }
}

释放资源


每次访问数据库结束,都需要释放资源

  1. 需要释放的对象:Connection、Statement、ResultSet
  2. 释放顺序:和创建的顺序相反,
    创建是时候:connnection->statement->resultSet
    释放的时候:resultSet->statement->connection
  3. 写在哪里:finally的代码块
  4. 是否可以放在try()代码块中:需要实现AutoCloseable接口,均可以放

    /*
    TODO: 释放资源
        1).  java程序访问数据库底层本质是IO流, 访问结束释放资源
             java中有涉及两个程序的数据传输,本质就是IO
        2). 流的关闭: 先开后关
        3). 标准写法:
               try...catch...finally(在finally里释放资源)
    
    */
    public class JdbcDemo {
    
    public static void main(String[] args)  {
        Connection conn = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            //1. 注册驱动
            //可以不写: jdk5(jdbc4.0)会自动注册
            Class.forName("com.mysql.jdbc.Driver");
            //2. 获取连接
            String url = "jdbc:mysql://localhost:3306/day02_3?characterEncoding=utf8";
            String user = "root";
            String password = "root";
            conn = DriverManager.getConnection(url, user, password);
            //3. 执行sql,获取结果
            statement = conn.createStatement();
            String sql = "select * from user";
            resultSet = statement.executeQuery(sql);
            //4. 处理结果
            while(resultSet.next()){
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                int sex = resultSet.getInt("sex");
                System.out.println(id + "," + name + "," + age + "," + sex);
            }
    
        }  catch (Exception throwables) {
            throwables.printStackTrace();
        } finally {
            //5. 释放资源
            if(resultSet != null){
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(statement != null){
                try {
                    statement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(conn != null){
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
    
        }
    
    }
    }
    

第三章 JDBC的应用

# sql分类
0. 我们可以怎么操作数据库
1. DDL : data definition language(数据定义语言)   很少用!!!
        create/drop/alter/truncate + database/table ...
        操作仓库和表
2. DML : manipulation(数据操作语言)   常用!!!
        insert/delete/update ...
        增删改数据
3. DQL : query(数据查询语言)            常用!!!
        select ...
        查询数据
4. DCL : control(数据控制语言)        用的少,重要!!
        事务,权限控制

3.1 执行DDL操作

需求


使用JDBC在MySQL的数据库中创建一张学生表

  1. id是主键,整数类型,自增长
  2. name是varchar(20),非空
  3. 性别是boolean类型
  4. 生日是date类型
-- 创建student学生表
create table student(
    id int primary key auto_increment,
    name varchar(20) not null,
    sex boolean,  -- 自动转成微整型1 表示真 0 表示假 tinyint
    birthday date
);

步骤


  1. 创建连接
  2. 通过连接对象得到语句对象
  3. 通过语句对象发送SQL语句给服务器,执行SQL
  4. 释放资源

代码


public class DDLdemo {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/itcast";
        // 1. 创建数据库连接
        Connection connection = DriverManager.getConnection(url, "root", "root");
        // 2. 获取语句对象
        Statement statement = connection.createStatement();
        // 3. 执行DDL语句
        statement.execute("create table student(id int primary key auto_increment,name varchar(20) not null,sex boolean,birthday date)");
        // 4. 关闭资源
        statement.close();
    }
}

3.2 执行DML操作

需求


  1. 向学生表添加4条记录
  2. 更新其中1条记录
  3. 删除其中1条记录

SQL语句


-- 添加4条记录
insert into student (name,sex,birthday) values ('孙悟空',1,'1999-2-10'),('猪八戒',1,'1999-2-10'),('唐三藏',1,'1999-2-10'),('沙悟净',1,'1999-2-10');

-- 更新1条,将唐三藏,姓名改成嫦娥,性别改成女,生日换成1997-04-10
update student set name='嫦娥', sex=0, birthday='1997-04-10' where name='唐三藏';

-- 删除4号记录
delete from student where name='沙悟净';

代码


package com.itheima06.jdbc;

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

public class DMLdemo {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {
//        insert();

//        update();

        delete();

    }

    private static void delete() throws SQLException {
        String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
        // 1.创建数据库连接
        Connection connection = DriverManager.getConnection(url, "root", "root");
        // 2.获取语句对象
        Statement statement = connection.createStatement();
        // 3.执行删除SQL
        int rows = statement.executeUpdate("delete from student where name='沙悟净'");
        System.out.println("删除了" + rows + "行");
        // 4.关闭资源
        statement.close();
        connection.close();
    }

    private static void update() throws SQLException {
        String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
        // 1.创建数据库连接
        Connection connection = DriverManager.getConnection(url, "root", "root");
        // 2.获取语句对象
        Statement statement = connection.createStatement();
        // 3.执行更新SQL
        String sql = "update student set name='嫦娥', sex=0, birthday='1997-04-10' where name='唐三藏'";
        int rows = statement.executeUpdate(sql);
        System.out.println("更新了" + rows + "行");
        // 4.关闭资源
        statement.close();
        connection.close();
    }

    private static void insert() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");

        String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
        // 1.创建数据库连接
        Connection connection = DriverManager.getConnection(url, "root", "root");
        // 2.获取语句对象
        Statement statement = connection.createStatement();
        // 3.执行新增SQL
        int rows = statement.executeUpdate("insert into student (name,sex,birthday) values ('孙悟空',1,'1999-2-10'),('猪八戒',1,'1999-2-10'),('唐三藏',1,'1999-2-10'),('沙悟净',1,'1999-2-10');");
        System.out.println("新增了" + rows + "行");
        // 4.关闭资源
        statement.close();
        connection.close();
    }
}

3.3 执行DQL操作

需求


查询所有的学员信息

查询结果


02-JDBC - 图8

步骤


  1. 获取连接对象
  2. 得到语句对象
  3. 执行SQL语句后得到结果集ResultSet对象
  4. 循环遍历取出每一条记录
  5. 输出的控制台上
  6. 释放资源

代码


public class DQLdemo {

    public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://localhost:3306/itcast?characterEncoding=utf8";
        // 1.获取连接对象
        Connection connection = DriverManager.getConnection(url, "root", "root");
        // 2.获取语句对象
        Statement statement = connection.createStatement();
        // 3.执行DQL语句
        ResultSet resultSet = statement.executeQuery("select * from student");
        // 4.遍历ResultSet
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            boolean sex = resultSet.getBoolean("sex");
            Date birthday = resultSet.getDate("birthday");

            System.out.println("id: " + id + ", name: " + name + ",sex:" + (sex ? '男': '女') + ",birthday: " + birthday);
        }
        // 5.关闭资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

第四章 封装JDBC工具类

需求


上面的代码中出现了很多重复的代码,把这些代码抽取到一个公共类里面

步骤


创建类JdbcUtils包含2个方法:

  1. 得到数据库的连接:getConnection()
  2. 关闭所有打开的资源:
    close(Connection conn, Statement stmt, ResultSet rs)

代码

src目录下 jdbc.properties

url = jdbc:mysql://localhost:3306/day02_3?characterEncoding=utf8
user = root
password = root
driver = com.mysql.jdbc.Driver

/*
    TODO 工具类: 工具方法的集合
        1. 工具方法: 重复出现的代码抽取成的
        2. 工具方法的特点: 一般是static修饰,调用方便

    TODO: 关于getConnection方法的优化
        1.  注册驱动 放到静态代码块中: 类加载只需要一次
        2. 参数直接写死是不合适的
            1). 方案A: 设置成方法参数 (变化频率比较大,每次调用都可能不同)
            2). 方案B: 搞个配置文件 (变量频率比较小,有可能变动,一般不会每次调用都变动)
                I. 实参放入到properties文件中
                II. 要读取properties文件(写在静态代码块中,只需要一次)
 */
public class JdbcUtil {
    //注意: 作用域!
    private static String url;
    private static String user;
    private static  String password;
    private static String driver;

    static{
        //静态代码块的特点: 随着类加载的而执行,我们程序中用一个类多次,也只加载一次
        try {
            ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
            url = bundle.getString("url");
            user = bundle.getString("user");
            password = bundle.getString("password");
            driver = bundle.getString("driver");
            //1. 注册驱动 : 只需要一次,所以放着静态代码块中即可
            Class.forName(driver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws Exception {
        //2. 获取连接
            // 参数直接写死了
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }

    public static void release(Connection conn, Statement statement, ResultSet resultSet){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if(conn != null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

第五章 JDBC事务

5.1 API介绍


Connection接口中与事务有关的方法 说明
void setAutoCommit(boolean autoCommit) 设置事务模式,参数是false表示手动提交;参数是true表示自动提交 (相当于start transaction)
void commit() 提交事务 (commit)
void rollback() 回滚事务(rollback)

事务操作

start transaction; — 开启事务(设置事务手动提交,禁止事务自动提交)

    connection.setAutoCommit(false)

commit; — 成功提交

    connection.commit()

rollback; — 失败回滚

    connection.rollback();

5.2 事务控制案例


sql脚本:

create table account(
    id int(11) primary key auto_increment,
    name varchar(20),
    money float
);
insert into account values(null,"tom",1000),(null,"rose",1000);

没有事务的转账情况

  1. 获取连接
  2. 获取到Statement
  3. 使用Statement执行两次更新操作
  4. 最后关闭资源
public class TransferDemo {

    public static void main(String[] args)  {

        Scanner sc = new Scanner(System.in);
        System.out.println("开始转账...");
        System.out.println("请输入转出的账户:");
        String outUser = sc.nextLine();
        System.out.println("请输入转入的账户:");
        String inUser = sc.nextLine();
        System.out.println("请输入转账的金额:");
        int money = Integer.parseInt(sc.nextLine());

//        String outsql = "update account set money = money - ? where name = '?'"; // ?去掉,用 "+变量+" 替代
        String outsql = "update account set money = money - "+money+" where name = '"+outUser+"'";
        String insql = "update account set money = money + "+money+" where name = '"+inUser+"'";

        try {
            Connection conn = JdbcUtil.getConnection();
            Statement statement = conn.createStatement();
            //转账
            statement.executeUpdate(outsql); // 转出
            //TODO: 银行系统崩溃 (用算术异常模拟)
            int i = 1/0;
            statement.executeUpdate(insql); // 转入

            System.out.println("转账成功");
            JdbcUtil.release(conn,statement,null); //释放资源要放在finally中,这里偷个懒
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("转账失败");
        }

    }
}

使用事务的情况

  1. 获取连接
  2. 开启事务
  3. 获取到Statement
  4. 使用Statement执行两次更新操作
  5. 正常情况下提交事务
  6. 出现异常回滚事务
  7. 最后关闭资源
/*
    TODO: 有事务的代码
    start transaction; -- 开启事务(设置事务手动提交,禁止事务自动提交)
        connection.setAutoCommit(false)
    commit; -- 成功提交
        connection.commit()
    rollback; -- 失败回滚
        connection.rollback();

        try{
            //开启事务
            //转账操作(事务操作)
            //成功提交
        }catch(e){
            //失败回滚
        }
 */
public class TransferDemo2 {

    public static void main(String[] args) throws SQLException {

        Scanner sc = new Scanner(System.in);
        System.out.println("开始转账...");
        System.out.println("请输入转出的账户:");
        String outUser = sc.nextLine();
        System.out.println("请输入转入的账户:");
        String inUser = sc.nextLine();
        System.out.println("请输入转账的金额:");
        int money = Integer.parseInt(sc.nextLine());
       String outsql = "update account set money = money - "+money+" where name = '"+outUser+"'";
        String insql = "update account set money = money + "+money+" where name = '"+inUser+"'";

        Connection conn = null;
        Statement statement = null;
        try {
            conn = JdbcUtil.getConnection();
            statement = conn.createStatement();
            //TODO: 开启事务!!!
            conn.setAutoCommit(false);
            //转账
            statement.executeUpdate(outsql); // 转出
            //银行系统崩溃 (用算术异常模拟)
//            int i = 1/0;
            statement.executeUpdate(insql); // 转入

            //TODO: 成功提交
            conn.commit();
            System.out.println("转账成功");
        } catch (Exception e) {
            //TODO: 失败回滚
            conn.rollback();
            e.printStackTrace();
            System.out.println("转账失败");
        }finally{
            JdbcUtil.release(conn,statement,null); //释放资源要放在finally中,这里偷个懒
        }
    }
}

第六章 案例:用户登陆

6.1 实现用户登录

需求


通过用户名和密码能够查询到数据库中记录表示登录成功,否则登录失败

步骤和代码实现

  1. 创建一张用户表,添加2条用户记录 `` -- 建表 create tableuser`( id int primary key auto_increment, name varchar(20), password varchar(32) );

— 插入记录 insert into user values(null,’Jack’,’123’),(null,’Rose’,’456’); select * from user;

— Jack登陆成功
— 1. 用户输入: Jack的用户名和密码 — 2. 执行以下的sql — 1). 根据用户输入的用户名和密码执行之后能够查询到结果 — 说明用户名和密码正确, 表示登录成功 — 2). 根据用户输入的用户名和密码执行之后能够查不到结果 — 说明用户名不存在或密码错误,表示登录失败
select * from user where name=’Jack’ and password=’123’;

— Rose登陆成功 select * from user where name=’Rose’ and password=’456’;



2.  用户在控制台上输入用户名和密码,实现登录的功能。<br />步骤如下: 
   1.  得到用户从控制台上输入的用户名和密码 
   1.  调用下面写的登录方法来实现登录 
   1.  写一个登录的方法 
   1.  通过工具类得到连接 
   1.  创建语句对象,使用拼接字符串的方式生成SQL语句 
   1.  查询数据库,如果有记录则表示登录成功,否则登录失败 
   1.  释放资源 
```java
/*
    TODO:
        我们jdbc.properties中的用户名和密码
                是java程序访问mysql的账户  (开发者)
        登录案例:
            1). java开发者设计了一套程序: 微信
            2). 这里的用户名和密码, 是普通用户在这套程序上注册的账户
               微信(java编写的)     mysql数据库
                           root/root
     微信用户
          jack/123
 */
public class LoginDemo {

    public static void main(String[] args) throws Exception {
        System.out.println("这是用户登录界面!!!");
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();

//        String sql = "select * from user where name='?' and password='?'";
        String sql = "select * from user where name='"+username+"' and password='"+password+"'";

        Connection conn = JdbcUtil.getConnection();
        Statement statement = conn.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);
        //有结果表示登录成功,没有结果表示登录失败
        if(resultSet.next()){ //如果有结果,可以向下移动一行,并返回true
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败: 用户名不存在或密码错误");
        }

        JdbcUtil.release(conn,statement,resultSet);

        //Map<String,String> map  = new HashMap<>();
        //map集合没有数据, 但不为null

    }
}

6.2 SQL注入问题

访问步骤


输入下面的用户名和密码,即可登陆成功

请输入用户名:
Jack
请输入密码:
abc' or '1' = '1
完整的sql : select * from user where name='Jack' and password='abc' or '1' = '1'
登录成功!

问题分析


 TODO: SQL注入
        1). 用户通过往sql中注入 "含有sql语法的参数" , 使用原sql的含义发现了变化, 这种攻击方式称之为sql注入
        2). 举例
            原sql: select * from user where name=? and password=?;
                    根据用户名和密码查询信息
            被注入攻击之后的sql
                select * from user where name=? and password=? or '1' = '1';
                    查询信息
        3). sql注入 往往是专业人员 的恶意攻击

解决问题:不能使用Statement接口,应该使用它的子接口:PreparedStatement,以后我们都使用这个子接口进行编程

第七章 PreparedStatement接口

7.1 接口介绍

继承结构


02-JDBC - 图9

成员方法


Connection接口中的方法

方法 描述
PreparedStatement prepareStatement(String sql) 创建一个预编译的SQL语句

PreparedStatement接口中的方法

方法 描述
int executeUpdate() 执行增删改
返回值是影响的行数
ResultSet executeQuery() 查询
返回值是结果集
void set数据类型(int 参数1,参数2) 设置占位符参数
参数1:第几个占位符,从1开始计算
参数2:占位符的值

PreparedStatement设置参数


02-JDBC - 图10

7.2 执行DML操作

示例


/*
    # TODO: PreparedStatement(预编译语句)
        1. 介绍
               是Statement的子接口,比Statement更强大
               1). 防止sql注入
               2). 提高批量操作的效率
        2. 方法
            1). 获取预编译语句对象
             PreparedStatement pstm = connection.prepareStatement(sql);
             //这里的sql的参数不能直接写进去,需要占位符?来替代
            2). 设置参数
                void setInt(int parameterIndex, int x)
                void setString(int parameterIndex, String x)
                I. parameterIndex : 参数索引, 从sql的左边数起,从1开始
                II. x : 实参

           api不需要拼接参数到sql中!!!!
 */
public class PrepareDemo {

    public static void main(String[] args) throws Exception {
//        update();
        insert();
    }

    private static void insert() throws Exception{
        String sql = "insert into student values(null,?,?,?)";

        Connection conn = JdbcUtil.getConnection();
        //预编译sql
        PreparedStatement pstm = conn.prepareStatement(sql);
        //sql中的第1个?的参数为0
        pstm.setString(1,"白龙马");
        pstm.setInt(2,1);
        pstm.setString(3,"1998-01-01");
        int count = pstm.executeUpdate();
        System.out.println(count);//1

        JdbcUtil.release(conn,pstm,null);
    }

    private static void update() throws Exception {
        String sql = "update student set sex = ? where id = ?";

        Connection conn = JdbcUtil.getConnection();
        //预编译sql
        PreparedStatement pstm = conn.prepareStatement(sql);
        //sql中的第1个?的参数为0
        pstm.setInt(1,0);
        pstm.setInt(2,1);
        int count = pstm.executeUpdate();
        System.out.println(count);//1

        JdbcUtil.release(conn,pstm,null);
    }
}

7.3 改写用户登陆案例

02-JDBC - 图11

02-JDBC - 图12

代码


/*
       TODO:  PreparedStatement解决sql注入
        1. 实现方案:  PreparedStatement替代 Statement
        2. 原理:  将sql预先发给数据库进行预编译(先让数据库知道sql含义),后续再发送参数,就不会有sql注入问题啦
        3. PreparedStatement (预编译语句)
            1). 防止sql注入
            2). 提高效率
 */
public class LoginDemo3 {

    public static void main(String[] args) throws Exception {
        System.out.println("这是用户登录界面!!!");
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String username = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();

        String sql = "select * from user where name = ? and password= ?";

        Connection conn = JdbcUtil.getConnection();
        PreparedStatement pstm = conn.prepareStatement(sql);
        pstm.setString(1,username);
        pstm.setString(2,password);
        ResultSet resultSet = pstm.executeQuery();
        //有结果表示登录成功,没有结果表示登录失败
        if(resultSet.next()){ //如果有结果,可以向下移动一行,并返回true
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败: 用户名不存在或密码错误");
        }

        JdbcUtil.release(conn,pstm,resultSet);

    }
}

第八章 连接池

8.1 连接池解决现状的原理

8.1.1 JDBC访问数据库原理


每人每次访问数据库都是创建一个新的连接对象,连接对象不能共享。当用户使用完毕以后,就把连接对象关闭了

02-JDBC - 图13

8.1.2 连接存在的问题和解决方案

连接对象的使用问题


  1. 创建连接对象比较耗时
  2. 一个连接对象只使用一次就被关闭

需要解决两个问题


  1. 提高获取连接对象的速度
  2. 提高连接对象的使用率

8.1.3 应用连接池


  1. 服务器启动的时候,就会预先创建一个连接池,然后再创建一批连接对象放入连接池
  2. 当用户需要访问数据库的时候,就直接返回一个连接对象给用户使用。当用户使用完毕的时候,就把连接对象放回到连接池,给下一个用户使用

使用连接池的目的:提高访问数据库的效率

02-JDBC - 图14

8.2 连接池API

  1. 接口名 (数据源)
    javax.sql.DataSource
  2. 实现类
    由第三方厂商来实现,我们只需要学习使用这个工具就可以了
  3. 连接池接口中的方法
    connection.close(); // 本来是释放资源,在连接池中被重写为将连接还给连接池 | DataSource接口中的方法 | 描述 | | —- | —- | | Connection getConnection() | 从连接池中得到一个连接对象 |

8.3 连接池应用

8.3.1 常用连接池概述


DataSource本身只是一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现,我们只需要学习这个工具如何使用即可

常用的连接池实现组件有这些:

  1. 阿里巴巴-德鲁伊 Druid连接池:Druid是阿里巴巴开源平台上的一个项目
  2. DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件
  3. c3p0是一个开源的JDBC连接池,目前使用它的开源项目有Hibernate,Spring等。c3p0有自动回收空闲连接功能

8.3.2 Druid连接池配置

Druid简介

Druid是阿里巴巴开发的号称为监控而生的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票

Druid的下载地址:https://github.com/alibaba/druid

Druid连接池使用的jar包如下:

02-JDBC - 图15

常用的配置参数

参数 说明
url 数据库连接字符串,jdbc:mysql://localhost:3306/itcast
username 数据库用户名
password 数据库密码
driverClassName 数据库驱动全名,com.mysql.jdbc.Driver
initialSize 连接池初始连接数
maxActive 连接池最大连接数
maxWait 连接池最长等待时间,单位是毫秒

Druid连接池API介绍

Class类中的方法 说明
InputStream getResourceAsStream(String path) 读取连接池的配置文件为字节流

创建一个Properties对象,读取上面的输入流:load(输入流)

DruidDataSourceFactory的方法 方法
public static DataSource createDataSource(Properties properties) 根据properties属性值创建连接池

8.3.3 案例:使用Druid连接池

资料简介

02-JDBC - 图16

需求


使用druid创建连接池,从连接池中得到连接对象,输出得到的连接对象

步骤


  1. 导入jar包
    02-JDBC - 图17
  2. 在src目录下创建一个properties文件,命名为:druid.properties,设置上面的参数
  3. Java代码
    1. 加载properties文件的内容到Properties对象中
    2. 使用工厂类,创建Druid连接池,使用配置文件中的参数
    3. 从Druid连接池中取出10个连接输出

代码


属性文件druid.properties

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/itcast
username=root
password=root
initialSize=3
maxActive=5
maxWait=2000

Java代码


package com.itheima11.druid;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.itheima07.util.JdbcUtil;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;

/*
    TODO: druid使用
    1. 环境
        1). lib目录导入druid-1.0.0.jar包
        2). src目录下放druid.properties
    2. 编码
        1). static DataSource createDataSource(Properties properties)
 */
public class DruidDemo {

    public static void main(String[] args) throws Exception {

        //1. 创建druid连接池
            //加载druid.properties配置文件的
        Properties p = new Properties();
        p.load(new FileInputStream("day04-jdbc/src/druid.properties"));
            /*
                创建一个连接池 : 预先放入一些连接
                1). 创建连接:
                    driverClassName 驱动信息
                    url : 地址
                    username : 用户名
                    password : 密码
                2). 初始连接数: initialSize
             */
        DataSource ds =  DruidDataSourceFactory.createDataSource(p);

        //2. 从连接池中获取连接
        Connection conn = ds.getConnection();

        String sql = "select * from user";
        PreparedStatement pstm = conn.prepareStatement(sql);
        ResultSet resultSet = pstm.executeQuery();
        while(resultSet.next()){
            String name = resultSet.getString("name");
            System.out.println(name);
        }
        //3. 用完了要将连接还给连接池
        //connection.close(); // 本来是释放资源,在连接池中被重写为将连接还给连接池
        JdbcUtil.release(conn,pstm,resultSet);
    }
}

效果

public class DruidDemo2 {

    public static void main(String[] args) throws Exception {

        //1. 创建druid连接池
        Properties p = new Properties();
        p.load(new FileInputStream("day04-jdbc/src/druid.properties"));
            /*
                创建一个连接池 : 预先放入一些连接
                1). 创建连接:
                    driverClassName 驱动信息
                    url : 地址
                    username : 用户名
                    password : 密码
                2). 初始连接数: initialSize
                3). 最大连接数: maxActive
                4). 最长等待时间: maxWait
                    (超过最大连接数的并发,等待时间超过此限制,会抛出异常,中断等待)
             */
        DataSource ds =  DruidDataSourceFactory.createDataSource(p);

        //2. 从连接池中获取连接 (循环模拟并发)
        for (int i = 0; i < 6; i++) {
            Connection conn = ds.getConnection();
            System.out.println(conn);
            //将第4次访问的连接还给连接池
            if(i == 3){
                conn.close();
            }
        }
        //connection.close(); // 本来是释放资源,在连接池中被重写为将连接还给连接池

    }
}

02-JDBC - 图18