【JDBC教科书】阶段式JDBC数据库链接神操做总结

2020年05月29日 阅读数:35
这篇文章主要向大家介绍【JDBC教科书】阶段式JDBC数据库链接神操做总结,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

文章目录

注意: 若是转载文章请在文章明显处代表做者,以及原文连接,谢谢!java

原文连接: https://blog.csdn.net/weixin_44170221/article/details/105285457mysql

在这里插入图片描述

整个JDBC体系思惟导图参考此文章:👉JDBC知识树程序员

1、引言


1.1 如何操做数据

使用客户端工具访问数据库,须要手工创建连接,输入用户名和密码登陆,编写SQL语句,点击执行,查看操做结果(结果集或受影响行数)。web

1.2 实际开发中,会采用客户端操做数据库吗?

在实际开发过程当中,当用户的数据发生改变时,不可能经过客户端操做执行SQL语句,由于操做量过大!没法保证效率和正确性算法

2、JDBC(Java DataBase Connectivity)


2.1 什么是JDBC?

JDBC(Java DataBase Connectivity) Java链接数据库,可使用Java语言链接数据库完成CRUD操做sql

2.2 JDBC核心思想

Java中定义了访问数据库的接口,能够为多种关系型数据库提供统一的访问方式。数据库

由数据库厂商提供驱动实现类(Driver数据库驱动)apache

在这里插入图片描述

2.2.1 MySQL数据库驱动
  • mysql-connector-java-5.1.X 适用于5.X版本
  • mysql-connector-java-8.0.X 适用于8.X版本
2.2.2 JDBC API

JDBC 是由多个接口和类进行功能实现编程

类型 全限定名 简介
class java.sql.DriverManager 管理多个数据库驱动类,提供了获取数据库链接的方法
interface java.sql.Connection 表明一个数据库链接(当Connection不是NULL时,表示已链接一个数据库)
interface java.sql.Statement 发送SQL语句到数据库的工具
interface java.sql.ResultSet 保存SQL查询语句的结果数据(结果集)
class java.sql.SQLException 处理数据库应用程序时所发生的异常

2.3 环境搭建

  1. 在项目下新建 lib 文件夹,用于存放 jar 文件
  2. 将MySQL驱动文件mysql-connector-java-5.1.25-bin.jar 复制到项目的lib文件夹中
  3. 选中lib文件夹 右键选择 add as library,点击OK

3、JDBC开发步骤【重点

3.1 注册驱动

使用Class.forName(“com.mysql.jdbc.Driver”); 手动加载字节码文件到JVM中安全

Class.forName("com.mysql.jdbc.Driver");

3.2 链接数据库

Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=utf8","root","123456");

3.3 获取发送SQL的对象

经过Connection对象得到Statement对象,用于对数据库进行通用访问的

Statement statement = connection.createStatement();

3.4 执行SQL语句

编写SQL语句,并执行,接收执行后的结果

int result = statement.executeUpdate("update stu set student_name='Ziph',sex='男' where student_id = 'class1'");

3.5 处理结果

接收并处理操做结果

if(result > 0){
	System.out.println("执行成功!");
} else {
    System.out.println("执行失败!");
}

3.6 释放资源

遵循的是先开后关的原则,释放过程当中用到的全部资源对象

statement.close();
connection.close();

3.7 综合案例

综合核心六步,实现增删改

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

public class TestInsert {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加载驱动,将驱动字节码文件加载到JVM中
        Class.forName("com.mysql.jdbc.Driver");
        //2.链接数据库
        String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8";//数据库链接地址
        String user = "root";//用户名
        String password = "123456";//密码
        Connection connection = DriverManager.getConnection(url, user, password);
        //3.获取发送SQL语句的对象
        Statement statement = connection.createStatement();
        //4.编写SQL语句,并执行SQL语句
        String sql = "insert into user (userName, password, address, phone) values ('Ziph', '123456', '河北', '11111111111')";
        int result = statement.executeUpdate(sql);
        //5.处理结果
        if (result > 0) {
            System.out.println("添加成功!");
        } else {
            System.out.println("添加失败!");
        }
        //6.释放资源,先开后关
        statement.close();
        connection.close();
    }
}

4、 ResultSet(结果集)


在执行查询SQL后,存放查询到的结果集数据

4.1 接收结果集

ResultSet resultSet = statement.executeQuery(sql)

ResultSet resultSet = statement.executeQuery("SELECT * FROM user");

4.2 遍历ResultSet中的数据

ResultSet以表(Table)结构进行临时结果的存储,须要经过JDBC API将其中的数据进行依次获取

  • 数据行指针(resultSet.next()):初始位置在第一行数据前,每调用一次boolean返回值类型的next()方法,ResultSet中指针向下移动一行,结果为true,表示当前行有数据
  • resultSet.getXxx(“列名”); 根据列名得到数据
  • resultSet.getXxx(整数下标); 表明根据列的编号顺序得到!从1开始
boolean next() throws SQLException;//判断resultSet结果集中下一行是否有数据(注意:返回值boolean类型)
4.2.1 遍历方法
int getInt(int columnIndex) throws SQLException;//得到当前行的第N列的int值
int getInt(String columnLabel) throws SQLException;//得到当前行columnLabel列的int值

4.3 综合案例

对user表全部的数据进行遍历

4.3.1 根据列的名称获取
import java.sql.*;

public class TestSelect {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取链接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8", "root", "Mylifes1110");
        //3.获取执行SQL的对象
        Statement statement = connection.createStatement();
        //4.编写SQL语句并执行SQL语句
        String sql = "select userId, userName, password, address, phone from user";
        ResultSet resultSet = statement.executeQuery(sql);
        //5.处理结果(结果集)
        while (resultSet.next()) {//判断结果集中是否有下一行
            //根据列名获取当前行每一列的数据
            int userId = resultSet.getInt("userId");
            String userName = resultSet.getString("userName");
            String password = resultSet.getString("password");
            String address = resultSet.getString("address");
            String phone = resultSet.getString("phone");
            System.out.println(userId + "\t" + userName + "\t" + password + "\t" + address + "\t" + phone);
        }
        //6.释放资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}
4.3.2 根据列的下标获取
		//5.处理结果(结果集)
        while (resultSet.next()) {//判断结果集中是否有下一行
            //根据列的编号获取当前行每一列的数据
            int userId = resultSet.getInt(1);
            String userName = resultSet.getString(2);
            String password = resultSet.getString(3);
            String address = resultSet.getString(4);
            String phone = resultSet.getString(5);
            System.out.println(userId + "\t" + userName + "\t" + password + "\t" + address + "\t" + phone);
        }

5、常见错误

  • java.lang.ClassNotFoundException 找不到类(类名书写错误、没有导入jar包)
  • com.mysql.jdbc.exceptions.jdbc.MySQLSyntaxErrorException 与SQL语句相关的错误(表名列名书写错误、约束错误、插入的值是String类型,可是没有加单引号)建议:在客户端工具中测试sql语句后,再粘贴到代码中来
  • com.mysql.jdbc.exceptions.jdbc.MySQLIntegrityConstraintViolationException: Duplicate entry ‘1’ for key ‘PRIMARY’ 缘由:主键值已存在!更改要插入的主键值
  • com.mysql.jdbc.exceptions.jdbc.MySQLSyntaxErrorException:Unknown column ‘password’ in
    • 可能输入的值的类型不对,肯定插入元素时,对应的值的类型是否争取

6、综合案例【登陆】


6.1 建立表

  • 建立一张用户表 User
    • id 主键、自动增加
    • username 字符串类型 非空
    • password 字符串类型 非空
    • phone 字符串类型
  • 插入2条测试语句
#建立数据库
CREATE DATABASE temp CHARACTER SET utf8;
#使用该数据库
USE temp;
#建立用户表
CREATE TABLE user (
	id INT PRIMARY KEY auto_increment,
	username CHARACTER(20) NOT NULL,
	password CHARACTER(20) NOT NULL,
	phone CHARACTER(11)
) charset = utf8;
#初始化(插入)表中数据
INSERT INTO user (username, password, phone) VALUES ('ziph', '123456', '16688889999');
INSERT INTO user (username, password, phone) VALUES ('zhangsan', '123456', '16644445555');

6.2 实现登陆

  • 经过控制台,用户输入用户名和密码
  • 用户输入的用户名和密码做为参数,编写查询SQL语句。
  • 若是查询到用户,则用户存在,提示登陆成功,反之,提示失败!
import java.sql.*;
import java.util.Scanner;

public class TestLogin {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String userName = scanner.nextLine();
        System.out.print("请输入密码:");
        String password = scanner.nextLine();

        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取链接对象,链接数据库
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8", "root", "123456");
        //3.建立执行SQL语句的对象
        Statement statement = connection.createStatement();
        //4.编写SQL语句,并执行SQL语句
        String sql = "select * from user where username = '" + userName + "' and password = '" + password + "'";
        ResultSet resultSet = statement.executeQuery(sql);
        //5.处理结果
        if (resultSet.next()) {//经过参数,查到一行数据,就提示用户的登录成功!
            System.out.println("登陆成功!");
        } else {
            System.out.println("登陆失败!");
        }
        //6.释放资源
        resultSet.close();
        statement.close();
        connection.close();
    }
}

