JDBC学习笔记

JDBC


第1章 原生JDBC


1.1 JDBC概述


JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API。JDBC是Java访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口和类组成。
JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

JDBC规范(掌握四个核心对象):

  • DriverManager:用于注册驱动
  • Connection: 表示与数据库创建的连接
  • Statement: 操作数据库sql语句的对象
  • ResultSet: 结果集或一张虚拟表

1.2 JDBC原理


Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。

JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。

1.3 JDBC入门案例


准备数据

之前我们学习了sql语句的使用,并创建的分类表category,今天我们将使用JDBC对分类表进行增删改查操作。

#创建数据库
create database day04;
#使用数据库
use day04;
#创建分类表
create table category(
cid int PRIMARY KEYAUTO_INCREMENT ,
cname varchar(100)
);
#初始化数据
insert into category (cname) values('家电');
insert into category (cname) values('服饰');
insert into category (cname) values('化妆品');

导入驱动jar包(IDEA)

创建lib目录,存放mysql的驱动mysql-connector-java-5.1.37-bin.jar
选中mysql的jar包,右键选择“ Add as Library…” 完成jar导入

在java项目中,只需要引入mysql-connector-java-x.x.x-bin.jar就可以运行java项目。在web项目中,当Class.forName(“om.mysql.jdbc.Driver”);时eclipse是不会去查找字符串,不会去查找驱动的。所以只需要把mysql-connector-java-x.x.x-bin.jar拷贝到tomcat下lib目录就可以了。或者,放在WEB-INF目录下lib中

开发步骤

  1. 注册驱动.
  2. 获得连接.
  3. 获得执行sql语句的对象
  4. 执行sql语句,并返回结果
  5. 处理结果
  6. 释放资源.

案例实现

//查询所有的分类信息
@Test
public void demo1() throws Exception{
	// 注意:使用JDBC规范,采用都是 java.sql包下的内容
	//1 注册驱动
	Class.forName("com.mysql.jdbc.Driver");
	//2 获得连接
	String url = "jdbc:mysql://localhost:3306/mydb";
	Connection conn = DriverManager.getConnection(url, "root", "root");
	//3获得执行sql语句的对象
	Statement stmt = conn.createStatement();
	//4执行SQL语句
	ResultSet rs = stmt.executeQuery("select * from category");
	//5处理结果集
	while(rs.next()){
		// 获得一行数据
		Integer cid = rs.getInt("cid");
		String cname = rs.getString("cname");
		System.out.println(cid + " , " + cname);
	}
	//6释放资源
	rs.close();
	stmt.close();
	conn.close();
}

1.4 API详解


API详解:注册驱动

DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 不建议使用,原因有2个:
(1)导致驱动被注册2次
(2)强烈依赖数据库的驱动jar

  • 解决办法:
    Class.forName("com.mysql.jdbc.Driver");
  • 注意
    注册JDBC驱动6.0后使用Class.forName("com.mysql.cj.jdbc.Driver");
    注册JDBC驱动5.0后使用Class.forName("com.mysql.jdbc.Driver");
    两个版本的区别:首先驱动换了,不是com.mysql.jdbc.Driver而是’com.mysql.cj.jdbc.Driver’ ; 再时连接数据库地址有变化mysql8.0是不需要建立ssl连接的,你需要显示关闭,即url中的&useSSL=false;

API详解:获得链接

static Connection getConnection(String url, String user, String password) :试图建立到给定数据库 URL的连接。

  • 参数说明:
    • url 需要连接数据库的位置(网址)
    • user用户名
    • password 密码
  • 例如: getConnection("jdbc:mysql://localhost:3306/day04", "root", "root");
  • url数据库地址
    String url="jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Hongkong&characterEncoding=utf-8&autoReconnect=true";
    连接地址+ssl连接关闭+时区为Hongkong+字符集为utf-8+数据库自动连接

扩展:
URL:SUN公司与数据库厂商之间的一种协议。
jdbc:mysql://localhost:3306/day04
协议子协议 IP :端口号数据库 mysql: jdbc:mysql://localhost:3306/day04 或者 jdbc:mysql:///day04(默认
本机连接) oracle数据库: jdbc:oracle:thin:@localhost:1521:sid

API详解:java.sql.Connection接口:一个连接

接口的实现在数据库驱动中。所有与数据库交互都是基于连接对象的。

  • Statement createStatement(); 创建操作sql语句的对象

API详解:java.sql.Statement接口: 操作sql语句,并返回相应结果

String sql = "某SQL语句";
获取Statement语句执行平台:Statement stmt =con.createStatement();

常用方法:

  • int executeUpdate(String sql); --执行insert update delete语句.
  • ResultSet executeQuery(String sql); --执行select语句.
  • boolean execute(String sql); --仅当执行select并且有结果时才返回true,执行其他的语句返回false.

API详解:处理结果集(注:执行insert、update、delete无需处理)

