JDBC!从精通到入土

1、JDBC基础

JDBC:Java Database Conectivity

Java操作数据库的一种技术,在Java中定义了一套操作数据库的标准!

1、JDBC的基本规范

2、预处理语句

3、DAO模式

4、数据库连接池

5、DBUtils的使用

公司域名.项目名称.模块.结构名

com.itluma.myshop.user.dao

1.1 JDBC介绍

JDBC是java中规范操作数据库的一套技术,这套技术中就定义了大量的接口

  • Connection:连接接口
  • DriverManager:驱动管理器
  • Statement:声明接口
  • ResultSet:结果集接口
  • PreparedStatement:预处理声明接口

在这里插入图片描述

  • 规范:JDBC,java操作数据库的一套标准
  • 驱动:每个数据库按照规范要求的具体实现
1.2 JDBC入门案例

JDBC的实现过程

1、加载驱动

2、建立连接对象

3、创建声明语句

4、发送SQL语句

5、分析结果集(只有查询才会需要)

6、关闭

在这里插入图片描述
在这里插入图片描述

在开始JDBC之前,先准备好驱动包

mysql-connector-java-5.1.37-bin.jar,并还需要将该驱动包添加到项目的类路径中
在这里插入图片描述

将jar拷贝到该lib目录中,选中jar包,右键,Add as Library

在这里插入图片描述

在这里插入图片描述

class MyTest{
    public MyTest(){
        System.out.println("3 MyTest构造函数");
    }

    static{
        //在类加载的时候执行,而且每个类在它的生命过程中只会加载一次
        System.out.println("1 static代码块!");
    }

    {
        System.out.println("2 普通代码块!");
    }
}

1 static代码块!
2 普通代码块!
3 MyTest构造函数
2 普通代码块!
3 MyTest构造函数

