1、JDBC基础
   
    一般连接MySQL数据库的方法有:通过MySQL自带的命令行方式;使用客户端来操作MySQL数据库,如SQLyog、Navicat、SQLWave、MyDB Studio、EMS SQL Manager for MySQL等;使用Java来访问MySQL数据库,也就是JDBC(Java Database Connectivity)。
    
    JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供。
    
    程序员如果要开发访问数据库的程序,只需要会调用 JDBC 接口中的方法即可,不用关注类是如何实现的。使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库。
    
    
    
    JDBC的本质就是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
   
    
    
    1.1 用到的包和API
   
使用 JDBC 开发使用到的包:
| 用到的包 | 说明 | 
|---|---|
| java.sql | 所有与JDBC访问数据库相关的接口和类。 | 
| javax.sql | 数据库拓展包,提供数据库额外的功能。如:连接池。 | 
| 数据库驱动 | 由各大数据库厂商提供,需要额外去下载,是对JDBC接口实现的类。 | 
JDBC 的核心 API:
| 接口或类 | 作用 | 
|---|---|
| DriverManager类 | 管理和注册数据库驱动,获得数据库连接对象。 | 
| Connection接口 | 一个连接对象,可用于创建Statement和PreparedStatement对象。 | 
| Statement接口 | 一个SQL语句对象,用于把SQL语句发送到数据库服务器。 | 
| PreparedStatement接口 | 一个SQL语句对象,是Statement的子接口。 | 
| ResultSet接口 | 用于封装数据库查询的结果集,返回给客户端Java程序。 | 
    
    
    1.2 导入Jar包
   
    
    
     
   
    
    
    1.3 加载和注册驱动
   
    注册驱动就是告诉程序该使用哪一个数据库驱动jar。
    
    格式:
    
     Class.forName("数据库驱动实现类")
    
    ,如:
    
     com.mysql.jdbc.Driver
    
    
    可以这样注册的原因是数据库厂商在
    
     com.mysql.jdbc.Driver
    
    中实现了
    
     java.sql.Driver
    
    接口,源代码如下:
   
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
    光看这个源码还是无法理解,其实这个
    
     Driver
    
    类的的核心就是那个静态代码块,里面调用了
    
     DriverManager.registerDriver(new Driver());
    
    方法,输入是一个
    
     Driver
    
    对象,不要看这个Driver类好像是空的,其实内容都在
    
     extends NonRegisteringDriver
    
    上,
    
     NonRegisteringDriver
    
    就是实现了
    
     java.sql.Driver
    
    接口的实现类。
    
    
     Driver
    
    接口是所有数据库厂商必须实现的接口,表示这是一个驱动类。从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。
    
     Class.forName
    
    这句话可以省略。因为从JDBC4开始,驱动jar包的下图路径里就把驱动名称写在一个文件里,我们在使用
    
     DriverManager
    
    这个类时,静态代码块里面就已经读取这个字符串进行注册了。
    
     
   
    
    
    1.4 DriverManager类
   
    
     DriverManager
    
    类的作用主要有两个:管理和注册驱动、创建数据库的连接。
    
    从
    
     com.mysql.jdbc.Driver
    
    的源码中可以看到数据库厂商对
    
     Driver
    
    接口的实现类就是调用
    
     DriverManager
    
    类的
    
     registerDriver
    
    方法,向其输入一个厂商实现的
    
     Driver
    
    类对象。注册完后就可以调用
    
     DriverManager
    
    类的
    
     getConnection
    
    方法获得数据库连接对象。
   