ResultSet实际上就是一张二维的表格,我们可以调用其boolean next() 方法指向某行记录,当第一次调用next() 方法时,便指向第一行记录的位置,这时就可以使用ResultSet提供的 getXXX(int col) 方法来获取指定列的数据:(与数组索引从0开始不同,这里索引从1开始)

rs.next();//指向第一行
rs.getInt(1);//获取第一行第一列的数据

常用方法:

  • Object getObject(int index) / Object getObject(String name) 获得任意对象
  • String getString(int index) / String getString(String name) 获得字符串
  • int getInt(int index) / int getInt(String name) 获得整形
  • double getDouble(int index) / double getDouble(String name) 获得双精度浮点型

API详解:释放资源

与IO流一样,使用后的东西都需要关闭!关闭的顺序是先得到的后关闭,后得到的先关闭。

rs.close();
stmt.close();
con.close();

1.5 JDBC工具类


获得数据库连接操作,将在以后的增删改查所有功能中都存在,可以封装工具类JDBCUtils。提供获取连接对象的方法,从而达到代码的重复利用。
该工具类提供方法:public static Connection getConnection() 。代码如下:

public class JdbcUtils {
    private static String driver = "com.mysql.jdbc.Driver";
    private static String url = "jdbc:mysql://localhost:3306/webdb_4";
    private static String user = "root";
    private static String password = "root";
    static{
        try {
            //注册驱动
            Class.forName(driver);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
    * 获得连接
    * @return
    * @throws SQLException
    */
    public static Connection getConnection() throws SQLException{
    //获得连接
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }
    /**
    * 释放资源
    * @param conn
    * @param st
    * @param rs
    */
    public static void closeResource(Connection conn , Statement st , ResultSet rs){
    	if(rs != null){
   			 try {
    			rs.close();
   			 } catch (SQLException e) {
   			 }
    	}
        if(st != null){
            try {
                st.close();
            } catch (SQLException e) {
            }
        }
        if(conn != null){
            try {
            	conn.close();
            } catch (SQLException e) {
            }
        }
    }
}

1.6 JDBC增删改查操作


插入

@Test
public void demo01(){
//添加
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        //1 获得连接
        conn = JdbcUtils.getConnection();
        //操作
        //1) 获得语句执行者
        st = conn.createStatement();
        //2) 执行sql语句
        int r = st.executeUpdate("insert into category(cname) values('测试')");
        //3) 处理结果
        System.out.println(r);
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
        //释放资源
        JdbcUtils.closeResource(conn, st, rs);
    }
}

修改

@Test
public void demo02(){
        //修改
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
        conn = JdbcUtils.getConnection();
        st = conn.createStatement();
        int r = st.executeUpdate("update category set cname='测试2' where cid = 4");
        System.out.println(r);
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
    	JdbcUtils.closeResource(conn, st, rs);
    }
}

删除

@Test
public void demo03(){
    //删除
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        //操作
        st = conn.createStatement();
        int r = st.executeUpdate("delete from category where cid = 4");
        System.out.println(r);
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
    	JdbcUtils.closeResource(conn, st, rs);
    }
}

通过id查询详情

@Test
public void demo04(){
    //通过id查询详情
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        //操作
        st = conn.createStatement();
        rs = st.executeQuery("select * from category where cid = 30");
        if(rs.next()){
            String cid = rs.getString("cid");
            String cname = rs.getString("cname");
            System.out.println(cid + " @ " + cname );
        } else {
        	System.out.println("没有数据");
        }
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
    	JdbcUtils.closeResource(conn, st, rs);
    }
}

查询所有

@Test
public void demo05(){
    //查询所有
    Connection conn = null;
    Statement st = null;
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        //操作
        st = conn.createStatement();
        rs = st.executeQuery("select * from category");
        while(rs.next()){
            String cid = rs.getString("cid");
            String cname = rs.getString("cname");
            System.out.println(cid + " @ " + cname );
        }
    } catch (Exception e) {
    	throw new RuntimeException(e);
    }finally{
    	JdbcUtils.closeResource(conn, st, rs);
    }
}

第2章 PreparedStatement


2.1 SQL注入问题


SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。 假设有登录案例SQL语句
如下:

SELECT * FROM 用户表 WHERE NAME = 用户输入的用户名 AND PASSWORD = 用户输的密码;

此时,当用户输入正确的账号与密码后,查询到了信息则让用户登录。但是当用户输入的账号为XXX 密码为:XXX’ OR ‘a’=’a 时,则真正执行的代码变为:

SELECT * FROM 用户表 WHERE NAME = ‘XXX’ AND PASSWORD =’ XXX’ OR ’a’=’a’;

此时,上述查询语句时永远可以查询出结果的。那么用户就直接登录成功了,显然我们不希望看到这样的结果,这
便是SQL注入问题。 为此,我们使用PreparedStatement来解决对应的问题。