public class JdbcTest {
    public static void main(String[] args) {

        try {
            //1、加载驱动
            Class.forName("com.mysql.jdbc.Driver");

            //2、建立连接对象
            //建立连接的url地址格式:jdbc:mysql://数据库地址:3306/数据库名
            String url = "jdbc:mysql://localhost:3306/companydb";
            Connection conn = DriverManager.getConnection(url, "root", "123456");

            //3、创建声明语句
            Statement statement = conn.createStatement();

            //4、发送SQL语句
            String sql = "select * from t_countries";
            ResultSet rs = statement.executeQuery(sql);

            System.out.println(rs);

            // 5、分析结果集(只有查询才会需要)
            while (rs.next())//true:当前行存在数据, false:当前行没有数据了
            {
                //显示当前行中的每一列数据
                //rs.getXXXX("列名"):获取当前列对应的数据,XXXX:当前列的数据类型
                System.out.println(rs.getString("country_id") + "  " + rs.getString("country_name"));
            }

            //6、关闭
            rs.close();
            statement.close();
            conn.close();

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
1.3 API说明
  • 加载驱动:创建一个驱动对象, new xxx()

Class.forName(“com.mysql.jdbc.Driver”);//通过反射创建一个Driver对象,在JDK1.8中可以省略

  • 获取连接对象:建立数据库和Java程序之间的连接
    • url:连接的格式:
      • jdbc:mysql://localhost:3306/companydb
      • 协议:子协议://数据库服务器地址:端口/数据库名
    • username、password:访问数据库的用户名和密码

DriverManager.getConnection(url, username, password);

  • 连接对象:java.sql.Connection

    Statement createStatement() throws SQLException;//获取声明对象,主要用来操作SQL语句
    
  • 声明对象:java.sql.Statement,操作SQL语句,并返回相应结果

    Statement statement = conn.createStatement();

    常用方法:

    • ResultSet executeQuery(String sql):执行select语句
    • int executeUpdate(String sql):执行insert,update,delete,返回的是影响的行数
    • boolean execute(String sql):可以执行各种sql语句,如果执行的是select则返回true,否则返回false
  • 结果集:java.sql.ResultSet(执行insert,update,delete不要处理结果集对象了)

结果集其实就是SELECT语句执行的结果,可以理解成一个二维表,就分行和列。boolean next()指向某行的数据,如果是true则表示当前行存在数据,否则表示结束!getXXX(字段名),获取当前行指定列的数据,xxx是表示对应的字段类型

rs.next()://指向当前行,如果返回true则表示当前行存在
rs.getInt("字段");//rs.getInt(1);1表示当前字段在表中的位置
  • 释放资源
1.4 JDBC工具类

我们发现,只要进行JDBC操作,都需要先加载驱动然后建立连接对象,执行完成之前需要关闭连接对象。可以考虑设计一个通用代码出来。

创建新项目

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

定义工具类

package com.itluma.utils;

import java.sql.*;


public class JdbcUtils {
    //1.加载驱动
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //2.建立连接
    public static Connection getConnection() {
        try {
            Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb", "root", "123456");
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;

    }

    //3.实现资源
    public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null)
                rs.close();

            if (stmt != null) {
                stmt.close();
            }

            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

driverClassName、url、username、password

这几个参数应该可配置的,那么不要直接写在java文件中,然后在java程序中去读取配置文件中的参数值。

可以使用properties文件保存数据,该文件存储数据的特点是key=value

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

driverName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/companydb
username=root
password=123456
package com.itluma.utils;

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


public class JdbcUtils {

    private static final Properties PROPERTIES = new Properties();

    //1.加载驱动
    static {
        //
        InputStream is = JdbcUtils.class.getResourceAsStream("/db.properties");

        try {
            PROPERTIES.load(is);
            Class.forName(PROPERTIES.getProperty("driverName"));
        } catch (ClassNotFoundException | IOException e) {
            e.printStackTrace();
        }
    }

    //2.建立连接
    public static Connection getConnection() {
        try {
            Connection conn = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
            return conn;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    //3.实现资源
    public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null)
                rs.close();

            if (stmt != null) {
                stmt.close();
            }

            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Properties类是专门用来获取Properties文件中的数据的

public class GetDataFromPropertiesFile {
    public static void main(String[] args) {
        //将Properties文件中的值读取出来
        Properties p = new Properties();
        //InputStream is = new FileInputStream("C;//xxxx/src/db.properties");
        //1、构建流对象
        InputStream is = GetDataFromPropertiesFile.class.getResourceAsStream("/db.properties");
        try {
            //2、加载properties文件
            p.load(is);

            //3、获取propeties中driverName指定的value
            System.out.println(p.getProperty("driverName"));
            System.out.println(p.getProperty("url"));
            System.out.println(p.getProperty("username"));
            System.out.println(p.getProperty("password"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
1.5 JDBC增删改查操作
1.5.1 增加
public class InsertJdbc {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = JdbcUtils.getConnection();
            stmt = conn.createStatement();
            String sql = "insert into t_countries values('AA','Axxxaaxxx')";
            int rows = stmt.executeUpdate(sql);
            if (rows > 0) {
                System.out.println("添加成功!");
            } else {
                System.out.println("添加失败!");
            }


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, stmt, null);
        }
    }
}
1.5.2 修改
package com.itluma.dbtest;

import com.itluma.utils.JdbcUtils;

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


public class UpdateJdbc {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = JdbcUtils.getConnection();
            stmt = conn.createStatement();
            String sql = "update t_countries set country_name='American' where country_id='AA'";
            int rows = stmt.executeUpdate(sql);
            if (rows > 0) {
                System.out.println("修改成功!");
            } else {
                System.out.println("修改失败!");
            }


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, stmt, null);
        }
    }
}
1.5.3 删除
public class DeleteJdbc {
    public static void main(String[] args) {
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = JdbcUtils.getConnection();
            stmt = conn.createStatement();
            String sql = "delete from t_countries where country_id='AA'";
            int rows = stmt.executeUpdate(sql);
            if (rows > 0) {
                System.out.println("删除成功!");
            } else {
                System.out.println("删除失败!");
            }


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, stmt, null);
        }
    }
}
1.5.4 根据id查询
public class FindByIdJdbc {
    public static void main(String[] args) {
        //从键盘中获取country_id值
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入country_id:");
        String countryId = scan.next();


        try {

            Connection conn = JdbcUtils.getConnection();
            //3、创建声明语句
            Statement statement = conn.createStatement();

            //4、发送SQL语句
            String sql = "select * from t_countries where country_id='"+countryId+"'";
            System.out.println(sql);
            ResultSet rs = statement.executeQuery(sql);


            // 5、分析结果集(只有查询才会需要)
            if (rs.next()) {
                System.out.println(rs.getString("country_id") + "  " + rs.getString("country_name"));
            }

            //6、关闭
            JdbcUtils.closeAll(conn, statement, rs);

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
1.6 预处理对象(PreparedStatement)
1.6.1 SQL注入问题

用户登录案例:

create table users(
	id int primary key auto_increment,
 username varchar(200),
 password varchar(200)
);

insert into users values(null, 'admin','admin');
insert into users values(null, 'zhangsan','123456');

#实现一个用户登录
select *from users where username='输入的用户名' and password='输入的密码';

用户名和密码,是用户从输入设备输入进来,可以是任意字符组成!

usrname=‘XXXX’ or 1=1 #’

select *from users where username=‘XXXX’ or 1=1 --’ and password=‘输入的密码’;

SQL注入问题:就是输入一些特殊字符,破坏了SQL语句原有的业务功能!

public class SQLInject {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String name = scan.nextLine();
        System.out.print("请输入密码:");
        String password = scan.nextLine();

        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            stmt = conn.createStatement();

            String sql = "SELECT * FROM users WHERE username='" + name + "' AND PASSWORD='" + password + "'";

            rs = stmt.executeQuery(sql);
            if (rs.next()) {
                System.out.println("登录成功!");
            } else {
                System.out.println("用户名或密码错误!");
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, stmt, rs);
        }
    }
}
请输入用户名:fasdfasdf' or 1=1 #
请输入密码:fjasdklfasdf
登录成功!

我们发现SQL验证用户的功能失效了!!!

PreparedStatement可以防止出现SQL注入问题!

1.6.2 PreparedStatment

预编译对象,是Statement的子 接口

  • 性能高
  • 会把SQL语句提前进行编译
  • 而且会过滤用户输入的一些关键词
//SQL语句中的值只需要使用占位符?即可。具体值在执行这条SQL语句时提供
String sql = "SELECT * FROM users WHERE username=? AND PASSWORD=?;

使用PreparedStatement步骤:

1、创建PreparedStatement对象

PreparedStatement pstmt = conn.prepareStatement(sql);

2、设置占位符所对应的参数

setXXX(int index, Xxx xxx):设置指定位置对应的值

参数1:index实际占位符的位置,从1开始

参数2:xxx实际参数值, Xxx表示具体的类型

pstmt.setString(1, name)

3、执行SQL语句

ResultSet executeQuery():执行select语句

int executeUpdate():执行insert,update,delete语句

boolean execute():执行select返回true,否则返回false

public class SQLInject2 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入用户名:");
        String name = scan.nextLine();
        System.out.print("请输入密码:");
        String password = scan.nextLine();

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = JdbcUtils.getConnection();
            String sql = "SELECT * FROM users WHERE username=? AND PASSWORD=?";
            pstmt = conn.prepareStatement(sql);
            
            //设置占位符的内容
            pstmt.setString(1, name);//给SQL语句中第一个?设置值为admin
            pstmt.setString(2, password);

            rs = pstmt.executeQuery();     
            
            if (rs.next()) {
                System.out.println("登录成功!");
            } else {
                System.out.println("用户名或密码错误!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, stmt, rs);
        }
    }
}

2、DAO实现

Data Access Object,将数据库的访问抽象在一起,叫DAO层

在这里插入图片描述

DAO其实就是定义了数据库操作的接口,提供了项目的灵活性!

采用DAO的设计思想对t_employees表进行操作!

2.1 DAO的接口

1、数据封装

对操作表的数据进行封装,当前类中字段和数据库中表的字段一一对应


public class Employee {
    private Integer employeeId;
    private String firstName;
    private String lastName;
    private String email;
    private String phoneNumber;
    private Date hireDate;
    private String jobId;
    private Integer salary;
    private double comm;
    private String managerId;
    private String departmentId;


    public Integer getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

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

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public Date getHireDate() {
        return hireDate;
    }

    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }

    public String getJobId() {
        return jobId;
    }

    public void setJobId(String jobId) {
        this.jobId = jobId;
    }

    public Integer getSalary() {
        return salary;
    }

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

    public double getComm() {
        return comm;
    }

    public void setComm(double comm) {
        this.comm = comm;
    }

    public String getManagerId() {
        return managerId;
    }

    public void setManagerId(String managerId) {
        this.managerId = managerId;
    }

    public String getDepartmentId() {
        return departmentId;
    }

    public void setDepartmentId(String departmentId) {
        this.departmentId = departmentId;
    }
}

2、定义数据操作接口


public interface EmployeeDAO {
    /**
     * 增加雇员对象
     * @param employee 需要添加的具体雇员信息
     * @return 增加成功后的雇员信息
     */
    Employee insert(Employee employee);

    /**
     * 修改雇员信息
     * @param employee 需要修改的雇员对象
     * @return 修改成功后的雇员对象
     */
    Employee update(Employee employee);

    /**
     * 根据雇员Id删除雇员数据
     * @param employeeId 雇员Id
     * @return 删除成功返回true,否则返回false
     */
    boolean delete(int employeeId);

    /**
     * 根据雇员Id查询雇员对象
     * @param employeeId 雇员Id
     * @return 返回具体雇员对象
     */
    Employee findById(int employeeId);

    /**
     * 返回所有雇员信息
     * @return
     */
    List<Employee> list();
}

在这里插入图片描述

2.2 接口的具体实现
2.2.1 实现删除方法
  @Override
    public boolean delete(int employeeId) {
        Connection conn = null;
        PreparedStatement pstmt = null;

        String sql = "delete from t_employees where employee_id=?";
        try {
            conn = JdbcUtils.getConnection();
            pstmt = conn.prepareStatement(sql);

            //设置占位符对应的值
            pstmt.setInt(1, employeeId);

            //执行
            return pstmt.executeUpdate() > 0;

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, pstmt, null);
        }

        return false;
    }
2.2.2 修改方法
 @Override
    public Employee update(Employee employee) {

        Connection conn = null;
        PreparedStatement pstmt = null;

        String sql = "update t_employees set FIRST_NAME=?,LAST_NAME=?,EMAIL=?,PHONE_NUMBER=?,HIRE_DATE=?,JOB_ID=?,SALARY=?,COMM=?,MANAGER_ID=?,DEPARTMENT_ID=? where EMPLOYEE_ID=?";
        try {
            conn = JdbcUtils.getConnection();
            pstmt = conn.prepareStatement(sql);

            //设置占位符对应的值
            pstmt.setString(1, employee.getFirstName());
            pstmt.setString(2, employee.getLastName());
            pstmt.setString(3, employee.getEmail());
            pstmt.setString(4, employee.getPhoneNumber());
            pstmt.setDate(5, employee.getHireDae());
            pstmt.setString(6, employee.getJobId());
            pstmt.setInt(7, employee.getSalary());
            pstmt.setDouble(8, employee.getComm());
            pstmt.setString(9, employee.getManagerId());
            pstmt.setString(10, employee.getDepartmentId());
            pstmt.setInt(11, employee.getEmployeeId());


            //执行
            if (pstmt.executeUpdate() > 0) {
                return employee;
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, pstmt, null);
        }

        return null;
    }
2.2.3 查询
 @Override
    public Employee findById(int employeeId) {
        Connection conn = null;
        PreparedStatement pstmt = null;

        String sql = "select * from t_employees where employee_id=?";
        try {
            conn = JdbcUtils.getConnection();
            pstmt = conn.prepareStatement(sql);

            //设置占位符对应的值
            pstmt.setInt(1, employeeId);

            //执行
            ResultSet rs = pstmt.executeQuery();
            if(rs.next()){
                //将第一行中每一列的数据拿出来,封装成一个Employee对象
                Employee e = new Employee();
                e.setEmployeeId(rs.getInt(1));
                e.setFirstName(rs.getString(2));
                e.setLastName(rs.getString(3));
                e.setEmail(rs.getString(4));
                e.setPhoneNumber(rs.getString(5));
                e.setHireDate(rs.getDate(6));
                e.setJobId(rs.getString(7));
                e.setSalary(rs.getInt(8));
                e.setComm(rs.getDouble(9));
                e.setManagerId(rs.getString(10));
                e.setDepartmentId(rs.getString(11));
                return e;
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, pstmt, null);
        }

        return null;
    }