| DriverManager类的静态方法 | 描述 | 
|---|---|
| Connection getConnection (String url, String user, String password) | 通过连接字符串、用户名和密码来得到数据库的连接对象。 | 
| Connection getConnection (String url, Properties info) | 通过连接字符串属性对象来得到连接对象 | 
使用 JDBC 连接数据库的四个参数:
| 参数 | 说明 | 
|---|---|
| Strring url | 不同数据库的url是不同的,MySQL的写法:jdbc:mysql://localhost:3306/数据库[?参数名=参数值] | 
| String user | 登陆的用户名 | 
| String password | 登陆的密码 | 
| Properties info | com.mysql.jdbc.Driver | 
    连接数据库的 URL 地址格式:
    
     协议名: 子协议://服务器名或IP 地址: 端口号/数据库名? 参数=
    
    
    MySQL的写法:
    
    
    
    如果连接的是本地的服务器,还可以简写为:
    
     jdbc:mysql:///数据库名
    
//使用用户名、密码、URL 得到连接对象
import java.sql.Connection;
import java.sql.DriverManager;
public class JdbcDemo01 {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://localhost:3306/db1";
        Connection connection = DriverManager.getConnection(url, "root", "root");
        System.out.println(connection);	// com.mysql.jdbc.JDBC4Connection@2a70a3d8
    }
}
//使用属性文件和 url 得到连接对象
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
public class JdbcDemo02 {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:mysql://localhost:3306/db1";
        Properties info = new Properties();
        //把用户名和密码放在 info 对象中
        info.setProperty("user","root");
        info.setProperty("password","root");
        Connection connection = DriverManager.getConnection(url, info);
        System.out.println(connection); //com.mysql.jdbc.JDBC4Connection@75881071
    }
}
    
    
    1.5 Connection接口
   
    Connection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象。实现类实现了
    
     createStatement()
    
    方法,返回一个
    
     Statement
    
    对象。
   
    
    
    1.6 Statement接口
   
    JDBC访问数据库的步骤为:通过
    
     DriverManager
    
    注册驱动并获得数据库连接对象
    
     Connection
    
    ,连接后通过连接对象获得
    
     Statement
    
    对象用于向数据库服务器发送SQL语句,然后得到一个结果集
    
     ResultSet
    
    ,最后释放资源。
    
    
    
    
     Statement
    
    的作用就是代表一条SQL语句,发送SQL语句到数据库服务器,用于执行静态SQL语句并返回它所生成结果的对象。
   
| Statement接口中的方法 | 描述 | 
|---|---|
| int executeUpdate(String sql) | 用于发送 DML 语句,增删改的操作,insert、update、delete 参数:SQL 语句 返回值:返回对数据库影响的行数 | 
| ResultSet executeQuery(String sql) | 用于发送 DQL 语句,执行查询的操作。select 参数:SQL 语句 返回值:查询的结果集 | 
需要释放资源的对象:ResultSet 结果集,Statement 语句,Connection 连接。释放原则是先用后放,后用先放。放在finally块中执行。
    
    
    1.7 执行DDL操作
   
使用 JDBC 在 MySQL 的数据库中创建一张学生表:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDemo01 {
    public static void main(String[] args) {
        //1. 创建连接
        Connection conn = null;
        Statement statement = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql:///db1", "root", "root");
            //2. 通过连接对象得到语句对象
            statement = conn.createStatement();
            //3. 通过语句对象发送 SQL 语句给服务器
            //4. 执行 SQL
            statement.executeUpdate("CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT, " + "name VARCHAR(20) NOT NULL, gender BOOLEAN, birthday DATE)");
            //5. 返回影响行数(DDL 没有返回值)
            System.out.println("创建表成功");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        //6. 释放资源
        finally {
            //关闭之前要先判断
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
    
    
    1.8 执行DML操作
   
向学生表中添加 4 条记录,主键是自动增长:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDemo02 {
    public static void main(String[] args) throws SQLException {
        // 1) 创建连接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql:///db1", "root", "root");
        // 2) 创建 Statement 语句对象
        Statement statement = connection.createStatement();
        // 3) 执行 SQL 语句:executeUpdate(sql)
        int count = 0;
        // 4) 返回影响的行数
        count += statement.executeUpdate("insert into student values(null, '孙悟空', 1, '1993-03-24 ')");
        count += statement.executeUpdate("insert into student values(null, '白骨精', 0, '1995-03-24 ')");
        count += statement.executeUpdate("insert into student values(null, '猪八戒', 1, '1903-03-24 ')");
        count += statement.executeUpdate("insert into student values(null, '嫦娥', 0, '1993-03-11 ')");
        System.out.println("插入了" + count + "条记录");
        // 5) 释放资源
        statement.close();
        connection.close();
    }
}
    插入后student表如下:
    
     
   
    
    
    1.9 执行DQL操作
   
    
     ResultSet
    
    接口封装数据库查询的结果集,可以对结果集进行遍历,取出每一条记录。
    
     
   
| ResultSet接口中常用方法 | 描述 | 
|---|---|
| boolean next() | 1) 游标向下移动 1 行 2) 返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false | 
| 数据类型 getXxx() | 1) 通过字段名,参数是 String 类型。返回不同的类型 2) 通过列号,参数是整数,从 1 开始。返回不同的类型 | 
    其中
    
     getXxx
    
    表示多种方法,对应不同的类型都有专门的方法,API截取如下:
    
    
    
    常用数据类型转换如下表所示:
   