2.2 API详解:预处理对象


preparedStatement:预编译对象,是Statement对象的子类。
特点:

  • 性能高
  • 会把sql语句先编译
  • 能过滤掉用户输入的关键字。
    PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。
String sql = "select * from user where username = ?

PreparedStatement使用,需要通过以下3步骤完成:

  1. PreparedStatement预处理对象代码:
// 获得预处理对象,需要提供已经使用占位符处理后的SQL语句
PreparedStatement psmt = conn.prepareStatement(sql)
  1. 设置实际参数
void setXxx(int index, Xxx xx) 将指定参数设置指定类型的值
参数1index 实际参数序列号,从1开始。
参数2:xxx 实际参数值,xxx表示具体的类型。
例如:
setString(2, "1234")SQL语句中第2个位置的占位符?替换成实际参数 "1234"
  1. 执行SQL语句:
int executeUpdate(); --执行insert update delete语句.
``ResultSet executeQuery(); --执行select语句.
``boolean execute(); --执行select返回true 执行其他的语句返回false.

2.3 插入


@Test
public void demo01(){
    //添加:向分类表中添加数据
    Connection conn = null;
    PreparedStatement psmt = null;
    ResultSet rs = null;
    try {
        //1 获得连接
        conn = JdbcUtils.getConnection();
        //2 处理sql语句
        String sql = "insert into category(cname) values(? )";
        //3获得预处理对象
        psmt = conn.prepareStatement(sql);
        //4设置实际参数
        psmt.setString(1,"预处理");
        //5执行
        int r = psmt.executeUpdate();
        System.out.println(r);
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
        //6释放资源
        JdbcUtils.closeResource(conn, psmt, rs);
    }
}

2.4 更新


@Test
public void demo02(){
    //修改
    Connection conn = null;
    PreparedStatement psmt = null;
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        //1 sql语句
        String sql = "update category set cname = ? where cid = ?";
        //2 获得预处理对象
        psmt = conn.prepareStatement(sql);
        //3设置实际参数
        psmt.setString(1, "测试数据");
        psmt.setInt(2, 4);
        //4执行
        int r = psmt.executeUpdate();
        System.out.println(r);
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
    	JdbcUtils.closeResource(conn, psmt, rs);
    }
}

2.5 通过id查询详情


@Test
public void demo05(){
    //通过id查询
    Connection conn = null;
    PreparedStatement psmt = null;
    ResultSet rs = null;
    try {
        conn = JdbcUtils.getConnection();
        String sql = "select * from category where cid = ?";
        psmt = conn.prepareStatement(sql);
        psmt.setInt(1, 2);
        rs = psmt.executeQuery();
        if(rs.next()){
        	System.out.println("查询到");
        } else {
        	System.out.println("查询不到");
        }
    } catch (Exception e) {
    	throw new RuntimeException(e);
    } finally{
    	JdbcUtils.closeResource(conn, psmt, rs);
    }
}

第3章 使用连接池重写工具类


3.1 连接池原理


连接池理解为存放多个连接的集合。
在这里插入图片描述

使用连接池技术的目的:解决建立数据库连接耗费资源和时间很多的问题,提高性能。

3.2 编写标准的数据源(规范)


Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!

常见的连接池:C3P0、DRUID。

3.3 C3P0连接池


C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用C3P0连接池需要导入jar包,c3p0使用时还需要添加配置文件“c3p0-config.xml”

使用步骤

  1. 添加jar包
  2. 编写配置文件 c3p0-config.xml,放在src中(注:文件名一定不要写错)
  3. 编写工具类
  • 编写配置文件 c3p0-config.xml

    <c3p0-config>
        <!-- 使用默认的配置读取连接池对象 -->
        <default-config>
            <!-- 连接参数 -->
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql://localhost:3306/数据库名</property>
            <property name="user">root</property>
            <property name="password">root</property>
            <!-- 连接池参数 -->
            <property name="initialPoolSize">5</property>
            <property name="maxPoolSize">10</property>
            <property name="checkoutTimeout">2000</property>
            <property name="maxIdleTime">1000</property>
        </default-config>
    </c3p0-config>
    

c3p0连接池常用的配置参数:

参数说明
initialPoolSize初始连接数
maxPoolSize最大连接数
checkoutTimeout最大等待时间
maxIdleTime最大空闲回收时间

初始连接数 :刚创建好连接池的时候准备的连接数量

最大连接数 :连接池中最多可以放多少个连接

最大等待时间 :连接池中没有连接时最长等待时间

最大空闲回收时间 :连接池中的空闲连接多久没有使用就会回收

编写C3P0工具类