    @Override
    public List<Employee> list() {
        Connection conn = null;
        PreparedStatement pstmt = null;

        String sql = "select * from t_employees";
        try {
            conn = JdbcUtils.getConnection();
            pstmt = conn.prepareStatement(sql);

            //执行
            ResultSet rs = pstmt.executeQuery();
            List<Employee> list = new ArrayList<>();
            while(rs.next()){
                //将第一行中每一列的数据拿出来,封装成一个Employee对象
                Employee e = new Employee();
                e.setEmployeeId(rs.getInt(1));
                e.setFirstName(rs.getString(2));
                e.setLastName(rs.getString(3));
                e.setEmail(rs.getString(4));
                e.setPhoneNumber(rs.getString(5));
                e.setHireDate(rs.getDate(6));
                e.setJobId(rs.getString(7));
                e.setSalary(rs.getInt(8));
                e.setComm(rs.getDouble(9));
                e.setManagerId(rs.getString(10));
                e.setDepartmentId(rs.getString(11));
                list.add(e);
            }

            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, pstmt, null);
        }

        return null;
    }
2.2.4 添加

select LAST_INSERT_ID():在同一个连接中获取上一条insert语句自增的id

  @Override
    public Employee insert(Employee employee) {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs =null;
        String sql = "insert into t_employees values (null,?,?,?,?,?,?,?,?,?,?)";
        try {
            conn = JdbcUtils.getConnection();
            pstmt = conn.prepareStatement(sql);

            //设置占位符对应的值
            pstmt.setString(1, employee.getFirstName());
            pstmt.setString(2, employee.getLastName());
            pstmt.setString(3, employee.getEmail());
            pstmt.setString(4, employee.getPhoneNumber());
            pstmt.setDate(5, employee.getHireDate());
            pstmt.setString(6, employee.getJobId());
            pstmt.setInt(7, employee.getSalary());
            pstmt.setDouble(8, employee.getComm());
            pstmt.setString(9, employee.getManagerId());
            pstmt.setString(10, employee.getDepartmentId());

            //执行
            if (pstmt.executeUpdate() > 0) {
                //获取添加之后数据自增的id,并封装到当前employee对象中
                String getGenId = "select last_insert_id()";
                pstmt = conn.prepareStatement(getGenId);

                rs = pstmt.executeQuery();//执行查询语句
                if(rs.next()) {//获取第一行第一列值
                    employee.setEmployeeId(rs.getInt(1));
                }
                return employee;
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeAll(conn, pstmt, rs);
        }

        return null;
    }

作业:实现用户的注册和登录。

1、先提示用户进行注册,注册成功后要求进行登录

2、登录成功后显示上一次登录的时间

3、实现JDBC的过程要求采用DAO规范

3、JDBC连接"池"

3.1 连接池的原理

JDBC的基本步骤

1.创建连接

2.执行SQL语句

3.关闭连接

FileInputStream BufferedInputStream

连接池:存放连接对象的集合

在这里插入图片描述

解决了连接过程中性能的消耗,提高了程序执行的效率

在Java中提供了一个连接池标准:javax.sql.DataSource,各个连接池厂商需要自己去实现该接口,这样就很方便的实现不同连接池之间的切换!

常见连接池:DBCP、C3P0、Druid、HikariCP

3.2 常见的连接池
3.2.1 DBCP连接池对象(了解)

DBCP:Apache推出的,在Commons项目下

使用步骤:

1.添加jar包, commons-dbcp-1.4.jar,commons-pool-1.5.6.jar

2.设置配置文件

  • 文件名:*.properties
  • 位置:任意(src目录下classpath)
  • 内容:不能写中文
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb
username=root
password=123456
#初始连接个数
initialSize=10
#最大活跃个数
maxTotal=10
#最大空闲的个数
maxIdle=5
#最小空闲个数
minIdle=3
#最大等待时间(毫秒)
maxWaitMills=1000

拷贝需要jar包

在这里插入图片描述

设计使用连接池的JdbcUtils工具类

package com.itluma.pool.utils;

import org.apache.commons.dbcp.BasicDataSourceFactory;

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


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

    //声明一个连接池对象
    private static DataSource ds = null;

    //1.加载驱动
    static {
        //
        InputStream is = JdbcUtils.class.getResourceAsStream("/db.properties");

        try {
            PROPERTIES.load(is);
            //构造具体的连接池对象:ds
            ds = BasicDataSourceFactory.createDataSource(PROPERTIES);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //2.建立连接
    public static Connection getConnection() {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;

    }

    //3.实现资源
    public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null)
                rs.close();

            if (stmt != null) {
                stmt.close();
            }

            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
3.2.2 C3P0连接池(了解)

C3P0免费开源的连接池对象!目前使用该连接池的项目有:Hibernate,Spring等这些都在使用该连接池技术。使用第三方工具导入jar包,而且还需要添加一个配置文件c3p0-config.xml

使用步骤:
1.添加jar

c3p0-0.9.5.2.jar

mchange-commons-java-0.2.12.jar

2.编写配置文件

c3p0-config.xml,可以放在src目录中(注意:文件名不能写错了)

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <!-- 配置数据库用户名 -->
        <property name="user">root</property>
        <!-- 配置数据库密码 -->
        <property name="password">123456</property>
        <!-- 配置数据库链接地址 -->
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/companydb</property>
        <!-- 配置数据库驱动 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <!-- 数据库连接池一次性向数据库要多少个连接对象 -->
        <property name="acquireIncrement">20</property>
        <!-- 初始化连接数 -->
        <property name="initialPoolSize">10</property>
        <!-- 最小连接数 -->
        <property name="minPoolSize">5</property>
        <!--连接池中保留的最大连接数。Default: 15 -->
        <property name="maxPoolSize">30</property>
        <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0 -->
        <property name="maxStatements">0</property>
        <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 -->
        <property name="maxStatementsPerConnection">0</property>
        <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default:3 -->
        <property name="numHelperThreads">3</property>
        <!--用户修改系统配置参数执行前最多等待300秒。Default: 300 -->
        <property name="propertyCycle">3</property>
        <!-- 获取连接超时设置 默认是一直等待单位毫秒 -->
        <property name="checkoutTimeout">1000</property>
        <!--每多少秒检查所有连接池中的空闲连接。Default: 0 -->
        <property name="idleConnectionTestPeriod">3</property>
        <!--最大空闲时间,多少秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
        <property name="maxIdleTime">10</property>
        <!--配置连接的生存时间,超过这个时间的连接将由连接池自动断开丢弃掉。当然正在使用的连接不会马上断开,而是等待它close再断开。配置为0的时候则不会对连接的生存时间进行限制。 -->
        <property name="maxIdleTimeExcessConnections">5</property>
        <!--两次连接中间隔时间,单位毫秒。Default: 1000 -->
        <property name="acquireRetryDelay">1000</property>
        <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。Default: null -->
        <property name="automaticTestTable">Test</property>
        <!-- 获取connnection时测试是否有效 -->
        <property name="testConnectionOnCheckin">true</property>
    </default-config>
</c3p0-config>

编写JdbcUtils工具类

package com.itluma.pool.utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class JdbcUtils2 {

    //声明一个连接池对象
    private static DataSource ds = new ComboPooledDataSource();

    //2.建立连接
    public static Connection getConnection() {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    //3.实现资源
    public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null)
                rs.close();

            if (stmt != null) {
                stmt.close();
            }

            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

3.2.3 Druid连接池(掌握使用)

Druid相当于DBCP和C3p0而言性能非常不错。阿里的

使用步骤:

1、添加jar

druid-1.1.5.jar

2、创建配置文件

  • 文件名:*.properties
  • 名称任意,建议存放在src目录下
driverClassName=com.mysql.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:3306/companydb
#初始连接池大小
initialSize=10

#最大连接个数
maxActive=10
#最大等待时间
maxWait=1000

创建JdbcUtils工具类

package com.itluma.pool.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
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 DruidUtils {
    private static final Properties PROPERTIES = new Properties();

    //声明一个连接池对象
    private static DataSource ds = null;

    //1.加载驱动
    static {
        //
        InputStream is = DruidUtils.class.getResourceAsStream("/druid.properties");

        try {
            PROPERTIES.load(is);
            //构造具体的连接池对象:ds
            ds = DruidDataSourceFactory.createDataSource(PROPERTIES);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //2.建立连接
    public static Connection getConnection() {
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;

    }

    //3.实现资源
    public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null)
                rs.close();

            if (stmt != null) {
                stmt.close();
            }

            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4、DbUtils工具类的使用

DBUtils:简化JDBC代码开发的一个工具类,是apache旗下commons项目中的

DbUtils就是JDBC的一个简化开发工具包。需要使用到技术:连接池(获取连接)、SQL语句

4.1 JavaBean组件

JavaBean其实就是一个类,在开发中经常会使用一些数据封装。具有如下特性:

1、需要实现一个接口:java.io.Serializable,通常省略

2、提供私有字段:private 类型 字段名

3、给私有字段提供getter、setter

4、提供无参的构造方法

public class Employee {
    private Integer employeeId;//字段(field),成员变量
    private String firstName;
    private String lastName;
    private String email;
    private String phoneNumber;
    private Date hireDate;
    private String jobId;
    private Integer salary;
    private Double comm;
    private String managerId;
    private String departmentId;

	//属性(property) employeeId
    public Integer getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

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

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public Date getHireDate() {
        return hireDate;
    }

    public void setHireDate(Date hireDate) {
        this.hireDate = hireDate;
    }

    public String getJobId() {
        return jobId;
    }

    public void setJobId(String jobId) {
        this.jobId = jobId;
    }

    public Integer getSalary() {
        return salary;
    }

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

    public Double getComm() {
        return comm;
    }

    public void setComm(Double comm) {
        this.comm = comm;
    }

    public String getManagerId() {
        return managerId;
    }

    public void setManagerId(String managerId) {
        this.managerId = managerId;
    }

    public String getDepartmentId() {
        return departmentId;
    }

    public void setDepartmentId(String departmentId) {
        this.departmentId = departmentId;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "employeeId=" + employeeId +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                ", hireDate=" + hireDate +
                ", jobId='" + jobId + '\'' +
                ", salary=" + salary +
                ", comm=" + comm +
                ", managerId='" + managerId + '\'' +
                ", departmentId='" + departmentId + '\'' +
                '}';
    }
}

一般字段都设计成封装类型,包括基础类型

int a = (Integer)null;//出现NullPointerException

Integer a = (Integer)null;//没错

4.2 DbUtils完成CRUD

DbUtils中封装了数据库操作的相关方法,小巧灵活!

主要有三个核心功能:

  • QueryRunner提供大量关于SQL操作的API
  • ResultSetHanlder接口,用来对select操作之后的数据进行封装。
  • DbUtils类,提供关于资源的建立和关系以及事务相关操作的方法

QueryRunner核心类

  • QueryRunner(DataSource ds):提供数据源,DbUtils底层自动维护连接对象
  • update(String sql , Object … params):执行更新操作,insert,update,delete
  • query(String sql , ResultSetHanlder rsh, Object… params):执行查询select

ResultSetHandler结果集的处理类

类名说明
BeanHandler将一条记录转换成javabean对象
BeanListHandler将结果集中的每条记录转换成javabean对象,并且存入到List集合中
ScalarHandler

使用的基本步骤:

1、导入jar包:commons-dbutils-1.4.jar

2、创建连接池对象

3、DbUtils工具类的具体使用了

4.2.1 实现添加操作

在项目中创建lib目录,并将对应jar拷贝进去
在这里插入图片描述

需要将这个三个jar,添加到library中

在这里插入图片描述

提供连接池的配置文件:src/durid.properties文件

driverClassName=com.mysql.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:3306/companydb
#初始连接池大小
initialSize=10

#最大连接个数
maxActive=10
#最大等待时间
maxWait=1000

提供一个连接池的工具类

package com.itluma.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
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 DruidUtils {
    private static final Properties PROPERTIES = new Properties();
    private static DataSource dataSource;

    static {
        InputStream is = DruidUtils.class.getResourceAsStream("/druid.properties");
        try {
            PROPERTIES.load(is);
            dataSource = DruidDataSourceFactory.createDataSource(PROPERTIES);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static DataSource getDataSource() {
        return dataSource;
    }

    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    //3.实现资源
    public static void closeAll(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null)
                rs.close();

            if (stmt != null) {
                stmt.close();
            }

            if (conn != null) {
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

添加操作

package com.itluma.dao.impl;

import com.itluma.dao.EmployeeDAO;
import com.itluma.domain.Employee;
import com.itluma.utils.DruidUtils;
import org.apache.commons.dbutils.QueryRunner;

import java.sql.SQLException;


public class EmployeeDAOImpl implements EmployeeDAO {
    @Override
    public Employee insert(Employee employee) {

        try {
            //创建QuryRunner对象,主要用来执行SQL操作
            QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());

            //执行SQL操作
            String sql = "insert into t_employees values (null,?,?,?,?,?,?,?,?,?,?)";
            //参数
            Object[] params = {employee.getFirstName(), employee.getLastName(), employee.getEmail(), employee.getPhoneNumber(), employee.getHireDate(), employee.getJobId(), employee.getSalary(), employee.getComm(), employee.getManagerId(), employee.getDepartmentId()};
            int rows = qr.update(sql, params);
            if (rows > 0) {
                //获取当前行的主键自增数据
                //TODO 后续补充
                //select last_insert_id();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }
}
4.2.2 实现修改操作
@Override
    public void update(Employee employee) {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        String sql = "update t_employees set first_name=?,last_name=? where employee_id=?";
        Object[] obj = {employee.getFirstName(), employee.getLastName(), employee.getEmployeeId()};
        try {
            //执行
            queryRunner.update(sql, obj);
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }
4.2.3 实现删除操作
  @Override
    public void delete(Integer employeeId) {
        String sql = "delete from t_employees where employee_id=?";
        try {
            //执行
            int rows = queryRunner.update(sql, employeeId);
            System.out.println(rows);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
4.2.4 实现查询(难点)

1、查询单个对象

 @Override
    public Employee findById(Integer employeeId) {
        String sql = "select * from t_employees where employee_id=?";
        try {
            Employee e = queryRunner.query(sql, new MyResultSetHandler(), employeeId);
            return e;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

创建了一个类实现ResultSetHandler接口:主要作用其实就是用来将ResultSet转成我们需要的具体对象

class MyResultSetHandler implements ResultSetHandler<Employee> {

    @Override
    public Employee handle(ResultSet rs) throws SQLException {
        if (rs.next()) {
            //将ResultSet对象转换成我们需要的具体对象
            Employee e = new Employee();
            e.setEmployeeId(rs.getInt(1));
            e.setFirstName(rs.getString(2));
            e.setLastName(rs.getString(3));
            e.setEmail(rs.getString(4));
            e.setPhoneNumber(rs.getString(5));
            e.setHireDate(rs.getDate(6));
            e.setJobId(rs.getString(7));
            e.setSalary(rs.getInt(8));
            e.setComm(rs.getDouble(9));
            e.setManagerId(rs.getString(10));
            e.setDepartmentId(rs.getString(11));
            return e;
        }
        return null;
    }
}

其实在DbUtils中已经提供了一个BeanHandler类,可以之间将ResultSet转成我们需要类型,这样就不要自己去定类

BeanHanlder<类型>(类型字节码)

@Override
    public Employee findById(Integer employeeId) {
        String sql = "select * from t_employees where employee_id=?";
        try {

            //Employee e = queryRunner.query(sql, new MyResultSetHandler(), employeeId);
            Employee e = queryRunner.query(sql, new BeanHandler<Employee>(Employee.class), employeeId);
            return e;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

在这里插入图片描述

但是存在部分数据丢失:实体字段名和数据库中的对应的字段名不一致的丢失了值

employe_id employeId

first_name firstName;

//定义好实体字段和数据库中字段之间的映射关系
Map<String,String> map = new HashMap<>();
map.put("EMPLOYEE_ID", "employeeId");
map.put("FIRST_NAME", "firstName");
map.put("LAST_NAME","lastName");

Employee e = queryRunner.query(sql, new BeanHandler<Employee>(Employee.class, new BasicRowProcessor(new BeanProcessor(map))), employeeId);

在构造BeanHandler对象是,提供第二个参数new BasicRowProcessor(new BeanProcessor(map)),建立数据库中表字段名和封装成对象的实体字段关系

2、查询多个对象

@Override
    public List<Employee> findAll() {
        String sql = "select * from t_employees";
        try {
            List<Employee> list = queryRunner.query(sql, new BeanListHandler<Employee>(Employee.class));
            return list;
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

3、查询记录个数

 @Override
    public Long getTotal() {
        //该SQL语句只会返回一个值
        String sql = "select count(*) from t_employees";
        try {
            //ScalarHandler:获取结果集中第一行第一列的值
            Long res = queryRunner.query(sql, new ScalarHandler<Long>());
            return res;
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }

今天的作业:

将昨天的作业用DbUtils进行重构

5、三层架构

DAO:数据访问层,实现数据库的CRUD操作

Service:实现业务的过程,用户登录,用户注册,

View:显示层,将业务处理完的数据如何展示

"高内聚、低耦合"

分三层:在项目开发中应用分层思想解决问题

吃饭-》【服务员-》厨师-》采购】分工

玩游戏-》【打开应用界面-》业务处理-》操作数据库】分层

显示层:UI(View),数据展示,界面

业务层:Service(Business),数据的业务处理

数据层:DAO(Data Access Object),操作数据库

在这里插入图片描述

User Interface

Business Logic Layer

Data Acess Layer

//创建java项目包的基本规范
com.javac.myshop.cart.view   //存放显示层的代码
com.javac.myshop.cart.service //存放业务的接口代码
com.javac.myshop.cart.service.impl//存放业务的接口实现
com.javac.myshop.cart.dao    //存放数据访问层的接口
com.javac.myshop.cart.dao.impl  //存放数据访问层的接口实现   
com.javac.myshop.cart.domain  //存放数据的封装pojo/vo/bo/domain/entity/dto 
com.javac.myshop.cart.utils

6、JDBC事务的支持

事务:从逻辑上来说是一个整体,各个操作单元都成功整个业务才算成功,否则算失败!

6.1 MySQL中事务
sql语句说明
start transaction开启事务
rollback回滚事务
commit提交事务

转账业务如果应用事务来解决的话

try{
    开启事务
    update account set balance=balance-500 where name="张三";
    update account set balance=balance+500 where name="李四";
    提交事务
}catch(Exception e){
    回滚事务
}
6.2 JDBC中事务的支持

JDBC中Connection对象提供如下三个方法:

  • setAutoCommit(boolean flag):false表示取消自动提交
  • commit():提交SQL语句
  • rollback():撤销SQL语句的执行
Connection conn=null;
try{
    conn = DruidUtils.getConnection();
    conn.setAutoCommit(false);
    QueryRunner qr = new QueryRuner();
   
    String sql1 = "update account set balance=balance-500 where name='张三'";
    qr.update(conn, sql1);
    
    String sql2 = "update account set balance=balance+500 where name='李四'";
    qr.update(conn, sql2);
    conn.commit();
}catch(Exception e){
    conn.rollback();
}

注意:多个SQL语句在一个事务中执行,必须在同一个Connection对象中!

6.3 采用三层结构模拟转账过程

数据访问层

业务处理层

显示层

6.3.1 数据层的实现

1.实现数据层的SQL操作过程

package com.itluma.account.dao;

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


public interface AccountDAO {
    /**
     * 扣款
     * @param conn
     * @param fromName 来源账号
     * @param money 金额
     */
    void fromAccount(Connection conn, String fromName, double money) throws SQLException;

    /**
     * 加钱
     * @param conn
     * @param toName 接收钱的账号
     * @param money 金额
     */
    void toAccount(Connection conn, String toName, double money) throws SQLException;
}

接口实现类,有异常使劲抛

package com.itluma.account.dao.impl;

import com.itluma.account.dao.AccountDAO;
import org.apache.commons.dbutils.QueryRunner;

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


public class AccountDAOImpl implements AccountDAO {

    private QueryRunner qr = new QueryRunner();

    @Override
    public void fromAccount(Connection conn, String fromName, double money) throws SQLException {
        qr.update(conn, "update account set balance=balance-? where name=?", money, fromName);
    }

    @Override
    public void toAccount(Connection conn, String toName, double money) throws SQLException {
        qr.update(conn, "update account set balance=balance+? where name=?", money, toName);
    }
}
6.3.2 业务层的实现

实现具体的转账过程:张三要赚钱给李四了

抽象接口

package com.itluma.account.service;


public interface AccountService {
    /**
     * 具体转账过程
     * @param fromName 来源的账号
     * @param toName 接收的账号
     * @param money 金额
     */
    void transfer(String fromName, String toName, double money);
}

实现接口

package com.itluma.account.service.impl;

import com.itluma.account.dao.AccountDAO;
import com.itluma.account.dao.impl.AccountDAOImpl;
import com.itluma.account.service.AccountService;
import com.itluma.account.utils.DruidUtils;

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


public class AccountServiceImpl implements AccountService {
    private AccountDAO accountDAO = new AccountDAOImpl();

    @Override
    public void transfer(String fromName, String toName, double money) {
        Connection conn = null;

        try {
            //开启事务
            conn = DruidUtils.getConnection();
            conn.setAutoCommit(false);
            //1.扣钱
            accountDAO.fromAccount(conn, fromName, money);

            //2.加钱
            accountDAO.toAccount(conn, toName, money);

            //提交
            conn.commit();
        } catch (Exception e) {
            //回滚
            try {
                if (conn != null){
                    conn.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }
    }
}
6.3.3 显示层的模拟
package com.itluma.account.view;

import com.itluma.account.service.AccountService;
import com.itluma.account.service.impl.AccountServiceImpl;


public class AcountTest {
    public static void main(String[] args) {
        //实现转账过程
        AccountService accountService = new AccountServiceImpl();
        accountService.transfer("张三","李四", 500);
    }
}

瑕疵:在业务中需要去获取连接对象,因为需要控制事务的开关

解决的方案是提供一个单独的类来处理事务的开关

6.3.4 优化业务的事务管理

设置一个事务的管理类

package com.itluma.account.utils;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;


public class ConnectionManager {
    //保存连接对象
    private static Map<String, Connection> map = new HashMap<>();

    //获取集合中获取一个名词叫key的连接对象
    public static Connection getConnection() {
        Connection conn = map.get("key");
        if (conn == null) {
            conn = DruidUtils.getConnection();
            map.put("key", conn);
        }
        return conn;
    }

    public static void startTransaction() throws SQLException {
        Connection conn = getConnection();
        conn.setAutoCommit(false);
    }

    public static void commit() throws SQLException {
        Connection conn = getConnection();
        conn.commit();;
    }

    public static void rollback() throws SQLException {
        Connection conn = getConnection();
        conn.rollback();
    }
}

在业务中直接调用事务管理类,来开关事务!

public class AccountServiceImpl implements AccountService {
    private AccountDAO accountDAO = new AccountDAOImpl();

    @Override
    public void transfer(String fromName, String toName, double money) {


        try {
            //开启事务
            ConnectionManager.startTransaction();
            //1.扣钱
            accountDAO.fromAccount(fromName, money);

            int i = 1/0;

            //2.加钱
            accountDAO.toAccount(toName, money);

            //提交
            ConnectionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //回滚
            try {
                ConnectionManager.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        }
    }
}

在DAO中也对连接对象的获取做了修改

public class AccountDAOImpl implements AccountDAO {

    private QueryRunner qr = new QueryRunner();

    @Override
    public void fromAccount(String fromName, double money) throws SQLException {
        qr.update(ConnectionManager.getConnection(), "update account set balance=balance-? where name=?", money, fromName);
    }

    @Override
    public void toAccount(String toName, double money) throws SQLException {
        qr.update(ConnectionManager.getConnection(), "update account set balance=balance+? where name=?", money, toName);
    }
}

多线程存在并发问题:多个线程同时转账时会存在,几个线程共用同一个Connection对象,这是非常不合理的,解决的方案是使用ThreadLocal实现Connection对象在不同线程之间隔离。

从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

package com.itluma.account.utils;

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


public class ConnectionManager {
    //保存连接对象
    //private static Map<String, Connection> map = new HashMap<>();
    private static ThreadLocal<Connection> tl = new ThreadLocal<>();

    //获取集合中获取一个名词叫key的连接对象
    public static Connection getConnection() {
        //从ThreadLocal中获取连接对象
        Connection conn = tl.get();
        if(conn == null){
            conn = DruidUtils.getConnection();
            //向ThreadLocal中存储Connection对象
            tl.set(conn);
        }
        return conn;
    }

    public static void startTransaction() throws SQLException {
        Connection conn = getConnection();
        conn.setAutoCommit(false);
    }

    public static void commit() throws SQLException {
        Connection conn = getConnection();
        conn.commit();
        tl.remove();
    }

    public static void rollback() throws SQLException {
        Connection conn = getConnection();
        conn.rollback();
        tl.remove();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值