| SQL类型 | JDBC对应的方法 | 返回类型 | 
|---|---|---|
| BIT(1)bit(n) | getBoolean() | boolean | 
| TINYINT | getByte() | byte | 
| SMALLINT | getShort() | short | 
| INT | getInt() | int | 
| BIGINT | getLong() | long | 
| CHAR, VARCHAR | getString() | String | 
| DATE | getDate | java.sql.Date 只代表日期 | 
| TIME | getTime() | java.sql.Time 只代表是时间 | 
| TIMESTAMP | getTimestamp() | java.sql.Timestamp 同时有日期和时间 | 
    
     java.sql.Date
    
    、
    
     Time
    
    、
    
     Timestamp
    
    (时间戳),三个共同父类是:
    
     java.util.Date
    
    
    查询所有的学员信息:
   
import java.sql.*;
public class JdbcDemo03 {
    public static void main(String[] args) throws SQLException {
        //1) 得到连接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "root");
        //2) 得到语句对象
        Statement statement = connection.createStatement();
        //3) 执行 SQL 语句得到结果集 ResultSet 对象
        ResultSet rs = statement.executeQuery("select * from student");
        //4) 循环遍历取出每一条记录
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            boolean gender = rs.getBoolean("gender");
            Date birthday = rs.getDate("birthday");
            //5) 输出的控制台上
            System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" +
                    birthday);
        }
        //6) 释放资源
        rs.close();
        statement.close();
        connection.close();
    }
}
输出如下:
编号:1, 姓名:孙悟空, 性别:true, 生日:1993-03-24
编号:2, 姓名:白骨精, 性别:false, 生日:1995-03-24
编号:3, 姓名:猪八戒, 性别:true, 生日:1903-03-24
编号:4, 姓名:嫦娥, 性别:false, 生日:1993-03-11
    
    
    1.10 数据库工具类JdbcUtils
   
    通过上面三个操作可以看出代码存在很多的重复,比如数据库的连接、关闭资源等。可以创建一个工具类来执行这些操作。数据库的登陆所需要的信息就保存在一个文件上,在程序启动的时候自动读取文件信息来连接数据库。
    
    在src目录下创建文件jdbc.properties,内容如下:
   
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db1
user=root
password=root
然后创建一个JDBC工具类,实现加载文件链接数据库:
package cn.klb;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
public class JducUtils {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    static {
        try {
            //1. 创建Properties集合类。
            Properties pro = new Properties();
            //获取src路径下的文件的方式--->ClassLoader 类加载器
            ClassLoader classLoader = JducUtilsM.class.getClassLoader();
            URL res = classLoader.getResource("jdbc.properties");
            pro.load(new FileReader(res.getPath()));
            //3. 获取数据,赋值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");
            //4. 注册驱动
            Class.forName(driver);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }
    public static void close(Statement stmt, Connection conn) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void close(ResultSet rs, Statement stmt, Connection conn) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
    为了使用这个JDBC工具类,先创建如下数据库:
    
    
    