public class JdbcUtils {
    //创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
    public static DataSource ds = new ComboPooledDataSource();
        //从池中获得一个连接
        public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
    //释放资源
    public static void closeAll(ResultSet rs, Statement stmt, Connection conn){
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
            	throw new RuntimeException(e);
            }
            rs = null;
        }
        if (stmt != null) {
            try {
            	stmt.close();
            } catch (SQLException e) {
            	throw new RuntimeException(e);
            }
        stmt = null;
        }
        if (conn != null) {
            try {
            	conn.close();
            } catch (SQLException e) {
            	throw new RuntimeException(e);
            }
            conn = null;
        }
    }
}

C3P0连接池工具类的使用

public class Demo {
    public static void main(String[] args) throws Exception {
        // 拿到连接
        Connection conn = JdbcUtils.getConnection();
        // 执行sql语句
        String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
        PreparedStatement pstmt = conn.prepareStatement(sql);
        pstmt.setString(1, "李四");
        pstmt.setInt(2, 30);
        pstmt.setDouble(3, 50);
        int i = pstmt.executeUpdate();
        System.out.println("影响的函数: " + i);
        // 关闭资源
        JdbcUtils.closeAll(null,pstmt,conn)}
}

第4章 DBUtils


如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC开发,本案例我们讲采用apache commons组件一个成员:DBUtils。

DBUtils就是JDBC的简化开发工具包。需要项目导入commons-dbutils-1.6.jar才能够正常使用DBUtils工具。

4.1 概述


DBUtils是java编程中的数据库操作实用工具,小巧简单实用。DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。

Dbutils三个核心功能介绍

  • QueryRunner中提供对sql语句操作的API.
  • ResultSetHandler接口,用于定义select操作后,怎样封装结果集.
  • DbUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法

4.2 准备数据


  • 创建表:

    create table product(
    pid int primary key,
    pname varchar(20),
    price double,
    category_id varchar(32)
    );
    
  • 插入表记录

    INSERT INTO product(pid,pname,price,category_id) VALUES(1,'联想',5000,'c001');
    INSERT INTO product(pid,pname,price,category_id) VALUES(2,'海尔',3000,'c001');
    INSERT INTO product(pid,pname,price,category_id) VALUES(3,'雷神',5000,'c001');
    INSERT INTO product(pid,pname,price,category_id) VALUES(4,'JACK JONES',800,'c002');
    INSERT INTO product(pid,pname,price,category_id) VALUES(5,'真维斯',200,'c002');
    INSERT INTO product(pid,pname,price,category_id) VALUES(6,'花花公子',440,'c002');
    INSERT INTO product(pid,pname,price,category_id) VALUES(7,'劲霸',2000,'c002');
    INSERT INTO product(pid,pname,price,category_id) VALUES(8,'香奈儿',800,'c003');
    INSERT INTO product(pid,pname,price,category_id) VALUES(9,'相宜本草',200,'c003');
    INSERT INTO product(pid,pname,price,category_id) VALUES(10,'面霸',5,'c003');
    INSERT INTO product(pid,pname,price,category_id) VALUES(11,'好想你枣',56,'c004');
    INSERT INTO product(pid,pname,price,category_id) VALUES(12,'香飘飘奶茶',1,'c005');
    INSERT INTO product(pid,pname,price,category_id) VALUES(13,'果9',1,NULL);
    

4.3 QueryRunner核心类介绍


提供数据源

  • 构造方法
    QueryRunner(DataSource) 创建核心类,并提供数据源,内部自己维护Connection并自动关闭

  • 普通方法
    update(String sql , Object ... params) 执行DML语句

    query(String sql , ResultSetHandler , Object ... params) 执行DQL语句,并将查询结果封装到对象中。

提供连接

  • 构造方法
    QueryRunner() 创建核心类,没有提供数据源,在进行具体操作时,需要手动提供/关闭Connection

  • 普通方法
    update(Connection conn , String sql , Object ... params) 使用提供的Connection,完成DML语

    query(Connection conn , String sql , ResultSetHandler , Object ... params) 使用提供的Connection,执行DQL语句,并将查询结果封装到对象中。

  • 静态方法

    DbUtils.closeQuietly(conn);关闭Connection资源,不抛异常

4.4QueryRunner实现添加、更新、删除操作


update(String sql, Object... params) 用来完成表数据的增加、删除、更新操作

添加

@Test
public void insert() throws SQLException{
    //获取一个用来执行SQL语句的对象 QueryRunner
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
    String sql = "INSERT INTO product(pid,pname,price,category_id) VALUES(?,?,?,?);";
    Object[] params = {100,"百岁山", 5500, "c005"};
    int line = qr.update(sql,params);// 用来完成表数据的增加、删除、更新操作
    //结果集处理
    System.out.println("line = " + line);
}

更新

@Test
public void update() throws SQLException{
    //1 核心类
    QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
    //2 准备sql语句
    String sql = "update product set pname=?,price=?,category_id=? where pid=?";
    //3 准备实际参数
    Object[] params = {"芒果99","998","c009",13};
    //4 执行
    int r = queryRunner.update(sql, params);
    System.out.println(r);
}