7、SQL注入问题


7.1 什么是SQL注入

当用户输入的数据中有SQL关键字或语法时,而且参与了SQL语句的编译,致使SQL语句编译后条件结果为true,一直获得正确的结果。称为SQL注入

在这里插入图片描述

继续使用6.2实现登陆案例,用户名、密码(用户名数据库里没有,密码随便录入的)我在控制台输入的图上内容,却出现了登陆成功!这是怎么回事呢?那咱们再来看一下,看下图吧!

在这里插入图片描述

看一下高亮显示的部分,这一部分是SQL执行的语句,而userName、password是咱们控制台录入的信息(也参与SQL语句的执行)。可是当你输入lalala字符串是没问题的,可是当你再入’ 的时候,SQL执行的时候会认为’ 是userName的结束。随之,录入了or后面内容1=1参与SQL执行代替了录入的密码。这里我解释一下,由于1=1,默认为true,因此该密码就被越过了!; 被认为SQL语句的结束标志,随后一个#注释了password等后面的全部内容。当resultSet.next()判断时,由于它的结果返回的就是boolean类型,上述SQL注入天然而然的返回了一个true成功的越过了帐号和密码的界限,登陆成功!这就是SQL注入!你们能够理解了吧!那么咱们继续看如下语句吧!该语句就是在SQL注入后,被SQL执行的语句:

sql = "select * from user where username='xxx' or 1=1;#'and password ='123456'";

7.2如何避免SQL注入

因为编写的SQL语句,是在用户输入数据后,整合后再编译成SQL语句。因此为了不SQL注入的问题,使SQL语句在用户输入数据前,SQL语句已经完成编译,成为了完整的SQL语句,再进行填充数据(须要在咱们控制台录入后,提早把SQL语句编译成一行长长的字符串,防止注入),想知道怎么解决,那就继续看下面的重点,记住是重点哦!

8、 PreparedStatement【重点


PreparedStatement接口继承了Statement接口。执行SQL语句的方法没有区别!

8.1 PreparedStatement的应用

做用:

  • 1.预编译SQL语句,效率高!
  • 2.安全,避免SQL注入
  • 3.能够动态的填充数据,执行多个同构的SQL语句
8.1.1 参数标记
//1.预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
8.1.2 动态参数绑定

preparedStatement.setXxx(下标,值); 参数下标是从1开始,为指定占位符下标绑定值

//2.为占位符下标赋值
preparedStatement.setString(1,username);
preparedStatement.setString(2,password);
8.1.3 解决7.1遗留下来的SQL注入问题
import java.sql.*;
import java.util.Scanner;

public class TestSafeLogin {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String username = scanner.nextLine();
        System.out.print("请输入密码:");
        String password = scanner.nextLine();

        //1.注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获取链接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8","root", "123456");
        //3.建立执行sql语句的对象
        String sql = "select * from user where username = ? and password = ?";
        //预编译SQL语句————提早把SQL语句编译为字符串,其中用到了转义字符防止个别符号注入SQL
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        /**
         * 查看预编译后的SQL语句字符串
         * 此查看不计为jdbc的开发步骤中
         */
        System.out.println(preparedStatement);

        //为占位符下标赋值
        preparedStatement.setString(1, username);
        preparedStatement.setString(2, password);

        /**
         * 查看赋值后SQL语句的字符串
         * 此查看不计为jdbc的开发步骤中
         */
        System.out.println(preparedStatement);

        //4.执行SQL语句————此时executeQuery()不须要在传入参数
        ResultSet resultSet = preparedStatement.executeQuery();
        //5.处理结果
        if (resultSet.next()) {//经过参数,查到一行数据,提示用户登陆成功!
            System.out.println("登录成功!");
        } else {
            System.out.println("登陆失败!");
        }
        //6.释放资源
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
}

输入SQL注入问题的用户名和密码得出如下结果!

查看预编译后的SQL语句字符串结果: com.mysql.jdbc.JDBC4PreparedStatement@4b9af9a9: select * from user where username = ** NOT SPECIFIED ** and password = ** NOT SPECIFIED **

查看赋值后SQL语句的字符串结果: com.mysql.jdbc.JDBC4PreparedStatement@4b9af9a9: select * from user where username = ‘lalal’ or 1=1;#’ and password = ‘asdsad’

最终结果: 登陆失败!

总结: 咱们能够看出’lalal后面’ 被加了转义字符,而 or 1=1;#后加了SQL语句应该加的username结束标志 ’ 。

9、综合练习


9.1建立数据库、表

建立数据库 account

  • 建立一张表 t_ccount。有如下列
    • cardId:字符串,主键
    • password:字符串,非空
    • username:字符串,非空
    • balance:小数,非空
    • phone:字符串,非空
#建立数据库
create database account character set utf8;
#使用account数据库
use account;
#建立表
create table t_account
(
    card_id  character(20) primary key,
    password character(50) not null,
    username character(20) not null,
    balance  double(10, 2) not null,
    phone    character(11) not null
) character set utf8;

9.2 建立项目经过JDBC实现功能

建立AccountSystem类,完成下列功能

  • 开户:控制台输入全部的帐户信息,使用PreparedStatement添加至t_account表
  • 存款:输入卡号、密码、存储金额进行修改
  • 取款:输入卡号、密码、取款金额
  • 转帐:输入卡号、密码、对方卡号、转帐金额进行修改
  • 查看余额: 输入卡号、密码,查询余额
  • 修改密码:输入卡号、密码,再输入新密码进行修改
  • 注销:输入卡号、密码,删除对应的帐户信息
import java.util.Scanner;

/**
 * 菜单
 */
public class TestAccount {
    public static void main(String[] args) {
        AccountSystem accountSystem = new AccountSystem();
        Scanner scanner = new Scanner(System.in);
        System.out.println("欢迎使用中国银行ATM机");
        int choice = 0;
        do {
            System.out.println("1.开户 2.存款 3.取款 4.转帐 5.修改密码 6.查看帐户余额 7.注销 0.退出");
            System.out.print("请选择你的操做:");
            choice = scanner.nextInt();
            switch (choice) {
                case 1:
                    accountSystem.register();
                    break;
                case 2:
                    accountSystem.saveMoney();
                    break;
                case 3:
                    accountSystem.takeMoney();
                case 4:
                    accountSystem.transfers();
                    break;
                case 5:
                    accountSystem.updatePassword();
                    break;
                case 6:
                    accountSystem.getBalance();
                    break;
                case 7:
                    accountSystem.logout();
                    break;
                case 0:
                    accountSystem.closeConnection();
                    return;
                default:
                    break;
            }
        } while (choice != 0);
    }
}

import java.sql.*;
import java.util.Scanner;

/**
 * 要求:
 * <p>
 * 开户:输入全部的帐户信息,使用PreparedStatement添加至t_account表中
 * 存款:输入卡号、密码、存储金额进行修改
 * 取款:输入卡号、密码、取款金额
 * 转帐:输入卡号、密码、须要对其转帐的卡号、转帐金额进行修改
 * 修改密码:输入卡号、密码,再输入新密码进行修改
 * 查询余额:输入卡号、密码,查询出对应的卡内余额
 * 注销:输入卡号、密码,删除对应的帐户信息
 */
public class AccountSystem {
    Scanner scanner = new Scanner(System.in);
    private static Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;