    然后使用工具类连接数据库的方法,得到用户从控制台上输入的用户名和密码来查询数据库:
   
package cn.klb;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class JdbcDemo04 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("输入用户名:");
        String username = sc.nextLine();
        System.out.println("输入密码:");
        String password = sc.nextLine();
        boolean flag = new JdbcDemo04().login(username, password);
        if (flag) {
            System.out.println("登陆成功!");
        }
    }
    public boolean login(String username, String password) {
        if (username == null || password == null) {
            return false;
        }
        //连接数据库判断是否登录成功
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        //1.获取连接
        try {
            conn = JducUtils.getConnection();
            //2.定义sql
            String sql = "select * from user where name = '" + username + "' and password = '" + password + "' ";
            //3.获取执行sql的对象
            stmt = conn.createStatement();
            //4.执行查询
            rs = stmt.executeQuery(sql);
            return rs.next();//如果有下一行,则返回true
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JducUtils.close(rs, stmt, conn);
        }
        return false;
    }
}
    
    
    1.11 PreparedStatement 接口
   
    让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了原有 SQL 真正的意义,以上问题称为 SQL 注入。要解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。
    
    
     PreparedStatement
    
    是
    
     Statement
    
    接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句。
    
    
    
    因为有预先编译的功能,既提高了SQL的执行效率,也能有效防止SQL注入的问题,安全性更高。
    
    
     PreparedStatement
    
    对象可以通过
    
     Connection
    
    接口来创建,SQL语句中使用占位符
    
     ?
    
| PreparedStatement 接口中的方法 | 描述 | 
|---|---|
| int executeUpdate() | 执行 DML,增删改的操作,返回影响的行数。 | 
| ResultSet executeQuery() | 执行 DQL,查询的操作,返回结果集 | 
| 使用 的步骤: | 
- 
     编写 SQL 语句,未知内容使用?占位:
 
 "SELECT * FROM user WHERE name=? AND password=?"
 
 ;
- 
     获得
 
 PreparedStatement
 
 对象;
- 
     设置实际参数:
 
 setXxx
 
 (占位符的位置, 真实的值);
- 执行参数化 SQL 语句;
- 关闭资源。
| PreparedStatement 中设置参数的方法 | 描述 | 
|---|---|
| void setDouble(int parameterIndex, double x) | 将指定参数设置为给定 Java double 值。 | 
| void setFloat(int parameterIndex, float x) | 将指定参数设置为给定 Java REAL 值。 | 
| void setInt(int parameterIndex, int x) | 将指定参数设置为给定 Java int 值。 | 
| void setLong(int parameterIndex, long x) | 将指定参数设置为给定 Java long 值。 | 
| void setObject(int parameterIndex, Object x) | 使用给定对象设置指定参数的值。 | 
| void setString(int parameterIndex, String x) | 将指定参数设置为给定 Java String 值。 | 
使用 PreparedStatement 改写上面的登录程序:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
/**
 * 使用 PreparedStatement
 */
public class jdbcDemo05 {
    //从控制台上输入的用户名和密码
    public static void main(String[] args) throws SQLException {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String name = sc.nextLine();
        System.out.println("请输入密码:");
        String password = sc.nextLine();
        login(name, password);
    }
    /**
     * 登录的方法
     *
     * @param name
     * @param password
     */
    private static void login(String name, String password) throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        //写成登录 SQL 语句,没有单引号
        String sql = "select * from user where name=? and password=?";
        //得到语句对象
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置参数
        ps.setString(1, name);
        ps.setString(2, password);
        ResultSet resultSet = ps.executeQuery();
        if (resultSet.next()) {
            System.out.println("登录成功:" + name);
        } else {
            System.out.println("登录失败");
        }
        //释放资源,子接口直接给父接口
        JdbcUtils.close(connection, ps, resultSet);
    }
}
    
    
    1.12 表与类的关系
   
    
    
    使用
    
     PreparedStatement
    
    查询一条数据,封装成一个学生
    
     Student
    
    对象:
   