删除

@Test
public void delete() throws SQLException{
    QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
    String sql = "delete from product where pid = ?";
    Object[] params = {99};
    int r = queryRunner.update(sql, params);
    System.out.println(r);
}

4.5QueryRunner实现查询操作


query(String sql, ResultSetHandler rsh, Object... params)用来完成表数据的查询操作

ResultSetHandler 结果集

  • BeanHandler:将结果集中第一条记录封装到一个指定的javaBean中。
  • BeanListHandler:将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
  • ScalarHandler:它是用于单数据。例如select count(*) from 表操作。
  • ColumnListHandler:将结果集中指定的列的字段值,封装到一个List集合中

JavaBean
JavaBean就是一个类,在开发中常用语封装数据。具有如下特性

  1. 需要实现接口:java.io.Serializable ,通常实现接口这步骤省略了,不会影响程序。

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

  3. 提供getter/setter方法:

  4. 提供无参构造

    public class Product {
        private String pid;
        private String pname;
        private Double price;
        private String category_id;
        //省略 getter和setter方法
    }
    
    

BeanHandler

/*
* 查询数据表结果集处理其中一种方式:
* BeanHandler处理方式
* 将数据表的结果集第一行数据,封装成JavaBean类的对象
* 构造方法:
* BeanHandler(Class<T> type)
* 传递一个Class类型对象,将结果封装到哪个类的对象呢
* ZhangWu类的Class对象
*/
@Test
public void demo01() throws SQLException{
    // 通过id查询详情,将查询结果封装到JavaBean product
    //1核心类
    QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
    //2 sql语句
    String sql = "select * from product where pid = ?";
    //3 实际参数
    Object[] params = {6};
    //4 查询并封装
    Product product = queryRunner.query(sql, new BeanHandler<Product>(Product.class), params);
    System.out.println(product);
}

BeanListHandler

/*
* 查询数据表结果集处理其中一种方式:
* BeanListHandler处理方式
* 将数据表的每一行数据,封装成JavaBean类对象
* 多行数据了,多个JavaBean对象,存储List集合
*/
@Test
public void demo02() throws SQLException{
    //查询所有,将每一条记录封装到一个JavaBean,然后将JavaBean添加到List中,最后返回List,
    BeanListHandler
    QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
    String sql = "select * from product";
    Object[] params = {};
    List<Product> list = queryRunner.query(sql, new BeanListHandler<Product>(Product.class),
    params);
    for(Product product : list){
    	System.out.println(product);
    }
}

ScalarHander

/*
* 查询数据表结果集处理其中一种方式:
* ScalarHandler处理方式
* 处理单值查询结果,执行的select语句后,结果集只有1个
*/
@Test
public void demo03() throws SQLException{
    // ScalarHandler : 用于处理聚合函数执行结果(一行一列)
    // * 查询总记录数
    QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
    String sql = "select count(*) from product";
    Long obj = queryRunner.query(sql, new ScalarHandler<Long>());
    //System.out.println(obj.getClass());
    System.out.println(obj);
}

ColumnListHandler

/*
* 查询数据表结果集处理其中一种方式:
* ColumnListHandler处理方式
* 将查询数据表结果集中的某一列数据,存储到List集合
* 哪个列不清楚,数据类型也不清楚, List<Object>
* ColumnListHandler构造方法
* 空参数: 获取就是数据表的第一列
* int参数: 传递列的顺序编号
* String参数: 传递列名
*
* 创建对象,可以加入泛型,但是加入的数据类型,要和查询的列类型一致
*/
@Test
public void demo04() throws SQLException{
    // ColumnListHandler : 查询指定一列数据
    QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
    String sql = "select * from product";
    List<String> list = queryRunner.query(sql, new ColumnListHandler<String>("pname"));
    System.out.println(list);
}

5.6 DBUtils工具小结


DBUtils工具

  • 作用:简化JDBC的操作

DBUtils常用类与方法

  • QueryRunner 用来执行SQL语句对象

    • update(Connection conn, String sql, Object… params) 插入表记录、更新表记录、删除表记录
    • query(Connection conn, String sql, ResultSetHandler handler, Object… params)查询表记录
  • ResultSetHandler 处理结果集的对象

    • BeanHandler:将结果集中第一条记录封装到一个指定的javaBean中。
    • BeanListHandler:将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
    • ScalarHandler:它是用于单数据。例如select count(*) from 表操作。
    • ColumnListHandler:将结果集中指定的列的字段值,封装到一个List集合中

第6章 事务操作


事务概述

  • 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
  • 事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败.

6.1 mysql事务操作


sql语句描述
start transaction开启事务
commit提交事务
rollback回滚事务
  • 准备数据