    static {
        //重复的操做,就出发一次加载便可!
        try {
            //1.加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.链接数据库
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8", "root", "Mylifes1110");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭数据库链接对象Connection
     * <p>
     * 注意:在程序结束时关闭,若是过早关闭的话,将与数据库断开链接
     * 因此咱们放在退出ATM机系统时调用关闭
     */
    public void closeConnection() {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    //开户
    public void register() {
        System.out.print("请输入卡号:");
        String card_id = scanner.next();
        System.out.print("请输入密码:");
        String password = scanner.next();
        System.out.print("请输入用户名:");
        String username = scanner.next();
        System.out.print("请输入存储金额:");
        double balance = scanner.nextDouble();
        System.out.print("请输入预留手机号:");
        String phone = scanner.next();


        //3.建立PreparedStatement,预编译
        String sql = "insert into t_account(card_id, password, username, balance, phone) values (?, ?, ?, ?, ?)";
        try {
            preparedStatement = connection.prepareStatement(sql);
            //4.为占位符赋值
            preparedStatement.setString(1, card_id);
            preparedStatement.setString(2, password);
            preparedStatement.setString(3, username);
            preparedStatement.setDouble(4, balance);
            preparedStatement.setString(5, phone);
            //5.执行SQL语句
            int result = preparedStatement.executeUpdate();
            //6.处理结果
            if (result > 0) {
                System.out.println("开户成功!");
            } else {
                System.out.println("开户失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //存款
    public void saveMoney() {
        System.out.print("请输入卡号:");
        String card_id = scanner.next();
        System.out.print("请输入密码:");
        String password = scanner.next();
        System.out.print("请输入存储金额:");
        double money = scanner.nextDouble();
        if (money > 0) {
            String sql = "update t_account set balance = balance + ? where card_id = ? and password = ?";
            //预编译
            try {
                preparedStatement = connection.prepareStatement(sql);
                //为占位符赋值
                preparedStatement.setDouble(1, money);
                preparedStatement.setString(2, card_id);
                preparedStatement.setString(3, password);
                //执行SQL语句
                int result = preparedStatement.executeUpdate();
                //处理结果
                if (result > 0) {
                    System.out.println("存款成功!");
                } else {
                    System.out.println("卡号或密码错误!");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                if (preparedStatement != null) {
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            System.out.println("输入存储金额错误!");
        }
    }

    //取款
    public void takeMoney() {
        System.out.print("请输入卡号:");
        String card_id = scanner.next();
        System.out.print("请输入密码:");
        String password = scanner.next();
        System.out.print("请输入取出金额:");
        double money = scanner.nextDouble();
        if (money > 0) {
            String sql = "update t_account set balance = balance - ? where card_id = ? and password = ?";
            //预编译
            try {
                preparedStatement = connection.prepareStatement(sql);
                //为占位符赋值
                preparedStatement.setDouble(1, money);
                preparedStatement.setString(2, card_id);
                preparedStatement.setString(3, password);
                //执行SQL语句
                int result = preparedStatement.executeUpdate();
                //处理结果
                if (result > 0) {
                    System.out.println("取款成功!");
                } else {
                    System.out.println("卡号或密码错误!");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    //查看余额
    public void getBalance() {
        System.out.print("请输入卡号:");
        String card_id = scanner.next();
        System.out.print("请输入密码:");
        String password = scanner.next();
        String sql = "select balance from t_account where card_id = ? and password = ?";
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, card_id);
            preparedStatement.setString(2, password);
            ResultSet resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                double balance = resultSet.getDouble("balance");
                System.out.println("卡内余额为:" + balance);
            } else {
                System.out.println("卡号或密码错误!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            } else if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //修改密码
    public void updatePassword() {
        System.out.print("请输入卡号:");
        String card_id = scanner.next();
        System.out.print("请输入密码:");
        String password = scanner.next();
        System.out.print("请输入新密码:");
        String newPassword = scanner.next();
        String sql = "update t_account set password = ? where card_id = ? and password = ?";
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, newPassword);
            preparedStatement.setString(2, card_id);
            preparedStatement.setString(3, password);
            int result = preparedStatement.executeUpdate();
            if (result > 0) {
                System.out.println("密码修改为功!");
            } else {
                System.out.println("卡号或密码错误!请核对后再修改!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //注销
    public void logout() {
        System.out.print("请输入卡号:");
        String card_id = scanner.next();
        System.out.print("请输入密码:");
        String password = scanner.next();
        String sql = "delete from t_account where card_id = ? and password = ?";
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, card_id);
            preparedStatement.setString(2, password);
            int result = preparedStatement.executeUpdate();
            if (result > 0) {
                System.out.println("注销成功!");
            } else {
                System.out.println("卡号或密码错误!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //转帐
    public void transfers() {
        System.out.print("请输入付款卡号:");
        String card_id = scanner.next();
        System.out.print("请输入付款密码:");
        String password = scanner.next();
        System.out.print("请输入转帐金额:");
        double money = scanner.nextDouble();
        if (money > 0) {
            String sql1 = "select balance from t_account where card_id = ? and password = ?";
            try {
                preparedStatement = connection.prepareStatement(sql1);
                preparedStatement.setString(1, card_id);
                preparedStatement.setString(2, password);
                ResultSet resultSet = preparedStatement.executeQuery();
                if (resultSet.next()) {
                    double balance = resultSet.getDouble("balance");
                    if (money <= balance) {
                        System.out.print("请输入收款卡号:");
                        String toCard_id = scanner.next();
                        String sql2 = "select balance from t_account where card_id = ?";
                        preparedStatement = connection.prepareStatement(sql2);
                        preparedStatement.setString(1, toCard_id);
                        resultSet = preparedStatement.executeQuery();
                        if (resultSet.next()) {
                            String sql3 = "update t_account set balance = balance - ? where card_id = ?";
                            preparedStatement = connection.prepareStatement(sql3);
                            preparedStatement.setDouble(1, money);
                            preparedStatement.setString(2, card_id);
                            preparedStatement.executeUpdate();
                            String sql4 = "update t_account set balance = balance + ? where card_id = ?";
                            preparedStatement = connection.prepareStatement(sql4);
                            preparedStatement.setDouble(1, money);
                            preparedStatement.setString(2, toCard_id);
                            preparedStatement.executeUpdate();
                            System.out.println("转帐成功!");
                        } else {
                            System.out.println("收款卡号不存在!");
                        }
                    } else {
                        System.out.println("卡内余额不足!");
                    }
                } else {
                    System.out.println("卡号或密码错误!");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (resultSet != null) {
                        resultSet.close();
                    }
                    if (preparedStatement != null) {
                        preparedStatement.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        } else {
            System.out.println("请从新验证,输入正确的转帐金额!");
            this.transfers();
        }
    }

10、封装工具类


10.1 重用性方案

  • 封装了获取链接、释放资源两个方法
    • 提供public static Connection getConnection()方法
    • 提供public static void closeAll(Connection conn, Statement statement,ResultSet resultSet)
10.1.1 工具实现
import java.sql.*;

/**
 * 数据库工具类
 * 1.提供链接--Connection
 * 2.提供统一资源关闭
 * 可复用性方案
 */
public class DBUtils {
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 硬编码
     * 获取链接对象
     */
    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp?useUnicode=true&characterEncoding=utf8", "root", "123456");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 释放资源
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

10.2 跨平台方案

  • 定义 private static final Properties properties = new Properties();//配置文件集合

  • 定义static {

    ​ //首次使用工具类,触发类加载

    ​ InputStream is = DBUtils.class.getResourceAsStream(“路径”);//复用本类自带流,读取配置文件

    ​ properties.load(is);//将is流中的配置文件信息,加载到集合中

    ​ Class.forName(properties.getProperty(“driver”));

    }

  • 在getConnection方法中。应用properties.getProperty(“url”);

10.2.1 实现

在src目录下新建db.properties文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql:localhost:3306/temp?useUnicode=true&characterEnding=utf8
username=root
password=123456

DBUtils代码实现

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

/**
 * 数据库工具类
 * 1.获取链接对象--Connection
 * 2.提供统一释放资源
 * 可跨平台性方案
 */
public class DBUtils {
    private static final Properties PROPERTIES = new Properties();
    static {
        try {
            //使用类自身带的流,无需关闭
            InputStream inputStream = DBUtils.class.getResourceAsStream("/db.properties");
            PROPERTIES.load(inputStream);//经过流将配置信息的内容分割成键值对
            Class.forName(PROPERTIES.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取链接对象
     * @return 返回链接对象
     */
    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 释放资源
     * @param connection 链接对象
     * @param statement 发送SQL语句的对象,是PreparedStatement的父接口,即便PreparedStatement用的普遍,父接口中也包含了它
     * @param resultSet 存储结果的结果集对象
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

11、ORM


ORM(Object Relational Mapping)

从数据库查询到的结果集(ResultSet)在进行遍历时,逐行遍历,取出的都是零散的数据。在实际应用开发中,咱们须要将零散的数据进行封装整理

11.1 ORM 实体类(entity):零散数据的载体

11.1.1 ORM应用
/**
 * create table user
 * (
 * id       int auto_increment
 * primary key,
 * username varchar(20) not null,
 * password varchar(20) not null,
 * sex      char(2)     not null,
 * email    varchar(20) not null,
 * address  varchar(50) not null
 * );
 *
 * insert into user (username, password, sex, email, address)
 * values ('ziph', '123456', '男', 'mylifes1110@163.com', '河北省')
 *
 * insert into user (username, password, sex, email, address)
 * values ('marry', '123456', '女', 'marry@163.com', '北京市')
 */
class User {
    private int id;
    private String username;
    private String password;
    private String sex;
    private String email;
    private String address;

    public User() {
    }

    public User(int id, String username, String password, String sex, String email, String address) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.sex = sex;
        this.email = email;
        this.address = address;
    }

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", email='" + email + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * User{id=1, username='ziph', password='123456', sex='男', email='mylifes1110@163.com', address='河北省'}
 * User{id=2, username='marry', password='123456', sex='女', email='marry@163.com', address='北京市'}
 */
public class OrmSelect {
    public static void main(String[] args) {
        Connection connection = DBUtils.getConnection();
        String sql = "select id,username,password,sex,email,address from user";
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {//拿到每一行数据
                //拿到每一列数据
                User user = new User();
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");
                String sex = resultSet.getString("sex");
                String email = resultSet.getString("email");
                String address = resultSet.getString("address");
                //将一行中零散的数据,封装在一个User对象里
                user.setId(id);
                user.setUsername(username);
                user.setPassword(password);
                user.setSex(sex);
                user.setEmail(email);
                user.setAddress(address);
                System.out.println(user);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, preparedStatement, resultSet);
        }
    }
}

12、DAO(Data Access Object)


数据访问对象

import www.mylifes1110.jdbc.DBUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * 对数据库中User表的一系列操做
 * 只作对数据库访问的操做!
 * 复用!对同一张表的操做,实现复用!
 */
public class UserDaoImpl {
    private Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;

    /**
     * 增
     */
    public int insert(User user) {
        try {
            connection = DBUtils.getConnection();
            String sql = "insert into user(username,password,sex,email,address) values (?,?,?,?,?)";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, user.getUsername());
            preparedStatement.setString(2, user.getPassword());
            preparedStatement.setString(3, user.getSex());
            preparedStatement.setString(4, user.getEmail());
            preparedStatement.setString(5, user.getAddress());
            int result = preparedStatement.executeUpdate();
            return result;//将操做结果返回给调用者!
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, preparedStatement, resultSet);
        }
        return 0;
    }

    /**
     * 删
     */
    public int delete(int id) {
        try {
            connection = DBUtils.getConnection();
            String sql = "delete from user where id = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, id);
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, preparedStatement, resultSet);
        }
        return 0;
    }

    /**
     * 修改
     */
    public int update(User user) {
        try {
            connection = DBUtils.getConnection();
            String sql = "update user set username = ?, password = ?, sex = ?, email = ?, address = ? where id = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, user.getUsername());
            preparedStatement.setString(2, user.getPassword());
            preparedStatement.setString(3, user.getSex());
            preparedStatement.setString(4, user.getEmail());
            preparedStatement.setString(5, user.getAddress());
            preparedStatement.setInt(6, user.getId());
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, preparedStatement, resultSet);
        }
        return 0;
    }

    /**
     * 查询一条
     */
    public User select(int id) {
        try {
            connection = DBUtils.getConnection();
            String sql = "select id, username, password, sex, email, address from user where id = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, id);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                int id1 = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");
                String sex = resultSet.getString("sex");
                String email = resultSet.getString("email");
                String address = resultSet.getString("address");
                User user = new User(id1, username, password, sex, email, address);
                return user;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, preparedStatement, resultSet);
        }
        return null;
    }

    /**
     * 查询所有
     */
    public List<User> selectAll() {
        try {
            connection = DBUtils.getConnection();
            String sql = "select id, username, password, sex, email, address from user";
            List<User> list = new ArrayList<>();
            preparedStatement = connection.prepareStatement(sql);
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");
                String sex = resultSet.getString("sex");
                String email = resultSet.getString("email");
                String address = resultSet.getString("address");
                User user = new User(id, username, password, sex, email, address);
                list.add(user);
            }
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, preparedStatement, resultSet);
        }
        return null;
    }
}
import java.util.List;

public class TestDao {
    public static void main(String[] args) {
        UserDaoImpl userDaoImpl = new UserDaoImpl();
        //调用增功能,模拟用户控制台输入,调用构造方法传参数
        User user = new User("Lucy", "123456", "女", "lucy@163.com", "河南省");
        int result = userDaoImpl.insert(user);
        if (result > 0) {
            System.out.println("添加成功!");
        } else {
            System.out.println("添加失败!");
        }
        //调用删除功能
        int result1 = userDaoImpl.delete(6);
        if (result1 > 0) {
            System.out.println("删除成功!");
        } else {
            System.out.println("删除失败!");
        }
        //调用修改功能
        User user1 = new User(5, "Marry", "666666", "女", "marry@qq.com", "深圳");
        int result2 = userDaoImpl.update(user);
        if (result2 > 0) {
            System.out.println("修改为功!");
        } else {
            System.out.println("修改失败!");
        }
        //调用查询查询一条信息
        User user2 = userDaoImpl.select(5);
        System.out.println(user);
        //调用查询全部信息
        List<User> users = userDaoImpl.selectAll();
        users.forEach(System.out::println);
    }
}

再次练习详解地址: JDBC采用DAO和Entity层完成增删改查(提供DateUtils转换工具类、DBUtils数据库链接工具类)

十3、日期类型


  • java.util.Date
    • Java语言常规应用层面的日期类型。能够经过字符串建立对应的时间对象
    • 没法直接经过JDBC插入数据库
  • java.sql.Date
    • 不能够经过字符串建立对应的时间对象。只能经过毫秒值建立对象(1970年1月1日至今的毫秒值)
    • 能够直接经过JDBC插入数据库

13.1 日期格式化工具

SimpleDateFormat 日期格式化

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");//按照指定格式转换成util.Date类型
java.util.Date date = simpleDateFormat.parse("2000-01-01");

13.2 日期工具类 DateUtils

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * 日期转换
 * <p>
 * 字符串转换为UtilDate
 * 字符串转换为SqlDate
 * UtilDate转换为SqlDate
 * 注意:SqlDate转换为UtilDate是不能够的,由于UtilDate是SqlDate的父类
 */
public class DateUtils {
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 字符串转换为UtilDate
     */
    public static java.util.Date strToUtilDate(String string) {
        try {
            return SIMPLE_DATE_FORMAT.parse(string);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * UtilDate转换为SqlDate
     */
    public static java.sql.Date utilToSqlDate(java.util.Date date) {
        return new java.sql.Date(date.getTime());
    }

    /**
     * 字符串转换为SqlDate
     * 此转换不经常使用,而在JDBC中经常使用的转换只是前两个
     */
    public static java.sql.Date strToSqlDate(String string) {
        try {
            java.util.Date date = SIMPLE_DATE_FORMAT.parse(string);
            return new java.sql.Date(date.getTime());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
13.2.1 转换流程、测试
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TestDateTimes {
    public static void main(String[] args) throws ParseException {
        /**
         * java.util.Date  当前系统时间
         * Tue Mar 31 19:44:55 CST 2020
         */
        System.out.println(new java.util.Date());

        /**
         * 字符串转换为util.Date类型
         * 1.自定义一个时间字符串
         * 2.日期转换:字符串转换为java.util.Date
         * 3.将日期字符串转换为util.Date类型
         * Sun Feb 02 00:00:00 CST 2020
         */
        String str = "2020-02-02";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        java.util.Date utilDate = simpleDateFormat.parse(str);
        System.out.println(utilDate);

        /**
         * 字符串转换为sql.Date类型
         * 1.自定义一个时间字符串
         * 2.日期转换:字符串转换为java.util.Date
         * 3.将日期类型转换为util.Date类型
         * 4.将util.Date类型转换为sql.Date类型
         * 注意:getTime()方法传入的是毫秒值
         * 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至如今的总毫秒数。
         * 2020-03-06
         */
        String string = "2020-03-06";
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        java.util.Date utilsDate = dateFormat.parse(string);
        java.sql.Date sqlDate = new java.sql.Date(utilsDate.getTime());
        System.out.println(sqlDate);

        /**
         * 利用工具将字符串转换为util.Date类型
         * Sat Feb 08 00:00:00 CST 2020
         */
        java.util.Date date = DateUtils.strToUtilDate("2020-02-08");
        System.out.println(date);

        /**
         * 利用工具将util.Date类型转换为sql.Date类型
         * 2020-02-08
         */
        java.sql.Date date1 = DateUtils.utilToSqlDate(date);
        System.out.println(date1);

        /**
         * 将当前系统时间util.Date类型转换为sql.Date类型
         * 2020-03-31
         */
        System.out.println(new java.sql.Date(new java.util.Date().getTime()));
    }
}

十4、链接池


JDBC每次链接数据库,都要得到一个链接对象。每次建立一个链接对象,都是一个较大的资源,若是在链接量较大的场景下,会极大的浪费资源。容易内存溢出。

14.1 自定义链接池

Java中提供了一个接口DataSource,经过实现该接口,能够建立链接池

14.2 Druid(德鲁伊)

Druid 是目前比较流行高性能的,分布式列存储

1、亚秒级查询

2、实时数据注入

3、可扩展的PB级存储

4、多环境部署

5、丰富的社区

14.2.1 Druid配置
  • 建立database.properties 配置文件
  • 引入druid-1.1.5.jar
14.2.2 database.properties 文件配置
#链接设置
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8
username=root
password=123456
#初始化链接
initialSize=10
#最大链接数量
maxActive=30
#最小空闲链接
minIdle=5
#超时等待时间以毫秒为单位 1000毫秒等于1秒
maxWait=5000
14.2.3 链接池工具类
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * 链接池工具类
 */
public class DBPoolUtils {
    /**
     * 链接池对象
     */
    private static DruidDataSource druidDataSource;

    static {
        Properties properties = new Properties();
        InputStream inputStream = DBPoolUtils.class.getResourceAsStream("/database.properties");
        try {
            properties.load(inputStream);
            //使用德鲁伊工厂建立链接池
            druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到数据库链接对象
     */
    public static Connection getConnection() {
        try {
            //在链接池中得到Connection
            return druidDataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 调用的close()方法不是关闭资源,而是将资源放回池中!
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
14.2.4 测试链接池
import java.sql.Connection;

public class TestDruid {
    public static void main(String[] args) {
        for (int i = 0; i < 40; i++) {
            Connection connection = DBPoolUtils.getConnection();
            System.out.println(i + "-" + connection);
//            DBPoolUtils.closeAll(connection, null, null);
        }
    }
}

此时咱们的链接池配置是初始化链接数量10,最大链接数量30,超时等待为5000毫秒。测试链接池时,for循环建立了40个链接数量(注意:释放资源close已注释掉了),超过了最大链接数量,超时等待5000毫秒后,报错!报错信息以下:

超时等待毫秒数为5000,活跃(已使用)链接为30,最大链接数为30,可建立链接数为0。

从0到29的链接都是正常使用的,因为你没有释放资源,随后从30开始打印就都是i - null(我这里从31截的图)

在这里插入图片描述

在这里插入图片描述

此次我把注释掉的调用工具释放资源注释打开,再次测试,以下:

你会发现从0到39都是使用的同一个链接,由于它使用完就又放回到了链接池中,达到了复用性的效果!

在这里插入图片描述

再次注意:connection.close();不是释放资源关闭,而是放回到了链接池中,此时Connection存放的是DruidPooledConnection实现类,调用的close不是connection关闭的,而是从新放回到链接池中,达到了复用性的效果!

十5、Service(Biz/Business)


15.1 业务

概念:用户要完成的一个业务功能,是由一个或多个的DAO调用组成。

软件、程序提供的一个功能都叫业务

15.2 业务层的实现

/**
 * Users的业务逻辑层对象
 */
public class UsersServiceImpl {
    /**
     * 用户注册功能(业务)
     */
    public String register(Users users) {//1.接收参数
        UsersDaoImpl usersDao = new UsersDaoImpl();
        //2.调用数据访问层对象的查询方法
        Users check = usersDao.select(users.getUser_name());
        if (check != null) {//用户存在
            return "用户已存在";
        }
        //3.调用数据访问层对象的新增方法
        int result = usersDao.insert(users);
        //4.将操做结果返回给调用者
        if (result > 0) {
            return "注册成功!";
        } else {
            return "注册失败!";
        }
    }

    /**
     * 用户登陆功能(业务)
     */
    public Users login(String user_name, String user_pwd) {//1.收参
        UsersDaoImpl usersDao = new UsersDaoImpl();
        //2.调用数据访问层对象的查询方法
        Users users = usersDao.select(user_name);
        //3.接收结果,处理结果
        if (users != null) {//用户存在
            //校验查询到的用户名、密码和输入的用户名、密码是否一致
            if (users.getUser_pwd().equals(user_pwd)) {
                return users;
            }
        }
        //4.响应给调用者结果
        return null;
    }
}

15.3 复用

  • DAO数据访问操做复用
  • 业务功能的复用 //不一样的终端访问

15.4 转帐案例

考虑问题: 若是在转帐业务中途,付款方帐户已扣除了转帐金额,而收款方因异常则收不到转帐金额。在SQL中该问题使用事务解决,则在JDBC中也是用事务解决此问题!

数据库链接工具类(DBUtils)

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

public class DBUtils {
    private static final Properties PROPERTIES = new Properties();

    //全部操做即为单线程操做,应用了多个Connection对象,咱们将一个线程绑定一个Connection链接使用
    private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();

    static {
        InputStream inputStream = DBUtils.class.getResourceAsStream("/db.properties");
        try {
            PROPERTIES.load(inputStream);
            Class.forName(PROPERTIES.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取链接对象
     */
    public static Connection getConnection() {
        //在ThreadLocal里取
        Connection connection = THREAD_LOCAL.get();
        //connection对象为空则建立链接对象
        if (connection == null) {
            try {
                connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
                THREAD_LOCAL.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    /**
     * 释放资源
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
                /**
                 * 关闭链接后,移除线程中绑定的链接对象
                 */
                THREAD_LOCAL.remove();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

帐户实体类(Account)

public class Account {
    private String card_id;
    private String password;
    private String username;
    private double balance;
    private String phone;

    public Account() {
    }

    public Account(String card_id, String password, String username, double balance, String phone) {
        this.card_id = card_id;
        this.password = password;
        this.username = username;
        this.balance = balance;
        this.phone = phone;
    }

    public String getCard_id() {
        return card_id;
    }

    public void setCard_id(String card_id) {
        this.card_id = card_id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Account{" +
                "card_id='" + card_id + '\'' +
                ", password='" + password + '\'' +
                ", username='" + username + '\'' +
                ", balance=" + balance +
                ", phone='" + phone + '\'' +
                '}';
    }
}

数据库操做持久层(AccountDaoImpl)

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

public class AccountDaoImpl {
    private Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;

    public int update(Account account) {
        connection = DBUtils.getConnection();
        String sql = "update t_account set password = ?, username = ?, balance = ?, phone = ? where card_id = ?";
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, account.getPassword());
            preparedStatement.setString(2, account.getUsername());
            preparedStatement.setDouble(3, account.getBalance());
            preparedStatement.setString(4, account.getPhone());
            preparedStatement.setString(5, account.getCard_id());
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(null, preparedStatement, resultSet);
        }
        return 0;
    }

    public Account select(String card_id) {
        connection = DBUtils.getConnection();
        String sql = "select card_id, password, username, balance, phone from t_account where card_id = ?";
        Account account = null;
        try {
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, card_id);
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                account = new Account(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3), resultSet.getDouble(4), resultSet.getString(5));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(null, preparedStatement, resultSet);
        }
        return account;
    }
}

Service业务层(AccountServiceImpl)

import java.sql.Connection;
import java.sql.SQLException;

public class AccountServiceImpl {
    public String transfer(String username, String password, String toCard, double money) {//收参
        String result = "转帐失败!";

        //组织业务功能
        AccountDaoImpl accountDao = new AccountDaoImpl();
        //拿一个链接对象
        Connection connection = null;
        //创建一个数据库链接
        connection = DBUtils.getConnection();

        try {
            //开启事务,而且关闭事务的自动提交
            connection.setAutoCommit(false);

            //2.1验证用户名是否存在
            Account account = accountDao.select(username);
            if (account == null) {
                throw new RuntimeException("您输入的卡号不存在!");
            }

            //2.2验证密码是否正确
            if (!account.getPassword().equals(password)) {
                throw new RuntimeException("密码错误!");
            }

            //2.3验证余额是否充足
            if (account.getBalance() < money) {
                throw new RuntimeException("卡内余额不足!");
            }

            //2.4验证收款帐户是否存在
            Account toAccount = accountDao.select(toCard);
            if (toAccount == null) {
                throw new RuntimeException("收款卡号不存在!");
            }

            //2.5扣除付款卡号内的转帐金额
            account.setBalance(account.getBalance() - money);
            accountDao.update(account);

            /**
             * 出现异常,致使程序终止
             */
//            int i = 10 / 0;

            //2.6增长收款卡号内的转帐金额
            toAccount.setBalance(toAccount.getBalance() + money);
            accountDao.update(toAccount);

            //响应客户端
            result = "转帐成功!";

            //执行到这里,没有异常,则正常提交事务!
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                //中途出现异常,回滚事务
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            //关闭链接对象
            DBUtils.closeAll(connection, null, null);
        }
        return result;
    }
}

转帐测试类(TestTransfer)

public class TestTransfer {
    public static void main(String[] args) {
        AccountServiceImpl accountService = new AccountServiceImpl();
		//模拟客户端录入信息进行转帐
        String result = accountService.transfer("1", "123456", "2", 5000);
        System.out.println(result);
    }
}

测试结果

初始化帐户为:

在这里插入图片描述

转帐后帐户为:

在这里插入图片描述

注意: 在Service层注释了一个算数异常int i = 10 / 0; 用来模拟整个事务(整个转帐操做看做一个事务)中途异常而终止查看是否用事务解决了该问题!

15.5 解决转帐事务问题

一、总结:这条是总结,但也是须要注意的点(坑)。因为转帐问题,须要介入事务解决!由于咱们加入事务解决此问题。可是上文在DBUtiils数据库链接工具中,加入了一个局部变量(ThreadLocal并非一个Thread,而是Thread的局部变量 )来绑定该线程中使用的Connection对象,使得在单线程中原使用的全部不一样的Connection对象固定为了一个(即一个线程分配且固定一个私人对象)。这样保证了在DAO层和Service层使用都是同一个链接对象,而加入事务后就成功的解决了因异常而中断以致产生的种种问题。

二、注意:总结中说到,用ThreadLocal局部变量固定了链接对象来保证同一链接对象的使用。这里解释一下是由于connection.setAutoCommit(false);开启事务、connection.commit();提交事务、connection.rollback();回滚事务都须要上下层是同一个链接对象才能够解决此问题!若是咱们没有固定此单线程的链接对象,则解决不了该问题!

这就引伸出了小伙伴们的猜测,怎么解决呢?是否是单例模式能够建立对象解决此问题呢?假如在方法的参数列表中写入须要传入一个Connection对象可不能够解决呢?那我就在下面说一下这两个问题,小伙伴们看好了!

一、单例模式:会出现一个问题,限制了建立对象,导致当前项目只能有一个客户端能链接使用转帐功能(咱们的产品就是为客户提供的,不能这么限制吧,若是只有一我的可使用这怎么办呢?那么这个产品不就是废品了吗?对吧。继续看下一个吧!)

二、参数传递Connection:若是将Service得到的Connection对象,传递给DAO各个方法。能够。​可是定义接口是为了更容易更换实现!(强调复用性)而将Connection参数定义在接口方法中,就会污染当前接口,而没法复用。咱们要知道JDBC是使用的Connection,而MyBatis使用SqlSession等等,在之后的框架中,咱们能够引用其余对象实现复用此项目,这个传入参数虽然解决了目前功能上的问题,可是脱离了咱们的初衷(再次强调复用性!),这就会产生不能复用的问题!

三、加油打气:在这里但愿你们能够对该事务解决的转帐问题有一个思想上的脉路!你们加油,继续看吧!下一章就跟进该问题,解释了一下ThreadLocal这个局部变量的知识点!给你们一个Next的决心!

十6、ThreadLocal

线程工具类:在整个线程中,一直到释放资源,用的是同一个Connection链接对象。

16.1 ThreadLocal

一、在整个线程(单条执行路径中)所持有的Map中,存储一个键(threadlocal)值(connection)对

二、线程(Thread)对象中持有一个ThreadLocalMap类型的对象(ThreadthreadLocals),threadLocals中保存了以ThreadLocal对象为Key,set进去的值为Value

三、每一个线程都可绑定多个ThreadLocal,一个线程中可存储多个ThreadLocal

注意: ThreadLocal是Thread类的一个内部类,他只是一个局部变量,不少地方叫作线程本地变量,也有些地方叫作线程本地存储,其实意思差很少。

		//绑定到线程中! 绑定到当前线程中
        ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();//0x112233

//        Thread
        //得到当前线程对象-->t.threadLocals集合为空-->create-->table[entry]-->key=0x112233 value=connection
        threadLocal.set(null);
        //得到当前线程对象-->getMap--->t.threadLocals-->getEntry(0x112233)-->entry-->entry.value
        Connection connection = threadLocal.get();

        ThreadLocal<Integer> threadLocal1 = new ThreadLocal<Integer>();//0x2345

        //每一个线程能够绑定多个ThreadLocal,
        threadLocal1.set(123);

        Integer i = threadLocal1.get();
        System.out.println(i);

16.2 ThreadLocal事务控制优化

将业务层的多步事务控制操做,封装在DBUtils工具类里。实现复用

16.2.1 DBUtils封装事务控制
/**
 * 开启事务
 */
public static void begin() {
    Connection connection = getConnection();
    try {
        connection.setAutoCommit(false);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

/**
 * 提交事务
 */
public static void commit() {
    Connection connection = getConnection();
    try {
        connection.commit();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtils.closeAll(connection, null, null);
    }
}

/**
 * 回滚事务
 */
public static void rollback() {
    Connection connection = getConnection();
    try {
        connection.rollback();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtils.closeAll(connection, null, null);
    }
}
16.1.1 数据库完整版工具类(封装了之前全部的优化,详细解析看注释)
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * 数据库工具类
 * 1.建立链接对象并链接数据库
 * 2.释放资源
 * 3.加入开启、提交、回滚事务
 */
public class DBUtils {
    /**
     * 新建Properties集合,存放配置信息,以便后续流的读取
     */
    private static final Properties PROPERTIES = new Properties();

    /**
     * 全部操做即为单线程操做,应用了多个Connection对象,咱们将一个线程绑定一个Connection链接使用
     */
    private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 读取配置文件类加载时,只须要加载一次便可
     */
    static {
        /**
         * 使用类自身带的流读取配置,无需关闭
         */
        InputStream inputStream = DBUtils.class.getResourceAsStream("/db.properties");
        try {
            /**
             * 经过流将配置信息的内容分割成键值对,并链接数据库
             */
            PROPERTIES.load(inputStream);
            /**
             * 注册加载驱动
             */
            Class.forName(PROPERTIES.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取链接对象
     */
    public static Connection getConnection() {
        /**
         * 在ThreadLocal里取链接对象
         */
        Connection connection = THREAD_LOCAL.get();
        /**
         * connection对象为空则建立链接对象
         */
        if (connection == null) {
            try {
                connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
                /**
                 * 把链接对象存入ThreadLocal里
                 */
                THREAD_LOCAL.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    /**
     * 释放资源
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
                /**
                 * 关闭链接后,移除线程中绑定的链接对象
                 */
                THREAD_LOCAL.remove();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 开启事务
     */
    public static void begin() {
        Connection connection = getConnection();
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public static void commit() {
        Connection connection = getConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, null, null);
        }
    }

    /**
     * 回滚事务
     */
    public static void rollback() {
        Connection connection = getConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, null, null);
        }
    }
}

十7、三层架构设计


  • 表示层:
    • 命名:xxxVIew
    • 职责:收集用户的数据和需求、展现数据
  • 业务逻辑层
    • 命名:XXXServiceImpl
    • 职责:数据的加工处理、调用Dao组合完成业务实现、控制事务
  • 数据访问层
    • 命名:xxxDaoImpl
    • 职责:向业务层提供数据,将业务层加工处理后的数据同步到数据库
17.1 三层架构设计思想

在这里插入图片描述

咱们都知道公司里的项目是分组、分模块作的!咱们如何无缝链接的作好本身份内的模板呢?那就须要分层了,你分的架构层越清晰就越容易被人接纳!

那么咱们进入一个模拟现实的遐想空间,你玩一个游戏,你们都知道游戏是会更新的,至于更新什么呢,大概就是更新优化老版本的Bug、增长用户调研反馈的游戏功能优化等等,目的就是优化用户体验!那么咱们在更新的时候,老版本如何跨度到新版本呢?这就须要修改一些数据了,或者加入一些功能方法。定义一些接口!

那定义接口是为了什么呢?有些小伙伴会想,直接去实现功能很差吗?那么回到刚才的问题,咱们都是分模块作的项目,怎么保证你项目定义的功能方法和其余同事的功能方法作到无缝衔接?那就是利用了接口,咱们用接口来约束架构中所用到的方法,约束他们的命名、参数、方法规定等,这些不少程序员拿来就能够达到与其余同事的项目无缝衔接!并且有利于后期维护!

而还有一个好处就是当你在更新的时候,写好了新版本的方法等,直接去项目中简单修改更换实现类便可。这样就避免了大篇幅的修改项目代码,还保证了代码的安全性!

17.2 三层架构设计的好处与建议

好处:

一、面向接口编程,易修改、易扩展。耦合度低,Service层和Dao层设计接口,便于更换实现类!

二、使得后期对产品的维护更简单

三、划分层次清晰可见,有利于工做的合做完成,提升工做效率

建议:

知道了分层的重要性,在JavaSE的时候我就说过,分包写不要嫌麻烦,这下知道了吧。多了就不罗嗦了,如今懂得培养习惯还不晚,后面学到的框架等等,分的会更多的,因此咱们要从如今开始养成好习惯,为之后学习作好铺垫!俗话说:“凡事要从娃娃抓起嘛!”,哈哈

十8、工具类型的封装及普适性泛型工具


18.1 封装DML方法

	/**
     * 公共处理增、删、改方法
     * sql语句,参数列表
     *
     * @param sql  执行的sql语句
     * @param args 参数列表,占位符赋值
     * @return 受影响行数
     */
    public int commonsUpdate(String sql, Object... args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        connection = DBUtils.getConnection();
        try {
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(null, preparedStatement, null);
        }
        return 0;
    }

18.2 封装DQL方法

 /**
     * 公共查询方法(可查询单条信息,也能够查询多条信息,能够查任何一张表)
     * 实现了复用性
     *
     * @param sql       执行的sql语句
     * @param rowMapper 封装对象的接口,准备接口回调
     * @param args      参数列表,占位符赋值
     * @return 返回查询到的对象的集合
     */
    public List<T> commonsSelect(String sql, RowMapper<T> rowMapper, Object... args) {
        List<T> elements = new ArrayList<>();

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        connection = DBUtils.getConnection();
        try {
            preparedStatement = connection.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    preparedStatement.setObject(i + 1, args[i]);
                }
            }
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                /**
                 * 根据查询到的结果完成ORM,如何进行对象的建立及赋值呢?
                 * 接口回调---调用者提供了一个封装方法ORM
                 */
                T t = rowMapper.getRow(resultSet);
                elements.add(t);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(null, preparedStatement, resultSet);
        }
        return elements;
    }

18.3 最终版Account项目(包含全部工具、功能以及优化)

项目分层

在这里插入图片描述

Properties配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/account?useUnicode=true&characterEncoding=utf8
username=root
password=Mylifes1110
entity
package www.mylifes1110.jdbc.day5.t2.java.entity;

public class Account {
    private String card_id;
    private String password;
    private String username;
    private double balance;
    private String phone;

    public Account() {
    }

    public Account(String card_id, String password, String username, double balance, String phone) {
        this.card_id = card_id;
        this.password = password;
        this.username = username;
        this.balance = balance;
        this.phone = phone;
    }

    public String getCard_id() {
        return card_id;
    }

    public void setCard_id(String card_id) {
        this.card_id = card_id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Account{" +
                "card_id='" + card_id + '\'' +
                ", password='" + password + '\'' +
                ", username='" + username + '\'' +
                ", balance=" + balance +
                ", phone='" + phone + '\'' +
                '}';
    }
}
DateUtils
package www.mylifes1110.jdbc.day5.t2.java.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * 日期转换
 * <p>
 * 字符串转换为UtilDate
 * 字符串转换为SqlDate
 * UtilDate转换为SqlDate
 * 注意:SqlDate转换为UtilDate是不能够的,由于UtilDate是SqlDate的父类
 */
public class DateUtils {
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * 字符串转换为UtilDate
     */
    public static java.util.Date strToUtilDate(String string) {
        try {
            return SIMPLE_DATE_FORMAT.parse(string);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * UtilDate转换为SqlDate
     */
    public static java.sql.Date utilToSqlDate(java.util.Date date) {
        return new java.sql.Date(date.getTime());
    }

    /**
     * 字符串转换为SqlDate
     * 此转换不经常使用,而在JDBC中经常使用的转换只是前两个
     */
    public static java.sql.Date strToSqlDate(String string) {
        try {
            java.util.Date date = SIMPLE_DATE_FORMAT.parse(string);
            return new java.sql.Date(date.getTime());
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}
DBUtils
package www.mylifes1110.jdbc.day5.t2.java.utils;

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

/**
 * 数据库工具类
 * 1.建立链接对象并链接数据库
 * 2.释放资源
 * 3.加入开启、提交、回滚事务
 */
public class DBUtils {
    /**
     * 新建Properties集合,存放配置信息,以便后续流的读取
     */
    private static final Properties PROPERTIES = new Properties();

    /**
     * 全部操做即为单线程操做,应用了多个Connection对象,咱们将一个线程绑定一个Connection链接使用
     */
    private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 读取配置文件类加载时,只须要加载一次便可
     */
    static {
        /**
         * 使用类自身带的流读取配置,无需关闭
         */
        InputStream inputStream = DBUtils.class.getResourceAsStream("/db.properties");
        try {
            /**
             * 经过流将配置信息的内容分割成键值对,并链接数据库
             */
            PROPERTIES.load(inputStream);
            /**
             * 注册加载驱动
             */
            Class.forName(PROPERTIES.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取链接对象
     */
    public static Connection getConnection() {
        /**
         * 在ThreadLocal里取链接对象
         */
        Connection connection = THREAD_LOCAL.get();
        /**
         * connection对象为空则建立链接对象
         */
        if (connection == null) {
            try {
                connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
                /**
                 * 把链接对象存入ThreadLocal里
                 */
                THREAD_LOCAL.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    /**
     * 释放资源
     */
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
                /**
                 * 关闭链接后,移除线程中绑定的链接对象
                 */
                THREAD_LOCAL.remove();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 开启事务
     */
    public static void begin() {
        Connection connection = getConnection();
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public static void commit() {
        Connection connection = getConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, null, null);
        }
    }

    /**
     * 回滚事务
     */
    public static void rollback() {
        Connection connection = getConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(connection, null, null);
        }
    }
}
DaoUtils
package www.mylifes1110.jdbc.day5.t2.java.utils;

import www.mylifes1110.jdbc.day5.t2.java.advanced.RowMapper;

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 DaoUtils<T> {
    /**
     * 公共处理增、删、改方法
     * sql语句,参数列表
     *
     * @param sql  执行的sql语句
     * @param args 参数列表,占位符赋值
     * @return 受影响行数
     */
    public int commonsUpdate(String sql, Object... args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;

        connection = DBUtils.getConnection();
        try {
            preparedStatement = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            return preparedStatement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(null, preparedStatement, null);
        }
        return 0;
    }

    /**
     * 公共查询方法(可查询单条信息,也能够查询多条信息,能够查任何一张表)
     * 实现了复用性
     *
     * @param sql       执行的sql语句
     * @param rowMapper 封装对象的接口,准备接口回调
     * @param args      参数列表,占位符赋值
     * @return 返回查询到的对象的集合
     */
    public List<T> commonsSelect(String sql, RowMapper<T> rowMapper, Object... args) {
        List<T> elements = new ArrayList<>();

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        connection = DBUtils.getConnection();
        try {
            preparedStatement = connection.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    preparedStatement.setObject(i + 1, args[i]);
                }
            }
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                /**
                 * 根据查询到的结果完成ORM,如何进行对象的建立及赋值呢?
                 * 接口回调---调用者提供了一个封装方法ORM
                 */
                T t = rowMapper.getRow(resultSet);
                elements.add(t);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBUtils.closeAll(null, preparedStatement, resultSet);
        }
        return elements;
    }
}
AccountDao
package www.mylifes1110.jdbc.day5.t2.java.dao;

import www.mylifes1110.jdbc.day5.t2.java.entity.Account;

import java.util.List;

/**
 * 增、删、改、查一条信息、查全部信息
 */
public interface AccountDao {
    /**
     * 新增操做
     *
     * @param account Account对象
     * @return 返回一条影响行数
     */
    int insert(Account account);

    /**
     * 删除操做
     *
     * @param card_id 卡号
     * @return 返回一条受影响行数
     */
    int delete(String card_id);

    /**
     * 修改操做
     *
     * @param account Account对象
     * @return 返回一条影响行数
     */
    int update(Account account);

    /**
     * 查询一条信息操做
     *
     * @param card_id 卡号
     * @return 根据卡号返回一条Account对象信息
     */
    Account select(String card_id);

    /**
     * 查询全部信息操做
     *
     * @return 查询出全部信息返回全部Account对象集合
     */
    List<Account> selectAll();
}
AccountDaoImpl
package www.mylifes1110.jdbc.day5.t2.java.dao.impl;

import www.mylifes1110.jdbc.day5.t2.java.advanced.impl.AccountRowMapper;
import www.mylifes1110.jdbc.day5.t2.java.dao.AccountDao;
import www.mylifes1110.jdbc.day5.t2.java.entity.Account;
import www.mylifes1110.jdbc.day5.t2.java.utils.DaoUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;

/**
 * 数据库访问层(持久层)
 */
public class AccountDaoImpl implements AccountDao {
    private Connection connection = null;
    private PreparedStatement preparedStatement = null;
    private ResultSet resultSet = null;
    /**
     * DAO通用的方法工具
     */
    private DaoUtils<Account> daoUtils = new DaoUtils<>();

    /**
     * 新增功能实现
     *
     * @param account Account对象
     * @return 返回一个受影响行数
     */
    @Override
    public int insert(Account account) {
        String sql = "insert into t_account (card_id, password, username, balance, phone) values (?, ?, ?, ?, ?)";
        Object[] args = {account.getCard_id(), account.getUsername(), account.getPassword(), account.getBalance(), account.getPhone()};
        return daoUtils.commonsUpdate(sql, args);
    }

    /**
     * 删除功能实现
     *
     * @param card_id 卡号
     * @return 返回一个受影响行数
     */
    @Override
    public int delete(String card_id) {
        return daoUtils.commonsUpdate("delete from t_account where card_id = ?", card_id);
    }

    /**
     * 修改功能实现
     *
     * @param account Account对象
     * @return 返回一个受影响行数
     */
    @Override
    public int update(Account account) {
        String sql = "update t_account set password = ?, username = ?, balance = ?, phone = ? where card_id = ?";
        Object[] args = {account.getPassword(), account.getUsername(), account.getBalance(), account.getPhone(), account.getCard_id()};
        return daoUtils.commonsUpdate(sql, args);
    }

    /**
     * 查询一条信息操做实现
     *
     * @param card_id 卡号
     * @return 返回查询到的Account对象
     */
    @Override
    public Account select(String card_id) {
        List<Account> accountList = daoUtils.commonsSelect("select card_id, password, username, balance, phone from t_account where card_id = ?", new AccountRowMapper(), card_id);
        if (accountList != null && accountList.size() != 0) {
            return accountList.get(0);
        }
        return null;
    }

    /**
     * 查询全部信息操做实现
     *
     * @return 返回查询全部信息的集合
     */
    @Override
    public List<Account> selectAll() {
        return daoUtils.commonsSelect("select card_id, password, username, balance, phone from t_account", new AccountRowMapper());
    }
}
AccountService
package www.mylifes1110.jdbc.day5.t2.java.service;

import www.mylifes1110.jdbc.day5.t2.java.entity.Account;

import java.util.List;

public interface AccountService {
    /**
     * 转帐功能
     *
     * @param card_id   付款方卡号
     * @param password  付款方卡密
     * @param toCard_id 收款方卡号
     * @param money     转帐金额
     * @return 返回字符串提示信息
     */
    String transfer(String card_id, String password, String toCard_id, double money);

    /**
     * 注册功能
     *
     * @param account Account对象
     * @return 返回字符串提示信息
     */
    String register(Account account);

    /**
     * 登陆功能
     * @param card_id 卡号
     * @param password 卡密
     * @return 返回字符串提示信息
     */
    Account login(String card_id, String password);

    /**
     * 查询全部帐户信息(经理权限)
     */
    List<Account> showAllAccount();

    /**
     * 销户
     * @param card_id
     * @return
     */
    String delete(String card_id);

    /**
     * 查一条信息
     * @param card_id 卡号
     * @return 返回查到的一个Account对象
     */
    Account select(String card_id);
}
AccountServiceImpl
package www.mylifes1110.jdbc.day5.t2.java.service.impl;

import www.mylifes1110.jdbc.day5.t2.java.dao.impl.AccountDaoImpl;
import www.mylifes1110.jdbc.day5.t2.java.entity.Account;
import www.mylifes1110.jdbc.day5.t2.java.service.AccountService;
import www.mylifes1110.jdbc.day5.t2.java.utils.DBUtils;

import java.util.List;

/**
 * 业务逻辑层
 */
public class AccountServiceImpl implements AccountService {
    private AccountDaoImpl accountDao = new AccountDaoImpl();

    /**
     * 转帐功能实现
     *
     * @param card_id   付款方卡号
     * @param password  付款方卡密
     * @param toCard_id 收款方卡号
     * @param money     转帐金额
     * @return 返回字符串提示信息
     */
    @Override
    public String transfer(String card_id, String password, String toCard_id, double money) {
        String result = "转帐失败!";

        try {
            DBUtils.begin();
            Account account = null;
            try {
                account = accountDao.select(card_id);
            } catch (Exception e) {
                throw new RuntimeException("卡号不存在!");
            }

            if (!account.getPassword().equals(password)) {
                throw new RuntimeException("密码错误!");
            }
            if (account.getBalance() < money) {
                throw new RuntimeException("卡内余额不足!");
            }
            Account toAccount = null;
            try {
                toAccount = accountDao.select(toCard_id);
            } catch (Exception e) {
                throw new RuntimeException("收款方卡号不存在!");
            }

            account.setBalance(account.getBalance() - money);
            accountDao.update(account);
            toAccount.setBalance(toAccount.getBalance() + money);
            accountDao.update(toAccount);

            DBUtils.commit();
            return "转帐成功!";
        } catch (Exception e) {
            e.printStackTrace();
            DBUtils.rollback();
        }
        return result;
    }

    /**
     * 注册功能实现
     *
     * @param account Account对象
     * @return 返回字符串提示信息
     */
    @Override
    public String register(Account account) {
        try {
            DBUtils.begin();
            Account checkAccount = accountDao.select(account.getCard_id());
            if (checkAccount != null) {
                throw new RuntimeException("卡号已存在!");
            }
            int result = accountDao.insert(account);
            if (result > 0) {
                DBUtils.commit();
                return "注册成功!";
            } else {
                return "注册失败!";
            }
        } catch (Exception e) {
            DBUtils.rollback();
            e.printStackTrace();
        }
        return "注册失败!";
    }

    /**
     * 登陆功能实现
     *
     * @param card_id  卡号
     * @param password 卡密
     * @return 返回一个校验过的Account对象;没有校验经过就返回null
     */
    @Override
    public Account login(String card_id, String password) {
        Account checkAccount = accountDao.select(card_id);
        if (checkAccount == null)
            throw new RuntimeException("卡号不存在!");
        if (checkAccount.getPassword().equals(password)) {
            System.out.println("登录成功!");
            return checkAccount;
        }
        System.out.println("登陆失败!");
        return null;
    }

    @Override
    public List<Account> showAllAccount() {
        return accountDao.selectAll();
    }

    /**
     * 删除功能实现
     *
     * @param card_id 卡号
     * @return 返回字符串提示信息
     */
    @Override
    public String delete(String card_id) {
        try {
            DBUtils.begin();
            int result = accountDao.delete(card_id);
            if (result > 0) {
                DBUtils.commit();
                return "删除成功!";
            } else {
                return "删除失败!";
            }
        } catch (Exception e) {
            DBUtils.rollback();
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public Account select(String card_id) {
        Account result = accountDao.select(card_id);
        if (result != null) {
            return result;
        }
        System.out.println("您查询的卡号信息不存在!");
        return null;
    }
}
RowMapper
package www.mylifes1110.jdbc.day5.t2.java.advanced;

import java.sql.ResultSet;

/**
 * 约定,接受结果集。建立对象
 */
public interface RowMapper<T> {
    public T getRow(ResultSet resultSet);
}
AccountRowMapper
package www.mylifes1110.jdbc.day5.t2.java.advanced.impl;

import www.mylifes1110.jdbc.day5.t2.java.advanced.RowMapper;
import www.mylifes1110.jdbc.day5.t2.java.entity.Account;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 接口实现类,明确建立的对象,并返回
 */
public class AccountRowMapper implements RowMapper<Account> {

    @Override
    public Account getRow(ResultSet resultSet) {
        Account account = null;
        try {
            account = new Account(resultSet.getString(1), resultSet.getString(2), resultSet.getString(3), resultSet.getDouble(4), resultSet.getString(5));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return account;
    }
}
TestDemo
package www.mylifes1110.jdbc.day5.t2.java.view;

import www.mylifes1110.jdbc.day5.t2.java.service.impl.AccountServiceImpl;

/**
 * 测试类
 * 注意:有些功能在测试的时候不能一块儿测试,会有冲突,因此我把他们全都注释了!
 */
public class TestDemo {
    public static void main(String[] args) {
        AccountServiceImpl accountService = new AccountServiceImpl();
        /**
         * 测试转帐功能
         */
//        String result = accountService.transfer("1", "123456", "2", 100);
//        System.out.println(result);

        /**
         * 测试注册功能
         */
//        Account account = new Account("4", "123456", "lala", 100, "11111111122");
//        String result = accountService.register(account);
//        System.out.println(result);

        /**
         * 测试登陆功能
         */
//        accountService.login("1", "123456");

        /**
         * 测试查看全部信息
         */
//        accountService.showAllAccount().forEach(System.out::println);

        /**
         * 测试查看一条信息
         */
//        System.out.println(accountService.select("1"));

        /**
         * 测试删除功能
         */
//        accountService.delete("4");
    }
}

十9、Apache的DbUtils使用


Commons DbUtils 是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能勾简化JDBC应用程序的开发!同时,不会影响程序的性能

19.1 DbUtils简介

  • DbUtils是Java编程中数据库操做实用小工具,小巧、简单、实用
    • 对于数据表的查询操做,能够吧结果转换为List、Array、Set等集合。便于操做
    • 对于数据表的DML操做,也变得很简单(只须要写SQL语句);
19.1.1 DbUtils主要包含
  • ResultSetHandler接口:转换类型接口

    • BeanHandler类:实现类,把一条记录转换成对象
    • BeanListHandler类:实现类,把多条记录转换成List集合。
    • ScalarHandler类:实现类,适合获取一行一列的数据。
  • QueryRunner:执行sql语句的类

    • 增、删、改:update();

    • 查询:query();

19.2 DbUtils的使用步骤

  • 导入jar包
    • mysql链接驱动mysql-connector-java-5.1.25-bin.jar
    • druid-1.1.5.jar
    • database.properties配置文件
    • commons-dbutils-1.6.jar
19.2.1 代码实现
DBUtils工具类
package com.project.utils;

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

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 链接池工具类
 */
public class DBUtils {
    private static DruidDataSource dataSource;

    static {
        Properties properties = new Properties();
        InputStream inputStream = DBUtils.class.getResourceAsStream("/db.properties");
        try {
            properties.load(inputStream);
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 返回一个数据源
    public static DataSource getDataSource(){
        return dataSource;
    }
}

UserDaoImpl 数据访问对象
package com.project.dao.impl;

import com.project.dao.UserDao;
import com.project.entity.User;
import com.project.utils.DBUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

public class UserDaoImpl implements UserDao {
    //1.建立QueryRunner对象,并传递一个数据源对象
    private QueryRunner queryRunner = new QueryRunner(DBUtils.getDataSource());
    @Override
    public int insert(User user) {
        Object[] params={user.getId(),user.getUsername(),user.getPassword(),user.getSex(),user.getEmail(),user.getAddress()};
        try {
            return queryRunner.update("insert into user (id,username,password,sex,email,address) values(?,?,?,?,?,?)",params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public int update(User user) {
        Object[] params={user.getUsername(),user.getPassword(),user.getSex(),user.getEmail(),user.getAddress(),user.getId()};
        try {
            return queryRunner.update("update user set username=?,password=?,sex=?,email=?,address=? where id = ?",params);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public int delete(int id) {
        try {
            return queryRunner.update("delete from user where id = ?",id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }

    @Override
    public User select(int id) {
        try {
            //把查询到的记录封装成 指定对象
            return queryRunner.query("select * from user where id = ?", new BeanHandler<User>(User.class), id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查询全部
     * @return
     */
    @Override
    public List<User> selectAll() {
        try {
            return queryRunner.query("select * from user;",new BeanListHandler<User>(User.class));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
}

二10、大结局

在这里JDBC的内容就大结局了!你们加油吧!后面还有更多挑战等着你呢。一块儿加油!我会持续分享各类知识文章!我也是一个普通人!从此会从各个方面去写、去上传的文章,知识会随浅到深,专栏也会从JavaSE基础到整个Java结束以及Java的进阶深刻,你们能够按顺序学习、复盘了。其中我还会分享一些我本身学到解决的算法问题!总之,博客就是我学习的家,我会不断的上传并写好个人文章!持续成长中…