//Student类
public class Student {
    private int id;
    private String name;
    private boolean gender;
    private Date date;
	//此处表示set和get方法
}
//定义测试类
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class jdbcDemo06 {
    public static void main(String[] args) throws SQLException {
        //创建学生对象
        Student student = new Student();
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement ps = connection.prepareStatement("select * from student where id=?");
        //设置参数
        ps.setInt(1,2);
        ResultSet resultSet = ps.executeQuery();
        if (resultSet.next()) {
            //封装成一个学生对象
            student.setId(resultSet.getInt("id"));
            student.setName(resultSet.getString("name"));
            student.setGender(resultSet.getBoolean("gender"));
            student.setBirthday(resultSet.getDate("birthday"));
        }
        //释放资源
        JdbcUtils.close(connection,ps,resultSet);
        //可以数据
        System.out.println(student);
    }
}
输出如下:
Student{id=1, name='孙悟空', gender=true, birthday=1993-03-24}
    将多条记录封装成集合
    
     List<Student>
    
    ,集合中每个元素是一个 JavaBean 实体类的对象:
   
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class jdbcDemo07 {
    public static void main(String[] args) throws SQLException {
        //创建一个集合
        List<Student> students = new ArrayList<>();
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement ps = connection.prepareStatement("select * from student");
        //没有参数替换
        ResultSet resultSet = ps.executeQuery();
        while (resultSet.next()) {
            //每次循环是一个学生对象
            Student student = new Student();
            //封装成一个学生对象
            student.setId(resultSet.getInt("id"));
            student.setName(resultSet.getString("name"));
            student.setGender(resultSet.getBoolean("gender"));
            student.setBirthday(resultSet.getDate("birthday"));
            //把数据放到集合中
            students.add(student);
        }
        //关闭连接
        JdbcUtils.close(connection, ps, resultSet);
        //使用数据
        for (Student stu : students) {
            System.out.println(stu);
        }
    }
}
输出如下:
Student{id=1, name='孙悟空', gender=true, birthday=1993-03-24}
Student{id=2, name='白骨精', gender=false, birthday=1995-03-24}
Student{id=3, name='猪八戒', gender=true, birthday=1903-03-24}
Student{id=4, name='嫦娥', gender=false, birthday=1993-03-11}
    
    
    1.13 PreparedStatement执行DML
   
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class jdbcDemo08 {
    public static void main(String[] args) throws SQLException {
        //insert();
        //update();
        delete();
    }
    //插入记录
    private static void insert() throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement ps = connection.prepareStatement("insert into student values(null, ?, ?,?)");
        ps.setString(1, "小白龙");
        ps.setBoolean(2, true);
        ps.setDate(3, java.sql.Date.valueOf("1999-11-11"));
        int row = ps.executeUpdate();
        System.out.println("插入了" + row + "条记录");
        JdbcUtils.close(connection, ps);
    }
    //更新记录: 换名字和生日
    private static void update() throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement ps = connection.prepareStatement("update student set name=?, birthday=? where id = ? ");
        ps.setString(1, "黑熊怪");
        ps.setDate(2, java.sql.Date.valueOf("1999-03-23"));
        ps.setInt(3, 5);
        int row = ps.executeUpdate();
        System.out.println("更新" + row + "条记录");
        JdbcUtils.close(connection, ps);
    }
    //删除记录: 删除第 5 条记录
    private static void delete() throws SQLException {
        Connection connection = JdbcUtils.getConnection();
        PreparedStatement ps = connection.prepareStatement("delete from student where id=?");
        ps.setInt(1, 5);
        int row = ps.executeUpdate();
        System.out.println("删除了" + row + "条记录");
        JdbcUtils.close(connection, ps);
    }
}
    
    
    1.14 JDBC事务的处理
   
    
    
    1.14.1 准备数据
   