# 创建一个表:账户表.
create database webdb;
# 使用数据库
use webdb;
# 创建账号表
create table account(
	id int primary key auto_increment,
	name varchar(20),
	money double
);
# 初始化数据
insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);
  • 操作

    • MYSQL中可以有两种方式进行事务的管理:

      • 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
      • 手动提交:先开启,再提交
    • 方式1:手动提交

      start transaction;
      update account set money=money-1000 where name='jack';
      update account set money=money+1000 where name='rose';
      #提交
      commit;
      #或者回滚
      rollback;
      
    • 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制

      show variables like '%commit%';
      # 设置自动提交的参数为OFF:
      set autocommit = 0; -- 0:OFF 1:ON
      

6.2 jdbc事务操作


Connection 对象的方法名描述
conn.setAutoCommit(false/true)开启/关闭事务
conn.commit()提交事务
conn.rollback()回滚事务

注意:在释放Connection 对象前conn.setAutoCommit(true)设置事务自动提交,还原状态

代码演示

//事务模板代码
public void demo01() throws SQLException{
    // 获得连接
    Connection conn = null;
    try {
        //#1 开始事务
        conn.setAutoCommit(false);
        //.... 加钱 ,减钱
        //#2 提交事务
        conn.commit();
    } catch (Exception e) {
        //#3 回滚事务
        conn.rollback();
    } finally{
        conn.setAutoCommit(true);
        // 释放资源
        conn.close();
    }
}

6.3 DBUtils事务操作


必须保证连接为同一个连接,所以如果在业务层获得连接,再将连接传递到持久层,代码具有侵入性。

解决方式:使用ThreadLocal保存Connection对象

Connection对象的方法名描述
conn.setAutoCommit(false)开启事务,设置事务不自动提交
conn.setAutoCommit(false)创建核心类,不设置数据源(手动管理连接)
query(conn , sql , handler, params ) 或
update(conn, sql , params)
手动传递连接, 执行SQL语句CRUD
DbUtils.commitAndCloseQuietly(conn)提交并关闭连接,不抛异常
DbUtils.rollbackAndCloseQuietly(conn)回滚并关闭连接,不抛异常

注意:在释放Connection 对象前conn.setAutoCommit(true)设置事务自动提交,还原状态

代码演示

//事务模板代码
public void demo02() throws SQLException{
    // 获得连接
    Connection conn = null;
    try {
        //#1 开始事务
        conn.setAutoCommit(false);
        //.... 加钱 ,减钱
        //#2 提交事务并关闭连接
    	DbUtils.ommitAndCloseQuietly(conn);
    } catch (Exception e) {
        //#3 回滚事务并关闭连接
        DbUtils.rollbackAndCloseQuietly(conn);
        e.printStackTrace();
    }
}

6.4 案例:JDBC事务分层(dao、service)传递Connection


分析

在这里插入图片描述

分层思想

  • 开发中,常使用分层思想
    在这里插入图片描述

    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  • 不同层级结构彼此平等

  • 分层的目的是:

    • 解耦
    • 可维护性
    • 可扩展性
    • 可重用性
  • 不同层次,使用不同的包表示

    • com.itheima 公司域名倒写
    • com.itheima.dao dao层
    • com.itheima.service service层
    • com.itheima.domain javabean
    • com.itheima.utils 工具

代码实现

  • 工具类C3P0Utils
public class C3P0Utils {
    //创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
    public static DataSource ds = new ComboPooledDataSource();
    //从池中获得一个连接
    public static Connection getConnection() throws SQLException {
    	return ds.getConnection();
    }
}
  • c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
        <default-config>
        <!-- 连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/webdb</property>
        <property name="user">root</property>
        <property name="password">root</property>
        <!-- 连接池参数 -->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">2000</property>
        <property name="maxIdleTime">1000</property>
    </default-config>
</c3p0-config>
  • 入口程序(view)
package com.itheima.view;

import com.itheima.serivce.AccountService;

import java.util.Scanner;

/*
    创建转账案例的web层
        使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
        创建AccountSerivce对象
        调用转账方法,接收转账结果
        对结果进行判断,给用户展示结果
 */
public class Accountview {
    public static void main(String[] args) {
        //使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入付款人姓名:");
        String fromName = sc.next();
        System.out.print("请输入收款人姓名:");
        String toName = sc.next();
        System.out.print("请输入转账金额:");
        double money = sc.nextDouble();

        //创建AccountSerivce对象
        AccountService serivce = new AccountService();
        //调用转账方法,接收转账结果
        boolean b = serivce.transferAccount(fromName, toName, money);
        //对结果进行判断,给用户展示结果
        if (b){
            System.out.println("转账成功!");
        }else{
            System.out.println("转账失败!");
        }
    }
}
  • service层
package com.itheima.serivce;

import com.itheima.dao.AccountDao;
import com.itheima.utils.C3P0Utils;
import org.apache.commons.dbutils.DbUtils;

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

