JDBC详解

一、JDBC入门

1. jdbc的概念

  • JDBC(Java DataBase Connectivity:java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的。
  • JDBC的作用:可以通过java代码操作数据库

2. jdbc的本质

  • 其实就是java官方提供的一套规范(接口)。用于帮助开发人员快速实现不同关系型数据库的连接

3. jdbc的快速入门程序

  1. 导入jar包

  2. 注册驱动

     Class.forName("com.mysql.jdbc.Driver");
    
  3. 获取连接

    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db2", "root", "root");
    
  4. 获取执行者对象

    Statement stat = conn.createStatement();
    
  5. 执行sql语句,并接收返回结果

    String sql = "SELECT * FROM user";
    ResultSet rs = stat.executeQuery(sql);
    
  6. 处理结果

    while(rs.next()) {
        System.out.println(rs.getInt("id") + "\t" + rs.getString("name"));
    }
    
  7. 释放资源

    rs.close();
    stat.close();
    conn.close();
    
  8. 创建一个java项目:JDBC基础

  9. 将jar包导入,并添加到引用类库

  10. import java.sql.*;
    
    public class Main {
        public static void main(String[] args)  {
    
            Connection conn = null;
            Statement stat = null;
            ResultSet rs=null;
            try {
                Class.forName("com.mysql.cj.jdbc.Driver");
    
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456");
    
                stat = conn.createStatement();
    
                String sql = "SELECT * FROM user";
                rs = stat.executeQuery(sql);
    
                while(rs.next()) {
                    System.out.println(rs.getInt("id") + "\t" + rs.getString("name")+ "\t" + "\t" + rs.getInt("age")
                            + "\t" + rs.getString("status")+ "\t" + rs.getString("gender"));
                }
            }  catch (SQLException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            } finally {
    
                try {
                    if(rs!=null){
                        rs.close();
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
                try {
                    if(stat!=null)
                    {
                        stat.close();
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
                try {
                    if(conn!=null){
                        conn.close();
                    }
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
    
            }
        }
    }
    

1.1、使用配置文件读取连接信息

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

二、JDBC各个功能类详解

1. DriverManager

  • DriverManager:驱动管理对象

    • 注册驱动(告诉程序该使用哪一个数据库驱动)
      • 注册给定的驱动程序:static void registerDriver(Driver driver) (DriverManager的方法)

      • 使用了Class.forName:Class.forName(“com.mysql.jdbc.Driver”)

        • 通过了给forName指定了是mysql的驱动
        • 它会帮助我们注册驱动,如下:
      • 在com.mysql.jdbc.Driver类中存在静态代码块(通过查看源码发现)

        //这是com.mysql.jdbc.Driver的静态代码块,只要使用这个类,就会执行这段代码
        //而Class.forName("com.mysql.jdbc.Driver")就正好使用到了这个类
        static {
        	try {
        		java.sql.DriverManager.registerDriver(new Driver());
        	} catch (SQLException E) {
        		throw new RuntimeException("Can't register driver!");
        	}
        }
        
        
        
  • 获取数据库连接(获取到数据库的连接并返回连接对象)

    • static Connection getConnection(String url, String user, String password);
      • 返回值:Connection数据库连接对象
      • 参数
        • url:指定连接的路径。语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
        • user:用户名
        • password:密码

2.Connection

  • Connection:数据库连接对象
    • 获取执行者对象
      • 获取普通执行者对象:Statement createStatement();
      • 获取预编译执行者对象:PreparedStatement prepareStatement(String sql);
    • 管理事务
      • 开启事务:setAutoCommit(boolean autoCommit); 参数为false,则开启事务。
      • 提交事务:commit();
      • 回滚事务:rollback();
    • 释放资源
      • 立即将数据库连接对象释放:void close();

3.Statement

  • Statement:执行sql语句的对象
    • 执行DML语句:int executeUpdate(String sql);
      • 返回值int:返回影响的行数
      • 参数sql:可以执行insert、update、delete语句。
      • int i = statement.executeUpdate(sql);
    • 执行DQL语句:ResultSet executeQuery(String sql);
      • 返回值ResultSet:封装查询的结果
      • 参数sql:可以执行select语句。
      • ResultSet resultSet = statement.executeQuery(sql);
    • 释放资源
      • 立即将执行者对象释放:void close();

4.ResultSet

  • ResultSet:结果集对象
    • 判断结果集中是否还有数据:boolean next();
      • 有数据返回true,并将索引向下移动一行
      • 没有数据返回false
    • 获取结果集中的数据:XXX getXxx(“列名”);
      • XXX代表数据类型(要获取某列数据,这一列的数据类型)
      • 例如:String getString(“name”); int getInt(“age”);
    • 释放资源
      • 立即将结果集对象释放:void close();

获取列的数据问题(获取游标指定的那一行里列的数据)

  • resultSet.get类型(String columnLabel 或 int columnIndex);
    • columnLable: 列名 如果起别名了就 写别名 select * …
      select id as aid, account as ac …
    • columnIndex: 列的下角标 从左向右从1开始

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

总结

//1.注册驱动
方案1: 调用静态方法,但是会注册两次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射触发 - 注册一次
Class.forName("com.mysql.cj.jdbc.Driver");

//2.获取连接

Connection connection = DriverManager.getConnection();

3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=账号&password=密码 )

//3.创建statement

//静态
Statement statement = connection.createStatement();
//预编译
PreparedStatement preparedstatement = connection.preparedStatement(sql语句结构);

//4.占位符赋值

preparedstatement.setObject(?的位置从左向右从1开始,?的值)

//5.发送sql语句获取结果

int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL

//6.查询结果集解析

//移动光标指向行数据 next();  if(next())  while(next())
//获取列的数据即可   get类型(列的下标 从1开始 | 别名或列名)
//获取列的信息   getMetadata(); ResultsetMetaData对象 包含的就是列的信息
                getColumnCount(); //列的数量
                getCloumnLebal(index); //列的别名,没有别名直接取列名
//7.关闭资源
close(); 

三、代码优化(preparedStatement)

1. 灵活变通SQL

  • 这是我们写的SQL语句,但你是否觉得这样去写太死了,通常我们在操作数据库的时候都会写很多SQL语句,是非常灵活的,但是就下面这样的话确实是不太好

    String sql = "insert into student values (1, 'zhansan')";
    
    
  • 所以我们可以将代码改为下面这样,记录中的【学号】和【姓名】字段我们可以通过自己输入来进行控制

    // 3.输入学号和姓名
    System.out.println("请输入学号:");
    Scanner sc1 = new Scanner(System.in);
    int id = sc1.nextInt();
    System.out.println("请输入姓名:");
    Scanner sc2 = new Scanner(System.in);
    String name = sc2.next();
    
    // 4.构造一个 SQL 语句,来完成插入操作
    //String sql = "insert into student values (1, 'zhansan')";
    String sql = "insert into student values (" + id + ", '" + name + "')";
    
    

2.JDBC实现模糊查询

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

/**
 * 这个程序两个任务
 *      第一:测试DBUtil是否好用
 *      第二:模糊查询怎么写?
 */

public class JDBCTest12 {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //获取连接
            connection = DBUtil.getConnection();
            //获取预编译的数据库操作对象
            
            //错误写法
            /*String sql = "select ename from emp where ename like '_?%'";
            ps = connection.prepareStatement(sql);
            ps.setString(1,"A");*/

            String sql = "select ename from emp where ename like ?";
            ps = connection.prepareStatement(sql);
            ps.setString(1,"_A%");
            resultSet = ps.executeQuery();
            while (resultSet.next()){
                System.out.println(resultSet.getString("ename"));
            }

        } catch (Exception throwables) {
            throwables.printStackTrace();
        }finally {
            //释放资源
            DBUtil.close(connection,ps,resultSet);
        }

    }
}

3. 防止SQL注入攻击

黑客可以通过代码直接拼接构造sql语句导致sql语句结构可被恶意篡改

对于上面这种危害,我们进行预防最靠谱的方案,就是使用PreparedStatement中占位符替换的方式,来实现SQL的构造~

  • 我们可以把SQL语句构造成下面这样,两个【?】就相当于是占位符

    String sql = "insert into student values (?, ?)";       //通过占位符进行替换
    String sql = "update student set name = ? where id = ?";
    String sql = "delete from student where id = ?";
    
  • 还记得我们上面使用到的那个statement对象吗,它可以用来描述SQL的情况,我们可以通过里面的【setInt()】和【setString()】方法来设置记录中的两个字段,从而将这个SQL语句构造完整
    在这里插入图片描述

    // jdbc中还需要搭配一个特定的对象,来搭配描述这里的sql情况
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setInt(1, id);
    ps.setString(2, name);
    

Statement和PreparedStatement对比

  • Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题。
  • Statement是编译一次执行一次,PreparedStatement是编译一次,可执行N次,PreparedStatement效率较高一些。
  • PreparedStatement会在编译阶段做类型的安全检查。
    【综上所述】:PreparedStatement使用较多,只有较少数的情况下需要使用Statement。
    在这里插入图片描述
    Statement
    在这里插入图片描述
    PrepareStatement
    在这里插入图片描述

什么情况下必须使用Statement呢???

  • 业务方面要求必须支持SQL注入的时候
  • Statement支持SQL注入,凡是以业务方面要求是需要进行SQL语句拼接的,必须使用Statement。

四、Druid连接池技术

(一)连接性能消耗问题的分析

在这里插入图片描述

(二)数据库连接池的作用

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

在这里插入图片描述

(三)国货之光Druid连接池的使用

  • 记得导入Druid工具类的jar包
  • 记得创建一个学习Druid的测试类
/**
 * ClassName: DruidUsePar
 * Package: com.atguigu.api.druid
 * Description:
 * druid连接池使用类
 */
public class DruidUsePart {

}

1.硬编码方式(了解,不推荐)

    /**
     * 硬编码实现
     * <p>
     * 1.创建一个druid连接池对象
     * 2.设置连接池参数[必须 | 非必须]
     * 3.获取连接[通用方法,所有连接池都一样]
     * 4.回收连接[通用方法,所有连接池都一样] TODO:这里不是 “关闭(释放)连接” 而是 “回收连接”
     */
    @Test
    public void testHard() throws SQLException {
        //连接池对象
        //DruidDataSource实现了Java规定标准的的DataSource接口
        DruidDataSource druidDataSource = new DruidDataSource();
        //设置参数
        //必须: 连接数据库驱动类的全限定符[注册驱动] | url | user | password
        druidDataSource.setUrl("jdbc:myql://127.0.0.1:3306/atguigu");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("root");
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");//帮助我们进行驱动注册和获取连接
        //非必须:初始化连接数量 , 最大的连接数量 , ......
        druidDataSource.setInitialSize(5);//初始化连接数量
        druidDataSource.setMaxActive(10);//最大的连接数量
        //获取连接
        //DruidPooledConnection实现了Java规定标准的Connection接口
        //因此现在 用Connection类 来 实例化druidPooledConnection对象后,
        //TODO:当调用.close()方法时,已经 不是关闭(释放)连接 ,而是 回收连接 了
        Connection druidPooledConnection = druidDataSource.getConnection();
        //数据库CURD

        //回收连接
        druidPooledConnection.close();//连接池提供的连接的.close()就是回收连接
    }

2.软编码方式
(1)外部配置

  • 存放位置:src/druid.properties
    在这里插入图片描述
# 注:外部配置文件后缀必须是properties!
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

(2)Druid声明代码

    /**
     * 软编码实现
     * 通过读取外部配置文件的方法,实例化druid连接池对象
     */
    @Test
    public void testSoft() throws Exception {
        //1.读取外部配置文件 Properties
        Properties properties = new Properties();
        //src下的文件,可以使用类加载器提供的方法实现装载(properties.load(param))
        InputStream inputPropertiesStream = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(inputPropertiesStream);
        //2.使用连接池的工具类的工厂模式(DruidDataSourceFactory)来创建连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        Connection connection = dataSource.getConnection();
        
        //数据库CURD
        //......
        
        //回收连接
        connection.close();
    }

3.Druid配置(了解)在这里插入图片描述
4.笔记
在这里插入图片描述

  • 连接池帮我们进行注册驱动、创建链接
    在这里插入图片描述

  • 关于外部配置文件
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

封装

(一)JDBC工具类封装version1.0

# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu

package com.atguigu.api.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

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

/**
 * ClassName: JDBC_Utils
 * Package: com.atguigu.api.utils
 * Description:
 * v1.0版本工具类
 * 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 * <p>
 * 小建议:
 * 工具类的方法推荐写成静态方法,这样的话外部调用会更加方便
 * <p>
 * 实现:
 * 属性 连接池对象[实例化一次]
 * <p>
 * 实例化一次的方法:
 * 1.单例模式
 * 2.static{全局调用一次} (静态代码块)
 * <p>
 * 方法:
 * 1.对外提供连接的方法
 * 2.回收外部传入的连接的方法
 */
public class JDBC_Utils {
    private static DataSource dataSource = null;//连接池对象

    //初始化连接池对象
    static {
        Properties properties = new Properties();
        InputStream ips = JDBC_Utils.class.getClassLoader().getResourceAsStream("druid.properties");
        try {
            properties.load(ips);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 对外提供连接的方法
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    /**
     * 回收连接
     */
    public static void freeConnection(Connection connection) throws SQLException {
        connection.close();//连接池的连接,调用close()就是回收
    }
}

二)JDBC工具类封装version2.0
对工具类v1.0版本的进一步优化

  • 在考虑事务的情况下,如何从一个线程里的不同方法中获取同一个连接
    在这里插入图片描述
    1. 工具类v2.0
package com.atguigu.api.utils;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * ClassName: JDBC_Utils
 * Package: com.atguigu.api.utils
 * Description:
 * v2.0版本工具类
 * 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
 *  TODO:
 *      利用线程本地变量(ThreadLocal)来存储连接信息!确保一个线程的多个方法可以获取同一个connection!
 *      优势: 事务操作的时候 service 和 dao 属于同一个线程,不用再传递connection的参数了
 *      大家都可以调用getConnection()自动获取相同的连接池
 */
public class JDBC_Utils_V2 {
    private static DataSource dataSource = null;//连接池对象

    //TODO:线程本地变量
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //初始化连接池对象
    static {
        //方法一 v1.0
//        Properties properties = new Properties();
//        InputStream ips = JDBC_Utils_V2.class.getClassLoader().getResourceAsStream("druid.properties");
//        try {
//            properties.load(ips);
//        } catch (IOException e) {
//            throw new RuntimeException(e);
//        }
//
//        try {
//            dataSource = DruidDataSourceFactory.createDataSource(properties);
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }

        //方法二 v2.0
        try {
            Properties properties = new Properties();
            properties.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 对外提供连接的方法
     */
    public static Connection getConnection() throws SQLException {
        //线程本地变量是否存在连接
        Connection connection = threadLocal.get();

        //第一次没有
        if (connection == null) {
            //线程本地变量没有,从连接池中获取连接
            connection = dataSource.getConnection();
            threadLocal.set(connection);
        }

        return connection;
    }

    /**
     * 回收连接
     */
    public static void RecycleConnection() throws SQLException {
        Connection connection = threadLocal.get();
        if (connection != null) {
            threadLocal.remove();//清空线程本地变量的数据
            connection.setAutoCommit(true);//把BankService里面,修改过的事务提交状态给恢复一下(false -> true)
            connection.close();//将拿出来的连接回收到连接池即可
        }
    }
}

高级应用封装BaseDao

在这里插入图片描述
针对DQL查询和非DQL查询,分成两类(即,定义两个方法)

public abstract class BaseDao {
    /*
    通用的增、删、改的方法
    String sql:sql
    Object... args:给sql中的?设置的值列表,可以是0~n
     */
    protected int update(String sql,Object... args) throws SQLException {
//        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        //执行sql
        int len = ps.executeUpdate();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //connection.getAutoCommit()为false,不要在这里回收connection,由开启事务的地方回收
        //connection.getAutoCommit()为true,正常回收连接
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return len;
    }

    /*
    通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
    这里的clazz接收的是T类型的Class对象,
    如果查询员工信息,clazz代表Employee.class,
    如果查询部门信息,clazz代表Department.class,
    返回List<T> list
     */
    protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
        //        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();

        /*
        获取结果集的元数据对象。
        元数据对象中有该结果集一共有几列、列名称是什么等信息
         */
         ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集列数

        //遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
        while(res.next()){
            //循环一次代表有一行,代表有一个T对象
            T t = clazz.newInstance();//要求这个类型必须有公共的无参构造

            //把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
            for(int i=1; i<=columnCount; i++){
                //for循环一次,代表取某一行的1个单元格的值
                Object value = res.getObject(i);

                //这个值应该是t对象的某个属性值
                //获取该属性对应的Field对象
                //String columnName = metaData.getColumnName(i);//获取第i列的字段名
                //这里再取别名可能没办法对应上
                String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//这么做可以操作private的属性

                field.set(t, value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.free();
        }
        return list;
    }

	//目的主要是安全保障,提高代码的健壮性
    protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql, args);
        if(list == null || list.size() == 0){
            return null;
        }
        return list.get(0);
    }
}

五、JDBC工具类的封装

driverClass = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/test
username = root
password = 123456
package utils;

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

/**
 * @version 1.0
 * @Author pangyu
 * @Date 2024/5/16 16:52
 * @注释
 */
/*
    JDBC工具类
 */
public class JDBCUtils {
    private JDBCUtils(){

    }

    // 声明所需要的配置变量
    private static String driverClass;
    private static String url;
    private static String username;
    private static String password;
    private static Connection con;

    //提供静态代码块,读取配置文件信息为变量赋值,注册驱动
    static {
        try {
            InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties prop = new Properties();
            prop.load(is);

            driverClass = prop.getProperty("driverClass");
            url = prop.getProperty("url");
            username = prop.getProperty("username");
            password = prop.getProperty("password");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 获取数据库连接方法
    public static Connection getConecction(){
        try {
            Class.forName(driverClass);
            con = DriverManager.getConnection(url,username,password);
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        return con;
    }

    //释放资源方法
    public static void close(Connection con, Statement stat, ResultSet rs){
        if(con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

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

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

    public static void close(Connection con, Statement stat){
        close(con,stat,null);

    }
}


扩展

(一)自增长主键回显的实现

1.功能需求

  • 主键回显
    • 定义:在数据库里的表中插入一条记录时,如果该表设置了适当的参数,那么数据库会在插入操作完成后返回生成的主键值
      • 这个主键可以是有数据库自动生成的,也可以是由开发人员明确指定的。
    • 作用:主键回显能够允许开发者在执行数据库中表的插入操作后,立即在Java后端中获取到新插入记录的主键(值),方便后续的操作和数据管理
  • 这里要解决的问题:
    • **主表默认增长,但是从表不知道值,所以要主键回显,**让从表知道主表的主键增长了
    • 解决思路:
      • 在多表关联插入数据时,一般主表的主键都是自动生成的,因此在插入数据之前我们无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术

2.功能实现

  • 继续沿用之前的数据库里的表的数据
    /**
     * TODO:
     *     t_user插入一条数据,并且 -- 获取数据库自增长的主键 --
     * <p>
     * 使用总结:
     *  1.创建prepareStatement的时候,在.prepareStatement()的参数列表中加入一个参数,
     *      来告知数据库,返回数据的时候记得携带数据库自增长的主键。
     *    加入的参数:Statement.RETURN_GENERATED_KEYS
     * <p>
     *  2.获取 插入数据之后,其主键值也已经更新完后的 结果集对象,
     *      获取对应的数据即可 - ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
     */
    @Test
    //主键回显 和 主键值获取
    public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu", "root", "root");
        String sql = "insert into t_user(account,password,nickname) value(?,?,?)";

        //创建preparedStatement
        //注意:"PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的:要数据库返回数据时把KEYS的内容也带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

        preparedStatement.setObject(1, "test1");
        preparedStatement.setObject(2, 123456);
        preparedStatement.setObject(3, "驴蛋蛋");
        int i = preparedStatement.executeUpdate();

        //结果分析
        if (i > 0) {
            System.out.println("插入成功!");
            //可以获取回显的主键
            //获取搞完主键后的结果集对象,一行 一列 ,id = 值
            ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
            generatedKeys.next();
            int id = generatedKeys.getInt(1);
            System.out.println("id = " + id);
        } else {
            System.out.println("插入失败!");
        }

        preparedStatement.close();
        connection.close();
    }

(二)批量数据插入的性能提升

1.功能需求

  • 批量数据插入优化
  • 提升大量数据插入效率

2.功能实现
(1)使用普通的方式插入10000条数据所需的时间

    @Test
    //使用普通的方式插入10000条数据所需的时间
    public void testInsert() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu", "root", "root");
        String sql = "insert into t_user(account,password,nickname) value(?,?,?)";

        //创建preparedStatement
        //注意:"PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的:要数据库返回数据时把KEYS的内容也带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //占位符赋值
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1, "dd" + i);
            preparedStatement.setObject(2, "dd" + i);
            preparedStatement.setObject(3, "驴蛋蛋" + i);

            //TODO: 占位符赋值之后,发送SQL语句,并返回结果
            preparedStatement.executeUpdate();
        }
        long end = System.currentTimeMillis();
        //结果分析
        System.out.println("执行10000次数据插入消耗的时间: " + (end - start));//26816毫秒

        preparedStatement.close();
        connection.close();
    }

(2)优化:使用 批量插入 的方式插入10000条数

总结
批量输入

  1. 路径后面添加 ?rewriteBatchedStatements=true 允许批量输入
  2. insert into values[必须要写被插入数据的列] 且 SQL语句的最后不能添加';'
  3. 不是执行每条语句,而是利用addBatch()来批量添加数据到SQL语句中的values后面
  4. 遍历添加数据完以后,统一执行executeBatch()来执行SQL语句
    @Test
    //批量插入数据优化
    //使用 批量插入 的方式插入10000条数据所需的时间

    public void testBatchInsert() throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.cj.jdbc.Driver");
        //TODO:这里添加了一个url的路径属性里的可选信息 - "rewriteBatchedStatements=true"
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu?rewriteBatchedStatements=true", "root", "root");

        //TODO:这里不能写value,要写values,一直写values就行,记住
        //TODO: 这里SQL语句最后不能加上 ';' 否则会执行失败。 
        //        原因:不加';'是因为 - 批量插入的原理是在原来的sql语句后面继续追加相应的sql语句
        String sql = "insert into t_user(account,password,nickname) values(?,?,?)";

        //创建preparedStatement,TODO:注意"com.atguigu.api.PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的是要数据库返回数据时把KEYS的内容也带回来
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        //测试发送的时间
        long start = System.currentTimeMillis();

        //占位符赋值
        for (int i = 0; i < 10000; i++) {
            preparedStatement.setObject(1, "ddd" + i);
            preparedStatement.setObject(2, "ddd" + i);
            preparedStatement.setObject(3, "驴蛋蛋d" + i);

            //TODO: 占位符赋值之后,发送SQL语句,并返回结果
            //TODO:删去preparedStatement.executeUpdate();

            //TODO:赋值一次就传给数据库一次的想法就不用了
            //TODO:而是用 addBatch() 把赋值后的数据直接追加到SQL语句中的values后面
            preparedStatement.addBatch();
        }
        //TODO:最后执行批量操作
        preparedStatement.executeBatch();

        long end = System.currentTimeMillis();

        //结果分析
        System.out.println("执行10000次数据插入消耗的时间: " + (end - start));//310毫秒

        preparedStatement.close();
        connection.close();
    }

(三)、JDBC的事物自动提交机制

JDBC事务机制

  1. JDBC中的事务是自动提交的,什么是自动提交?
    • 只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为。
    • 但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。

手动提交机制

重点三行代码:

  • connection.setAutoCommit(false);//开启事务
  • connection.commit();//提交事务
  • connection.rollback();//回滚事务
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * sql脚本:
 *    drop table if exists t_act;
 *    create table t_act(
 *      actno int,
 *      balance double(7,2)   //注意:7表示有效数据的个数,2表示小数位的个数。
 *    );
 *    insert into t_act(actno,balance) values(111,20000);
 *    insert into t_act(actno,balance) values(222,0);
 *    commit;
 *    select * from t_act;
 *
 *    批量编辑快捷键:Alt+shift+insert
 */
public class JDBCTest11 {
    public static void main(String[] args) {

        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1.注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode?serverTimezone=UTC","root","user");
            //将自动提交机制改为手动提交
            connection.setAutoCommit(false);//开启事务
            //3.获取预编译的数据库操作对象
            String sql = "update t_act set balance = ? where actno = ?";
            ps = connection.prepareStatement(sql);
            //给?传值
            ps.setDouble(1,10000);
            ps.setInt(2,111);
            int count = ps.executeUpdate();

           //制造一个异常,这里坑定会出现空指针异常,
            // 出现异常下边的代码将会不执行,直接进入catch语句块...
            String s =null;
            s.toString();

            //给?传值
            ps.setDouble(1,10000);
            ps.setInt(2,222);
             count += ps.executeUpdate();

            System.out.println(count == 2 ?  "转账成功" : "转账失败");

            //程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据
            connection.commit();//提交事务
        } catch (Exception e) {
            //回滚事务
            if (connection != null){
                try {
                    connection.rollback();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            e.printStackTrace();
        }finally {
            //6.释放资源
            if (ps == null) {
                try {
                    ps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (connection == null) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}


  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值