CREATE TABLE account (
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(10),
	balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES ('Jack', 1000), ('Rose', 1000);
     
   
    
    
    1.14.2 API介绍
   
| Connection 接口中与事务有关的方法 | 说明 | 
|---|---|
| void setAutoCommit(boolean autoCommit) | 如果设置为 false,表示关闭自动提交,相当于开启事务 | 
| void commit() | 提交事务 | 
| void rollback() | 回滚事务 | 
    
    
    1.14.3 开发步骤
   
- 获取连接
- 开启事务
- 
     获取到
 
 PreparedStatement
 
- 
     使用
 
 PreparedStatement
 
 执行两次更新操作
- 正常情况下提交事务
- 出现异常回滚事务
- 关闭资源
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class jdbcDemo09 {
    public static void main(String[] args) throws SQLException {
        //1) 注册驱动
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //2) 获取连接
            connection = JdbcUtils.getConnection();
            //3) 开启事务
            connection.setAutoCommit(false);
            //4) 获取到 PreparedStatement
            // 从 jack 扣钱
            ps = connection.prepareStatement("update account set balance = balance - ? where name = ? ");
            ps.setInt(1, 500);
            ps.setString(2, "Jack");
            ps.executeUpdate();
            //出现异常
            System.out.println(100 / 0);
            //给 rose 加钱
            ps = connection.prepareStatement("update account set balance = balance + ? where name = ? ");
            ps.setInt(1, 500);
            ps.setString(2, "Rose");
            ps.executeUpdate();
            //提交事务
            connection.commit();
            System.out.println("转账成功");
        } catch (Exception e) {
            e.printStackTrace();
            try {
                //事务的回滚
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            System.out.println("转账失败");
        } finally {
            //7) 关闭资源
            JdbcUtils.close(connection, ps);
        }
    }
}
输出如下:
java.lang.ArithmeticException: / by zero
	at cn.itcast.day01.jdbcDemo02.main(jdbcDemo02.java:24)
转账失败
    
    
    2、JDBC连接池
   
连接池其实就是一个容器(集合),存放数据库连接的容器。当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。不用每次要连接数据库都跟操作系统申请资源,然后用完就回收。
    
    
    2.1 连接池的方法
   
    JDBC连接池实现用到
    
     javax.sql
    
    包下的标准接口
    
     DataSource
    
    ,主要方法是获取连接
    
     getConnection()
    
    和归还连接
    
     Connection.close()
    
    。
    
    如果连接对象
    
     Connection
    
    是从连接池中获取的,那么调用
    
     Connection.close()
    
    方法,则不会再关闭连接了。而是归还连接。
    
    连接池的实现由数据库厂家来实现,我们只需要会使用即可。
   
    
    
    2.2 C3P0
   
C3P0是一种数据库连接池技术,是一种比较老的连接池技术,但是学会如何使用它也就会使用新的连接池技术了。使用步骤如下:
- 导入数据库驱动jar包;
- 
     导入连接池jar包 (两个)
 
 c3p0-0.9.5.2.jar
 
 和
 
 mchange-commons-java-0.2.12.jar
 
 ;
- 
     定义配置文件:文件名称必须是
 
 c3p0.properties
 
 或者
 
 c3p0-config.xml
 
 ,放在src目录下即可;
- 
     创建数据库连接池对象:
 
 ComboPooledDataSource
 
 ;
- 
     获取连接:
 
 getConnection
 
 。
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class C3P0 {
    public static void main(String[] args) throws SQLException {
        DataSource ds  = new ComboPooledDataSource();
        for(int i = 1; i <= 10; i++){
            Connection conn = ds.getConnection();
            System.out.println(conn);
        }
    }
}
输出如下:
1月 19, 2020 6:11:26 下午 com.mchange.v2.log.MLog 
信息: MLog clients using java 1.4+ standard logging.
1月 19, 2020 6:11:28 下午 com.mchange.v2.c3p0.C3P0Registry 
信息: Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
1月 19, 2020 6:11:28 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 3000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hgetxda71giq1pr1y1henx|ca263c2, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hgetxda71giq1pr1y1henx|ca263c2, idleConnectionTestPeriod -> 0, initialPoolSize -> 5, jdbcUrl -> jdbc:mysql://localhost:3306/db1, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 10, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {password=******, user=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
com.mchange.v2.c3p0.impl.NewProxyConnection@6babf3bf [wrapping: com.mysql.jdbc.JDBC4Connection@3059cbc]
com.mchange.v2.c3p0.impl.NewProxyConnection@24fcf36f [wrapping: com.mysql.jdbc.JDBC4Connection@10feca44]
com.mchange.v2.c3p0.impl.NewProxyConnection@ea6147e [wrapping: com.mysql.jdbc.JDBC4Connection@4d02f94e]
com.mchange.v2.c3p0.impl.NewProxyConnection@1e683a3e [wrapping: com.mysql.jdbc.JDBC4Connection@2053d869]
com.mchange.v2.c3p0.impl.NewProxyConnection@14555e0a [wrapping: com.mysql.jdbc.JDBC4Connection@4bb33f74]
com.mchange.v2.c3p0.impl.NewProxyConnection@3c73951 [wrapping: com.mysql.jdbc.JDBC4Connection@3d5c822d]
com.mchange.v2.c3p0.impl.NewProxyConnection@73700b80 [wrapping: com.mysql.jdbc.JDBC4Connection@49c7b90e]
com.mchange.v2.c3p0.impl.NewProxyConnection@4d5b6aac [wrapping: com.mysql.jdbc.JDBC4Connection@3e84448c]
com.mchange.v2.c3p0.impl.NewProxyConnection@429bffaa [wrapping: com.mysql.jdbc.JDBC4Connection@5403f35f]
com.mchange.v2.c3p0.impl.NewProxyConnection@7e5afaa6 [wrapping: com.mysql.jdbc.JDBC4Connection@63a12c68]
    
    
    2.3 Druid
   
Druid是阿里巴巴提供的数据库连接池实现技术,是当前比较先进的。使用步骤如下:
- 导入数据库驱动jar包;
- 
     导入jar包
 
 druid-1.0.9.jar
 
 ;
- 
     定义配置文件:是
 
 properties
 
 形式的,可以叫任意名称,可以放在任意目录下;
- 
     加载配置文件
 
 xx.properties
 
 ;
- 
     获取数据库连接池对象:通过工厂来获取
 
 DruidDataSourceFactory
 
 ;
- 
     获取连接:
 
 getConnection
 
 .
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class Druid {
    public static void main(String[] args) throws Exception {
        Properties pro = new Properties();
        InputStream is = Druid.class.getClassLoader().getResourceAsStream("druid.properties");
        pro.load(is);
        DataSource ds = DruidDataSourceFactory.createDataSource(pro);
        Connection conn = ds.getConnection();
        System.out.println(conn);
    }
}
输出如下:
1月 19, 2020 6:16:46 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
com.mysql.jdbc.JDBC4Connection@17f7cd29
    
    
    2.4 Spring JDBC
   
    Spring框架对JDBC的简单封装。提供了一个
    
     JDBCTemplate
    
    对象简化JDBC的开发,步骤如下:
   
- 导入数据库驱动jar包;
- 
     导入
 
 commons-logging-1.2.jar
 
 、
 
 spring-beans-5.0.0.RELEASE.jar
 
 、
 
 spring-core-5.0.0.RELEASE.jar
 
 、
 
 spring-jdbc-5.0.0.RELEASE.jar
 
 、
 
 spring-tx-5.0.0.RELEASE.jar
 
 ;
- 
     创建
 
 JdbcTemplate
 
 对象,依赖于数据源
 
 DataSource
 
 ;
- 
     调用
 
 JdbcTemplate
 
 的方法来完成CRUD的操作:
 
 update()
 
 、
 
 queryForMap()
 
 、
 
 queryForList()
 
 、
 
 query()
 
 、
 
 queryForObject
 
 