/*
	事务管理方式:向下传递Connection。有侵入性。
    转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给view层
    定义一个转账方法:
        参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
        使用C3P0连接池获取Connection
        开启事务
        创建AccountDao对象
        调用减钱和加钱方法,接收结果
        对结果进行判断
        都执行成功,提交事务
        有异常,回滚事务
        把结果返回给view(web)层
        还原状态
        释放资源
 */
public class AccountService {
    //定义一个转账方法
    public boolean transferAccount(String fromName,String toName,double momey){
        //使用C3P0连接池获取Connection
        Connection conn = C3P0Utils.getConnection();

        //定义返回的结果
        boolean flag = false;

        try {
            //DBUtils进行事务处理的原理,是在Service层获得连接,以保证事务处理过程中的Connection对象为同一个Connection。
            //因为必须保证连接为同一个连接,所以在业务层获得连接,再将连接传递到持久层,代码具有侵入性。
            //开启事务,设置事务不自动提交
            conn.setAutoCommit(false);
            //创建AccountDao对象,调用dao层
            AccountDao dao = new AccountDao();
            //调用持久层
            int row1 = dao.fromAccount(conn, fromName, momey);
            System.out.println(0/0);
            int row2 = dao.toAccount(conn, toName, momey);
            //对结果进行判断
            if(row1>0 && row2>0){
                flag = true;
                //都执行成功,提交事务
                conn.commit();
            }

        } catch (Exception e) {
            e.printStackTrace();
            //有异常,回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

        } finally {
            //还原状态
            conn.setAutoCommit(true);
            //释放资源
            DbUtils.closeQuietly(conn);
        }
        //把结果返回给web层
        return flag;
    }
}
  • dao层
    • 注意:
      一张表–>一个dao
package com.itheima.dao;

import org.apache.commons.dbutils.QueryRunner;

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

/*
    创建转账案例的Dao层:用于对account表进行增删改查
    注意:
        一张表-->一个dao
    定义两个方法:
        一个减钱,一个加钱
 */
public class AccountDao {
    /*
        定义减钱方法
        参数:
            Connection conn:两个方法使用同一个Connection,从而保证使用同一个事务
            String fromName:付款人姓名
            double money:转账金额
        返回值:
            int:影响数据库的有效行数
     */
    public int fromAccount(Connection conn,String fromName,double money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法执行sql语句,接收结果
        int row = qr.update(conn,"update account set money=money-? where name=?;",money,fromName);
        //返回结果
        return row;
    }

    /*
        定义加钱方法
        参数:
            Connection conn:两个方法使用同一个Connection,从而保证使用同一个事务
            String toName:收款人姓名
            double money:转账金额
        返回值:
            int:影响数据库的有效行数
     */
    public int toAccount(Connection conn,String toName,double money) throws SQLException {
        //创建QueryRunner对象
        QueryRunner qr = new QueryRunner();
        //调用update方法执行sql语句,接收结果
        int row = qr.update(conn,"update account set money=money+? where name= ?;",money,toName);
        //返回结果
        return row;
    }
}

第7章 ThreadLocal


7.1 分析


在“事务传递Connection参数案例”中,我们必须传递Connection对象,才可以完成整个事务操作。如果不传递参
数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。
java.lang.ThreadLocal 该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据
在这里插入图片描述

7.2 相关知识:ThreadLocal


java.lang.ThreadLocal 该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据。

举例

public class ThreadLocalDemo {
    public static void main(String[] args) {
        ThreadLocal<String> mainThread = new ThreadLocal<>();
        mainThread.set("Hello Word");
        System.out.println(mainThread.get());//Hello Word
        new Thread(()->{
        System.out.println(mainThread.get());//null
        }).start();
    }
}

结论:向ThreadLocal对象中添加的数据只能在当前线程下使用。

7.3 使用ThreadLocal优化程序


分析

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

代码实现

  • 工具类
public class C3P0Utils {
    //创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
    public static DataSource ds = new ComboPooledDataSource();
    //给当前线程绑定 连接
    private static ThreadLocal<Connection> local = new ThreadLocal<Connection>();
    /**
    * 获得一个连接
    */
    public static Connection getConnection(){
        try {
            //#1从当前线程中, 获得已经绑定的连接
            Connection conn = local.get();
            if(conn == null){
                //#2 第一次获得,绑定内容 – 从连接池获得
                conn = ds.getConnection();
                //#3 将连接存 ThreadLocal
                local.set(conn);
            }
        	return conn; //获得连接
        } catch (Exception e) {
            //将编译时异常 转换 运行时 , 以后开发中运行时异常使用比较多的。
            throw new RuntimeException(e);
            /*
            类与类之间 进行数据交换时,可以使用return返回值。也可以使用自定义异常返回值,调用者try{}catch(e){ e.getMessage() 获得需要的数据}
            此处可以编写自定义异常。
            */
            //throw new MyConnectionException(e);
        }
    }
}
  • service层
public class AccountService {
    /**
    * 事务管理方式:向下传递Connection。有侵入性。使用DBUtils
    * 业务层事务管理转账的方法
    * @param from
    * @param to
    * @param money
    */
    public void transfer(String from, String to, double money) {
        //调用dao层
        AccountDao accountDao = new AccountDao();
        //DBUtils进行事务处理的原理,是在Service层获得连接,以保证事务处理过程中的Connection对象为同一个Connection。
        //因为必须保证连接为同一个连接,所以在业务层获得连接,再将连接传递到持久层,代码具有侵入性。
        //DBUtils使用的方法
        Connection conn = null;
        try {
            //获得连接
            conn = C3P0Utils.getConnection();
            //设置事务不自动提交
            conn.setAutoCommit(false);
            //调用持久层
            accountDao.outMoney(from,money);
            //如果有异常
            //int a = 1 / 0 ;
            accountDao.inMoney(to,money);
            //提交事务,并安静的关闭连接
            DbUtils.commitAndCloseQuietly(conn);
        } catch (SQLException e) {
            //有异常出现时,回滚事务,并安静的关闭连接
            DbUtils.rollbackAndCloseQuietly(conn);
            e.printStackTrace();
        }
    }
}
  • dao层
public class AccountDao {
    /**
    * 付款方法
    * @param from 付款人
    * @param money 金额
    */
    public void outMoney(String from, double money) {
        QueryRunner qr = new QueryRunner();
        try {
            Connection conn = C3P0Utils.getConnection();
            String sql = "update account set money = money - ? where name = ?";
            qr.update(conn, sql, money,from);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
    /**
    * 收款方法
    * @param to 收款人
    * @param money 金额
    */
    public void inMoney(String to, double money) {
        QueryRunner qr = new QueryRunner();
        try {
            Connection conn = C3P0Utils.getConnection();
            String sql = "update account set money = money + ? where name = ?";
            qr.update(conn, sql, money,to);
        } catch (SQLException e) {
        	e.printStackTrace();
        }
    }
}

第8章 事务总结


8.1 事务特性:ACID

  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  • 一致性(Consistency)事务前后数据的完整性必须保持一致。
  • 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

8.2 并发访问问题


如果不考虑隔离性,事务存在3中并发访问问题。

  1. 脏读:一个事务读到了另一个事务未提交的数据.
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

8.3 隔离级别:解决问题


  • 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。

a)存在:3个问题(脏读、不可重复读、虚读)。

b)解决:0个问题

  1. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
    a)存在:2个问题(不可重复读、虚读)。

    b)解决:1个问题(脏读)

  2. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。

    a)存在:1个问题(虚读)。

    b)解决:2个问题(脏读、不可重复读)

  3. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。

    a)存在:0个问题。

    b)解决:3个问题(脏读、不可重复读、虚读)

  • 安全和性能对比
  • 安全性: serializable > repeatable read > read committed > read uncommitted
  • 性能 : serializable < repeatable read < read committed < read uncommitted
  • 常见数据库的默认隔离级别:
  • MySql: repeatable read
  • Oracle: read committed

8.4 演示


  • 模拟脏读

在这里插入图片描述

  • 模拟不可重复读

在这里插入图片描述

  • 模拟虚读幻读

在这里插入图片描述

  • 查询数据库的隔离级别

    show variables like '%isolation%';select @@tx_isolation;
    
  • 设置数据库的隔离级别

    • set session transactionisolation level 级别字符串
    • 级别字符串: readuncommittedread committedrepeatable readserializable
    • 例如: set session transaction isolation level read uncommitted;
  • 配置文件全局修改

    [mysqld]
    # 可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE.
    transaction-isolation = REPEATABLE-READ
    
  • java中修改

    Connection接口的方法

    conn.setTransactionIsolation(int level);
    static int TRANSACTION_NONE 指示不支持事务的常量。
    static int TRANSACTION_READ_UNCOMMITTED 一个常量表示可能会发生脏读,不可重复读和幻读。
    static int TRANSACTION_READ_COMMITTED 一个常数表示防止脏读;可能会发生不可重复的读取和幻像读取。
    static int TRANSACTION_REPEATABLE_READ 一个常量表示防止了脏读和不可重复读;可以发生幻影读取。  
    static int TRANSACTION_SERIALIZABLE 一个常数表示防止脏读不可重复读和幻影读。 
    
  • 读未提交:readuncommitted

    • A窗口设置隔离级别
      • AB同时开始事务
      • A 查询
      • B 更新,但不提交
      • A 再查询?-- 查询到了未提交的数据
      • B 回滚
      • A 再查询?-- 查询到事务开始前数据
  • 读已提交:read committed

    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据改变,存在问题【不可重复读】
  • 可重复读:repeatable read

    • A窗口设置隔离级别
      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?–数据改变,另一个事务
  • 串行化:serializable

    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新?–等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?–等待结束,可以进行操作
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值