JDBC学习笔记(JDBC,结果集,预处理,反射封装,JDBC批处理)

JDBC概述

  • 刚开始通过客户端连接数据库
    在这里插入图片描述
  • 因为数据库厂商有Mysql,Oracle,DB2,SqlServer等等,所以SUN公司提出了连接数据库的规范
    在这里插入图片描述

JDBC:Java DataBase Connectivity(java数据库连接)

连接MySQL数据库

MySQL数据库这个厂商需要提供对JDBC接口的实现类,这些实现类被打包成了一个Jar,如果Java项目要使用,必须要跟项目建立依赖关系。

Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句

1.java.sql包下常用的接口

  • public interface Driver每个数据库厂商必须实现该驱动接口
  • public class DriverManager:管理驱动类
  • public interface Connection:建立数据库连接
  • public interface Statement:执行SQL语句
  • public interface ResultSet:表示数据库结果集的数据表,将查询后的虚拟表封装成Java对象ResultSet

2.JDBC连接数据库的步骤

在这里插入图片描述

示例1:变更语句:executeUpdate

准备:导入驱动包并建立依赖

package com.yu;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.Statement;
import java.util.Properties;

public class A_变更语句 {
    public static void main(String[] args) throws Exception {
        //1.加载驱动类——面向接口编程
        /*
        * driver在编译期间只可使用java.sql.Driver()接口定义的变量与方法
        * driver在运行期间使用java.sql.Driver()接口实现的方法
        * */
        Driver driver = new com.mysql.jdbc.Driver();//接口回调,编译时异常处理
        //2.建立数据库连接通道
        /*
        * URL:数据库访问路径
        * Properties:提供用户名和密码
        * */
        //String url="http://www.baidu.com:80"=>协议://IP地址:“端口号”;
        //?verifyServerCertificate=false&useSSL=false:禁用SSL  characterEncoding=UTF-8:防止数据库语言乱码
        String url = "jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8";
        Properties props = new Properties();
        props.setProperty("user","root");
        props.setProperty("password","");

        Connection conn = driver.connect(url,props);
        //System.out.println("coon = " + conn);//=>控制台输出地址:coon = com.mysql.jdbc.JDBC4Connection@326de728
        //3.设置执行的sql指令
        String sql = "INSERT INTO student(student_name,student_sex,age,birthday) values ('老王','女',66,null)";
        //4.获取执行sql语句对象
        Statement statement = conn.createStatement();
        //5.执行sql变更语句:execute
        statement.executeUpdate(sql);
		//6.关闭资源(顺序由内向外)
        statement.close();
        conn.close();
    }
}

结果:
在这里插入图片描述

示例2:查询语句:executeQuery
package com.yu;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

public class B_查询语句 {
    public static void main(String[] args) throws Exception {
        //1.加载驱动类——面向接口编程
        Driver driver = new com.mysql.jdbc.Driver();//接口回调,编译时异常处理
        //2.建立数据库连接通道
        String url = "jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8";
        Properties props = new Properties();
        props.setProperty("user","root");
        props.setProperty("password","");
        Connection conn = driver.connect(url,props);

        //3.设置执行的sql指令
        String sql = "select * from student";
        //4.获取执行sql语句对象
        Statement statement = conn.createStatement();
        //5.执行sql变更语句:execute   ResultSet是将虚拟表封装了
        ResultSet resultSet = statement.executeQuery(sql);
        System.out.println("resultSet = " + resultSet);
         //6.关闭资源(顺序由内向外)
        resultSet.close();
        statement.close();
        conn.close();
    }
}

3.JDBC优化

准备

如果我们没有提供数据库厂商的Jar,那么我们写上述两个类在编译期间就无法通过

Driver driver = new com.mysql.jdbc.Driver();//com.ysql.jdbc.Driver必须存在
用class.forName()方法不会执行com.mysql.jdbc.Driver类的构造方法,会直接直接静态代码块中的DriverManager.registerDriver(new Driver());创建驱动类的对象而不需要com.mysql.jdbc.Driver(是字符串)该类存在,编译便可通过。解释如下:
com.mysql.jdbc.Driver类做了什么?

调用了构造方法

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {//处理编译时异常
        //构造方法,创建对象使用和对实例变量的初始化
    }

    static {//静态代码块随着类的加载而加载,只是加载一次,如果使用Class.forName加载com.mysql.jdbc.Driver(是字符串)
        try {
            DriverManager.registerDriver(new Driver());//new Driver()创建驱动类的对象
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

如果使用Class.forName加载com.mysql.jdbc.Driver(因为这个位置是字符串,可以随意改,不需要该类真实存在编译不会报错,顶多运行时报错会执行静态代码块,而不会加载构造方法
编译通过,该类在运行的时候才报错ClassNotFoundException

package com.os.test;

public class C_静态代码块 {
	public static void main(String[] args)throws Exception {
		//随着类的加载而加载,加载类到“方法区”(类的信息、全局数据区域static、常量池)
		//Class clazz1 = Person.class;//不会执行静态代码块
		//System.out.println(clazz1);
		//创建对象的时候才开始加载
		//Person p1 = (Person)clazz1.newInstance();
		//System.out.println("p1 = " + p1);

		//Person p2 = new Person();//先加载类的信息,才能创建对象,会执行静态代码块
		/*
		* 上述两种方式,我们使用的类必须存在,如果不存在,编译无法通过
		* */
		//手动加载类
		//Class.forName("com.osasdfasdf.asdfsadf.asdf.sadf");//编译通过,该类在运行的时候才报错ClassNotFoundException
		Class.forName("com.os.test.Person");//加载静态代码块
	}
}
class Person{
	static{
		System.out.println("静态代码块:随着类的加载而加载");
	}
	public Person(){
		System.out.println("Person的构造方法");
	}
}

静态代码块随着类的加载而加载,加载类到“方法区”(类的信息、全局数据区域static、常量池),所以;
Class clazz1 = Person.class=>不会执行静态代码块,这行代码只是获取了类对象,并没有加载类
Person p2 = new Person()=>可以执行静态代码块,因为代码创建了对象

Class.forName(“com.os.test.Person”)=>没有创建对象却加载静态代码块
Class.forName()的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。**
与new区别:
1.Class.forName()返回的是一个类,new返回对象
2.new就相当于Class.forName(“XXX”).newInstance();

使用Class.forName()驱动类
package com.yu;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.Statement;
import java.util.Properties;

public class B_改进代码 {
    public static void main(String[] args) throws Exception {
        //1.加载驱动类——面向接口编程
        //Driver driver = new com.mysql.jdbc.Driver();//接口回调,编译时异常处理
        Class.forName("com.mysql.jdbc.Driver");//底层已将帮我们创建了com.mysql.jdbc.Driver()对象,并且将该驱动对象使用DriverManage管理
        //2.建立数据库连接通道
        String url = "jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8";
        Connection conn = DriverManager.getConnection(url,"root","");
       // System.out.println("coon = " + conn);//=>控制台输出地址:coon = com.mysql.jdbc.JDBC4Connection@326de728
        //3.设置执行的sql指令
        String sql = "INSERT INTO student(student_name,student_sex,age,birthday) values ('lao ','女',66,null)";
        //4.获取执行sql语句对象
        Statement statement = conn.createStatement();
        //5.执行sql变更语句:execute
        int row = statement.executeUpdate(sql);//此处是影响的行数
        System.out.println("影响的行数 = " + row);
        //6.关闭资源(顺序由内向外)
        statement.close();
        conn.close();

    }
}

如果上述的代码,没有了源文件,我们就无法对访问路径等信息进行维护
所以设置配置文件

改进代码:

1.配置文件:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/yue?verifyServerCertificate=false&useSSL=false&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=

2.java代码:

package com.yu;

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

public class C_属性文件 {
    private static String DRIVER;
    private static String URL;
    private static String USERNAME;
    private static String PWD;
    static {
        try {
            Properties properties = new Properties();
            //读取属性文件
            InputStream in = C_属性文件.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //加载属性文件
            properties.load(in);
            //赋值
            DRIVER = properties.getProperty("jdbc.driverClassName");
            URL = properties.getProperty("jdbc.url");
            USERNAME = properties.getProperty("jdbc.username");
            PWD = properties.getProperty("jdbc.password");

        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("加载配置文件失败!");
        }
    }
    public static void main(String[] args) throws Exception {
        //1.加载驱动类——面向接口编程
        Class.forName(DRIVER);
        //2.建立数据库连接通道
        Connection conn = DriverManager.getConnection(URL,USERNAME,PWD);
       // System.out.println("coon = " + conn);//=>控制台输出地址:coon = com.mysql.jdbc.JDBC4Connection@326de728
        //3.设置执行的sql指令
        String sql = "INSERT INTO student(student_name,student_sex,age,birthday) values ('瑶 ','女',66,null)";
        //4.获取执行sql语句对象
        Statement statement = conn.createStatement();
        //5.执行sql变更语句:execute
        statement.executeUpdate(sql);
        //6.关闭资源(顺序由内向外)
        statement.close();
        conn.close();

    }
}

结果集

一.查询:ResultSet

1.遍历数据

当执行SELECT语句之后,数据库中获取了“虚拟表(数据表)”(包含数据和虚拟表的字段信息),JDBC将虚拟封装成一个ResultSet对象返回给程序员使用。

ResultSet对象具有**指向当前数据行的光标

光标最初的所在位置被安置于第一行之前

使用next方法可以将光标移动下一行,并且由于在ResultSet对象中没有更多行时返回false。

查询的结果集ResultSet的使用步骤:

  • 光标(游标)向下移动一行
  • 判断是否有数据
  • 获取数据

(全文引入的工具类)代码:

DBHelper类:
package com.yu;

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

/**
 * 工具类:获取数据库连接和关闭资源
 * */
public class DBHelper {
    private static String DRIVER;
    private static String URL;
    private static String USERNAME;
    private static String PWD;
    static {
        try {
            Properties properties = new Properties();
            //读取属性文件
            InputStream in = DBHelper.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //加载属性文件
            properties.load(in);
            //赋值
            DRIVER = properties.getProperty("jdbc.driverClassName");
            URL = properties.getProperty("jdbc.url");
            USERNAME = properties.getProperty("jdbc.username");
            PWD = properties.getProperty("jdbc.password");

        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("加载配置文件失败!");
        }
    }
    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("加载驱动类失败!");
        }
    }
    public Connection getConnection(){
        try {
            return DriverManager.getConnection(URL,USERNAME,PWD);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException("建立数据库连接通道失败!");
        }
    }

    public void close(Connection conn, Statement st,PreparedStatement ps, ResultSet rs){
        if (rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (st!=null){
            try {
                st.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (ps!=null){
            try {
                ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    public void close(Connection conn, Statement st){
        this.close(conn,st,null,null);
    }
    public void close(Connection conn, Statement st,ResultSet rs){
        this.close(conn,st,null,null);
    }
    public void close(Connection conn, PreparedStatement ps){
        this.close(conn,null,ps,null);
    }
    public void close(Connection conn, PreparedStatement ps,ResultSet rs){
        this.close(conn,null,ps,null);
    }
}


java代码:

package com.Demo;

import com.yue.DBHelper;

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

public class A_ResultSet获取数据 {
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        //获取数据库连接
        Connection conn = dbHelper.getConnection();
        //设置指令
        String sql = "SELECT student_id 学生编号,student_name 姓名,student_sex 性别,age 年龄,birthday 生日 FROM student";
        //创建执行sql语句对象
        Statement st = conn.createStatement();
        //封装虚拟表,获取结果集对象
        ResultSet rs = st.executeQuery(sql);//光标处于虚拟表第一行,也就是字段信息的前面,所以需要下移一行才能获得数据
        while (rs.next()){//rs.next():返回布尔类型
            System.out.print(rs.getInt("学生编号")+"\t");//索引值从1开始,和java不同
            System.out.print(rs.getString("姓名")+"\t");
            System.out.print(rs.getString("性别")+"\t"+"\t");
            System.out.print(rs.getString("年龄")+"\t"+"\t"+"\t");
            System.out.println(rs.getString("生日")+"\t");

        };
        //如果你Java中使用的是java.util.Date类型,我们ResultSet得到的日期类型为java.sql.Date,转换会丢失
        // 使用(java.util.Date)rs.getObject("字段名")方法强转就不会丢失


        //关闭资源
        dbHelper.close(conn,st,rs);
    }
}

结果:
在这里插入图片描述

2.获取记录数(两种方法)

一.光标移动:rs.beforeFirst()
public class B_获取记录数 {
	public static void main(String[] args) throws SQLException {
		DBHelper dbHelper = new DBHelper();
		//获取数据库连接
		Connection conn = dbHelper.getConnection();
		//设置指令
		String sql = "SELECT student_id studentId,student_code studentCode,student_name studentName ,student_age,sex FROM student";
		//获取执行SQL语句对象
		Statement st = conn.createStatement();
		//获取结果集对象(封装虚拟表)
		ResultSet rs = st.executeQuery(sql);//光标处于第一行的前面
		rs.last();//移动到最后一行★★★★★
		int rowCount = rs.getRow();//★★★★★

		System.out.println("rowCount = " + rowCount);
		System.out.println("当前关闭已经处于最后一行,无法在进行遍历了,将光标移动到第一行的前面");
		rs.beforeFirst();//★★★★★移动至第一行前,即可继续遍历

		while(rs.next()){//移动光标了,false代表没有记录了!
			System.out.print(rs.getInt("studentId")+"\t");//rs.getXXX(虚拟表中的字段名获取数据)
			System.out.print(rs.getString("studentCode")+"\t");
			System.out.print(rs.getString("studentName")+"\t");
			System.out.print(rs.getDouble("student_age")+"\t");
			System.out.println(rs.getObject("sex"));
			//如果你Java中使用的是java.util.Date类型,我们ResultSet得到的日期类型为java.sql.Date,转换会丢失
			// (java.util.Date)rs.getObject("字段名")
		}
}
二.普通方法
	System.out.println("获取记录数:推荐使用,聚合函数");
		sql = "SELECT COUNT(*) FROM student";
		rs = st.executeQuery(sql);
		rs.next();//★★★★★
		rowCount = rs.getInt(1);//★★★★★
		System.out.println("rowCount = " + rowCount);
		//关闭资源
		dbHelper.close(conn,st,rs);

3.获取列信息

ResultSet其实是获取的是行信息,当我们不知道你查询的有哪些列时候,如何遍历我们的数据。

ResultSetMetaData是描述结果集的元数据对象,获得每个字段的类型、是否可以为空值等信息,有可以直接获取数据和列的个数。

适用情况:不知道列数,列名

public class C_获取列信息 {
	public static void main(String[] args) throws SQLException {
		DBHelper dbHelper = new DBHelper();
		//获取数据库连接
		Connection conn = dbHelper.getConnection();
		//设置指令
		String sql = "SELECT student_id studentId,student_code studentCode,student_name studentName ,student_age,sex FROM student";
		//获取执行SQL语句对象
		Statement st = conn.createStatement();
		//获取结果集对象(封装虚拟表)
		ResultSet rs = st.executeQuery(sql);//光标处于第一行的前面
		rs.next();
		ResultSetMetaData rsmd = rs.getMetaData();

		int columnCount = rsmd.getColumnCount();
		System.out.println("获取列的总数量:"+columnCount);
		//遍历获取对应的列名和是否为空,注意:mysql列的索引从1开始
		for(int i=1;i<=columnCount;i++){
			System.out.println("真实字段名:"+rsmd.getColumnName(i)+",字段的别名:"+rsmd.getColumnLabel(i)+",判断是否为空:"+rsmd.isNullable(i));
		}
		//获取每列中的数据,如果完成
		rs.previous();//后退,移动到上一行
		while (rs.next()){
			for(int i=1;i<=columnCount;i++) {
				String columnName = rsmd.getColumnLabel(i);//获取虚拟表中的字段名
				System.out.print("字段名:"+columnName+",值:"+rs.getString(columnName)+"\t");
			}
			System.out.println();
		}

		//关闭资源
		dbHelper.close(conn,st,rs);
	}
}

结果:
在这里插入图片描述

4.ResultSet相关问题

当我们ResultSet查询结果集必须要及时的关闭资源,当关闭之后我们就无法再使用ResultSet对象了(内存地址依旧存在,但是无法操作)

//关闭资源
dbHelper.close(conn,st,rs);
System.out.println("rs = " + rs);
rs.first();
//控制台输出异常:Exception in thread "main" java.sql.SQLException: Operation not allowed after ResultSet closed
ResultSet二次封装

在这里插入图片描述

当关闭资源后便无法使用ResultSet对象
所以对ResultSet对象进行二次封装,这样java程序对其操作就不需要关心内部有没有关闭资源

1.ResultSet对象封装一条记录:Map<>
package com.Demo;

import com.yue.DBHelper;

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

public class A_ResultSet二次封装一条数据 {
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        //获取数据库连接
        Connection conn = dbHelper.getConnection();
        //设置指令
        String sql = "SELECT student_id 学生编号,student_name 姓名,student_sex 性别,age 年龄,birthday 生日 FROM student where student_id = 6";
        //创建执行sql语句对象
        Statement st = conn.createStatement();
        //封装虚拟表,获取结果集对象
        ResultSet rs = st.executeQuery(sql);

        //创建Map集合储存一条记录
        Map<String,Object> resultMap = null;
        while (rs.next()){
            resultMap = new HashMap<>();
            //对结果集进行二次封装
            resultMap.put("学生编号",rs.getInt("学生编号")+"\t");
            resultMap.put("姓名",rs.getString("姓名")+"\t");
            resultMap.put("性别",rs.getString("性别")+"\t");
            resultMap.put("年龄",rs.getDouble("年龄")+"\t");
            resultMap.put("生日",rs.getDate("生日")+"\t");
        };

        //关闭资源
        dbHelper.close(conn,st,rs);

        //关闭资源后的操作
        System.out.println(resultMap);//结果:null或一条记录
    }
}

结果:
在这里插入图片描述
改良代码:

如果字段过多的,可根据我们查询的列进行自动的处理

package com.Demo;

import com.yue.DBHelper;

import java.sql.*;
import java.util.HashMap;
import java.util.Map;

public class A_ResultSet获取数据 {
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        //获取数据库连接
        Connection conn = dbHelper.getConnection();
        //设置指令
        String sql = "SELECT * FROM student where student_id = 6";
        //创建执行sql语句对象
        Statement st = conn.createStatement();
        //封装虚拟表,获取结果集对象
        ResultSet rs = st.executeQuery(sql);

        //创建Map集合储存一条记录
        Map<String,Object> resultMap = null;
        while (rs.next()){
            resultMap = new HashMap<>();
            //对结果集进行二次封装
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            for (int i = 1; i < columnCount; i++) {
                String columnName = rsmd.getColumnName(i);
                Object columnValue = rs.getObject(columnName);//rs.getObject(i)也可
                resultMap.put(columnName,columnValue);
            }
        };

        //关闭资源
        dbHelper.close(conn,st,rs);

        //关闭资源后的操作
        System.out.println(resultMap);//结果:null或一条记录
    }
}

2.ResultSet对象封装多条记录:List

也是一条一条的处理,目前我们都是使用Java提供的容器完成

package com.Demo;

import com.yue.DBHelper;

import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class A_ResultSet获取数据 {
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        //获取数据库连接
        Connection conn = dbHelper.getConnection();
        //设置指令
        String sql = "SELECT * FROM student where student_id = 6";
        //创建执行sql语句对象
        Statement st = conn.createStatement();
        //获取结果集对象(封装虚拟表)
        ResultSet rs = st.executeQuery(sql);//光标处于第一行的前面
        List<Map<String,Object>> resultList = null;
        //判断是否由数据
        if(rs.next()){
            //1.对集合进行实例化
            resultList = new ArrayList<>();
            //2.获取列的元信息
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            //3.直接使用do...while循环
            do{
                //每次处理完一条记录
                Map<String,Object> recordMap = new HashMap<>();
                for(int i=1;i<=columnCount;i++){
                    String columnName = rsmd.getColumnLabel(i);
                    Object columnValue = rs.getObject(i);
                    recordMap.put(columnName,columnValue);
                }
                //就往集合中进行存储
                resultList.add(recordMap);
            }while(rs.next());
        }

        //关闭资源
        dbHelper.close(conn,st,rs);
        //关闭之后的操作
        for (Map<String, Object> map : resultList) {
            System.out.println(map);
        }
    }
}

预处理

1.数据进行处理

在JDBC中对执行SQL语句的对象提供了两种方式:StatementPrepareStatement对象

在开发中推荐使用PrepareStatement对象, 原因如下:

使用Statement对象的代码测试:

package com.Demo02;

import com.yue.DBHelper;

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

public class A_Statement对象 {
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        String student_id = "1001";
        String studnet_name = "陆'峰";//改为"陆峰"则无问题
        String student_sex = "男";
        Integer age = 19;

        String sql  = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES ('"+student_id+"','"+studnet_name+"','"+student_sex+"',"+age+")";
        Statement st = conn.createStatement();
        st.executeUpdate(sql);//报错:check the manual that corresponds to your MySQL server version for the right syntax to use near '峰','男',19)' at line 1
    }
}

原因:

String studentName = "陆‘’峰";//假设控制台输入的字符串为"陆‘’峰"

控制台报错:
sql = INSERT INTO student (student_code,student_name,student_age,sex) VALUES('1007','陆‘’峰',19,'男')

"陆'峰"中的“‘”恰好同sql指令中前面的单引号配对,所以sql语句无法继续执行下去

解决方式:

如果是程序员自己,那么我就需要对特殊的字符必须进行单独的转义处理

String studentName = "陆\\'峰";//假设控制台输入的字符串为陆'峰,需要自己对该数据进行处理 陆\\'峰

可以使用第三方提供的jar工具包进行处理,但是不推荐
可以直接使用预处理方式,可以帮我们对数据进行自动的处理

使用PreparStatement对象的代码测试:

package com.Demo02;

import com.yue.DBHelper;

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

public class B_PreparStatement预处理对象 {
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        String student_id = "1003";
        String student_name = "陆'峰";//'峰','男',19)'
        String student_sex = "男";
        Integer age = 19;

        //占位符与预处理对象
        String sql  = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES (?,?,?,?)";
        PreparedStatement ps = conn.prepareStatement(sql);
        //占位符的索引从1开始,当赋值的时候,系统对数据进行处理
        ps.setString(1,student_id);
        ps.setString(2,student_name);
        ps.setString(3,student_sex);
        ps.setInt(4,age);
        //执行
        ps.executeUpdate();
    }
}

省略了自己对数据进行处理的步骤

2.防止SQL注入

该代码只是去演示如何做到防止SQL注入,实际开发中业务的逻辑不是这么写的。

package com.Demo02;

import com.yue.DBHelper;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class C_Statement注入 {
    private static Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();

        System.out.println("登录页面");
        System.out.println("账号");
        String account = scanner.next();
        System.out.println("密码");
        String password = scanner.next();

        String sql  = "SELECT * FROM userinfo where account = '"+account+"' and password = '"+password+"'";
        System.out.println("sql = " + sql);
        Statement st = conn.createStatement();
        ResultSet rs = st.executeQuery(sql);

        if (rs.next()){
            System.out.println("登陆成功");
        }else {
            System.out.println("登录失败");
        }


    }
}

上述演示代码就算不知道账号和密码,也能登录成功!
在这里插入图片描述

SQL注入,不安全
解决方式:预处理

package com.Demo02;

import com.yue.DBHelper;

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

public class C_PreparStatement注入 {
    private static Scanner scanner = new Scanner(System.in);
    public static void main(String[] args) throws SQLException {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();

        System.out.println("登录页面");
        System.out.println("账号");
        String account = scanner.next();
        System.out.println("密码");
        String password = scanner.next();
        String sql = "SELECT * FROM userinfo WHERE account=? and password=?";
        System.out.println(sql);
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1,account);
        ps.setString(2,password);
        ResultSet rs = ps.executeQuery();

        if(rs.next()){
            System.out.println("登录成功");
        }else{
            System.out.println("登录失败");
        }
    }
}

结果:
在这里插入图片描述

3.PrepareStatement的效率高于Statement

package com.Demo02;

import com.yue.DBHelper;

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

public class E_效率 {
    public static void main(String[] args) throws SQLException {
        DBHelper db = new DBHelper();
        Connection conn = db.getConnection();
        Statement st = conn.createStatement();

        long start1 = System.currentTimeMillis();
        for(int i=5000;i<=6000;i++){
            String studentName1 = "包子"+i;
            String studentSex1 = new Random().nextBoolean()?"男":"女";
            Integer age1 = new Random().nextInt(100);

            String sql1  = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES ('"+i+"','"+studentName1+"','"+studentSex1+"',"+age1+")";
            st.executeUpdate(sql1);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("statement毫秒数 = " +(end1-start1));//35251

        long start2 = System.currentTimeMillis();
        String sql2 = "INSERT INTO student (student_id,student_name,student_sex,age) VALUES (?,?,?,?)";
        PreparedStatement ps = conn.prepareStatement(sql2);
        for(int j=8000;j<=9000;j++){
            String studentName2 = "辣条"+j;
            String studentSex2 = new Random().nextBoolean()?"男":"女";
            Integer age2 = new Random().nextInt(100);

            ps.setObject(1,j);
            ps.setObject(2,studentName2);
            ps.setObject(3,studentSex2);
            ps.setObject(4,age2);

            ps.executeUpdate();

        }
        long end2 = System.currentTimeMillis();
        System.out.println("Preparstatement毫秒数 = " +(end2-start2));
    }
}

结果:
在这里插入图片描述

通过反射将JDBC封装

student测试类

package com.Demo04.model;

public class Student {
    private Integer student_id;
    private String student_name;
    private String student_sex;
    private Integer age;

    public Integer getStudent_id() {
        return student_id;
    }

    public void setStudent_id(Integer student_id) {
        this.student_id = student_id;
    }

    public String getStudent_name() {
        return student_name;
    }

    public void setStudent_name(String student_name) {
        this.student_name = student_name;
    }

    public String getStudent_sex() {
        return student_sex;
    }

    public void setStudent_sex(String studentSex) {
        this.student_sex = studentSex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }



    @Override
    public String toString() {
        return "Student{" +
                "studentId=" + student_id +
                ", student_name='" + student_name + '\'' +
                ", age=" + age +
                ", studentSex='" + student_sex + '\'' +
                '}';
    }
}

将记录封装为自定义java对象

package com.Demo04;

import com.Demo04.model.Student;
import com.yu.DBHelper;

import java.lang.reflect.Field;
import java.sql.*;

public class A_记录转成Java对象 {
    public static void main(String[] args) throws Exception {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        String sql = "SELECT * FROM student  where student_id = ?";

        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1,10);

        ResultSet rs = ps.executeQuery();

        Student student= null;
        Class clazz = Student.class;
        if (rs.next()){
            student = (Student) clazz.newInstance();//实例化
           /* student = new Student();
            //自己完成数据的存储
            student.setStudentId(rs.getInt("studentId"));
            student.setStudent_name(rs.getString("studentName"));
            student.setStudentSex(rs.getString("studentSex"));
            student.setAge(rs.getInt("studentAge"));*/
            //改良代码
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                String columnName = rsmd.getColumnName(i);
                Field field = getFieldObject(clazz,columnName);
                if (field!=null){
                    Object columnValue = rs.getObject(columnName);
                    field.setAccessible(true);//破坏封装性,允许访问
                    field.set(student,columnValue);
                }

            }
        }
        System.out.println("student = " + student);
    }
    public static Field getFieldObject(Class clazz,String columnName){
        try {
            return clazz.getDeclaredField(columnName);
        } catch (NoSuchFieldException e) {
            return null;
        }
    }
}

结果:
在这里插入图片描述

BaseDao工具的简单封装(针对SQL单表的基础操作)

package com.yue.util;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * @Author: 鱼
 * @Date: 2021/05/18/19:58
 * @Description:针对SQL单表的基础操作
 * @param <T>:传入要封装成对象的对象
 * @version : 1.0
 * @since 1.8

 */
public class BaseDao<T> {
    private DBHelper db = DBHelper.getInstance();//单例:每次调用都返回相同的对象
    private Class clazz;

    public BaseDao() {
        Type type = this.getClass().getGenericSuperclass();//当前堆内存中实际运行的对象的父类的泛型类型
        ParameterizedType parameterizedType = (ParameterizedType) type;//强转
        Type[] types = parameterizedType.getActualTypeArguments();//获取父类泛型数组
        clazz = (Class) types[0];//赋值泛型中所设类型
    }

    /**
     * 执行变更语句,使用预处理模式
     *
     * @param sql
     * @param params
     */
    public void update(String sql, Object... params) {//可变参数就是数组,数组索引从0开始,sql中索引从1开始
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
            ps.executeQuery();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(clazz.getClass() + "执行变更的SQL语句错误" + e.getMessage());
        } finally {
            db.close(conn,ps);
        }

    }

    /**
     * 查询结果为单条记录,封装成Map类型
     *
     * @param sql
     * @param params
     * @return java.util.Map/null
     */
    public Map<String, Object> queryForMap(String sql, Object... params) {
        Map<String, Object> resultMap = null;
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i+1, params[i]);//预处理赋值
            }
            rs = ps.executeQuery();//获取结果集对象
            if (rs.next()) {
                resultMap = new HashMap<>();//创造存储的容器
                ResultSetMetaData rsmd = rs.getMetaData();//获取元信息
                int columnCount = rsmd.getColumnCount();
                //存储数据
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = rsmd.getColumnLabel(i);
                    Object colunmValue = rs.getObject(columnName);
                    resultMap.put(columnName,colunmValue);
                }
            }
            return resultMap;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(clazz.getClass() + "queryForMap,执行查询的SQL语句错误" + e.getMessage());
        }finally {
            db.close(conn,ps,rs);
        }
    }

    /**
     *封装多条记录
     * @param sql
     * @param params
     * @return List<Map<String,Object>>
     */
    public List<Map<String,Object>> queryForList(String sql,Object...params){
        List<Map<String,Object>> resultList = null;
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);//预处理赋值
            }
            rs = ps.executeQuery();//获取结果集对象
            if (rs.next()) {
                resultList = new ArrayList<>();//创造存储的容器
                ResultSetMetaData rsmd = rs.getMetaData();//获取元信息
                int columnCount = rsmd.getColumnCount();
                do{
                    Map<String,Object> recordMap = new HashMap<>();
                    //存储数据
                    for (int i = 1; i <= columnCount ; i++) {
                        String columnName = rsmd.getColumnLabel(i);
                        Object colunmValue = rs.getObject(columnName);
                        recordMap.put(columnName,colunmValue);
                    }
                    //将每条记录存储list
                    resultList.add(recordMap);
                }while (rs.next());

            }

            return resultList;

        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(clazz.getClass() + "queryForMap,执行查询的SQL语句错误" + e.getMessage());
        }finally {
            db.close(conn,ps,rs);
        }
    }

    /**
     *返回单条记录的自定义对象
     * @param sql
     * @param params
     * @return
     */
    public T queryForRecordObject(String sql, Object... params) {
        T resultDate = null;
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);//预处理赋值
            }
            rs = ps.executeQuery();//获取结果集对象
            if (rs.next()) {
                resultDate = (T) clazz.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();//获取元信息
                int columnCount = rsmd.getColumnCount();
                //存储数据
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = rsmd.getColumnLabel(i);
                    Field field = getFieldObject(columnName);
                    if (field!=null){
                        field.setAccessible(true);//破坏封装性,允许访问
                        Object colunmValue = rs.getObject(columnName);
                        field.set(resultDate,colunmValue);
                    }
                }
            }

            return resultDate;

        } catch (SQLException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException(clazz.getClass() + "queryForObject,执行查询的SQL语句错误" + e.getMessage());
        }finally {
            db.close(conn,ps,rs);
        }
    }
    private Field getFieldObject(String columnName){
        try {
            return clazz.getDeclaredField(columnName);
        } catch (NoSuchFieldException e) {
            return null;
        }
    }

    /**
     * 返回list<T>类型
     * @param sql
     * @param params
     * @return
     */
    public List<T> queryForObjectList(String sql,Object...params){
        List<T> resultList = null;
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i+1,params[i]);//预处理赋值
            }
            rs = ps.executeQuery();//获取ResultSet对象
            if(rs.next()){
                resultList = new ArrayList<>();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                do{
                    T recordData = (T)clazz.newInstance();
                    for(int i=1;i <= columnCount;i++){
                        String columnName = rsmd.getColumnLabel(i);
                        Field field = getFieldObject(columnName);
                        if(field!=null){
                            field.setAccessible(true);
                            Object columnValue = rs.getObject(columnName);//rs.getObject(i);
                            field.set(recordData,columnValue);
                        }
                        resultList.add(recordData);
                    }
                }while (rs.next());

            }
            return resultList;
        } catch (SQLException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();//给程序员使用
            throw new RuntimeException(clazz.getName()+".queryForObjectList,执行查询单条的SQL语句错误:"+e.getMessage());
        }finally {
            db.close(conn,ps,rs);
        }
    }

    /**
     * 返回单条记录
     * @param sql
     * @param params
     * @return
     */
    public Object queryForObject(String sql,Object...params){
        Object resultObject = null;
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i+1,params[i]);//预处理赋值
            }
            rs = ps.executeQuery();//获取ResultSet对象
            if(rs.next()){
                resultObject = rs.getObject(1);
            }
            return resultObject;
        } catch (SQLException e) {
            e.printStackTrace();//给程序员使用
            throw new RuntimeException(clazz.getName()+".queryForObject,执行查询单条的SQL语句错误:"+e.getMessage());
        }finally {
            db.close(conn,ps,rs);
        }
    }

    /**
     * 返回Integer类型的单条记录
     * */
    public Integer queryForInteger(String sql,Object...params){
        Object object = queryForObject(sql,params);
        if(object==null){
            return null;
        }
        return Integer.parseInt(String.valueOf(object));
    }
    /**
     * 返回String类型的单条记录
     * */
    public String queryForString(String sql,Object...params){
        Object object = queryForObject(sql,params);
        if(object==null){
            return null;
        }
        return String.valueOf(object);
    }

    /**
     * 返回类型为string的单列数据
     * @param sql
     * @param params
     * @return
     */
    public List<String> queryForStringList(String sql,Object...params){
        List<String> resultList = null;
        Connection conn = db.getConnection();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i+1,params[i]);//预处理赋值
            }
            rs = ps.executeQuery();//获取ResultSet对象
            if(rs.next()){
                resultList = new ArrayList<>();
                do{
                    resultList.add(rs.getString(1));
                }while (rs.next());
            }
            return resultList;
        } catch (SQLException e) {
            e.printStackTrace();//给程序员使用
            throw new RuntimeException(clazz.getName()+".queryForObject,执行查询单条的SQL语句错误:"+e.getMessage());
        }finally {
            db.close(conn,ps,rs);
        }
    }
}




getInstance的使用

  • 在主函数开始时调用,返回一个实例化对象,此对象是static的,在内存中保留着它的引用,即内存中有一块区域专门用来存放静态方法和变量,
  • 可以直接使用,调用多次返回同一个对象。

一般和private配合使用,使之不可被实例化,只能调用getinstance方法。

事务

概述

在工作中,一定会遇到事务方面的问题,不管任何一种语言都需对其进行处理

数据库中有事务处理。
Java通过JDBC进行事务处理

什么是事务:一个业务逻辑有多个动作组成的操作集合,当全部的操作都是正确的情况下,所有执行才起作用。如果有一个操作错误,之前所有的操作都不起作用。

1.事务处理

错误示例:

package yue.Demo01;

import com.yu.DBHelper;

import java.sql.Connection;
import java.sql.PreparedStatement;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 鱼
 * @Date: 2021/05/20/19:25
 * @Description:
 */
public class A_多个连接_事务情况 {
    public static void main(String[] args) throws Exception {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        String sql1 = "INSERT INTO st(id,student_code) VALUES (?,?)";
        PreparedStatement ps = conn.prepareStatement(sql1);
        ps.setObject(1,"9");
        ps.setObject(2,"juy");
        ps.executeUpdate();//自动提交事务

        String sql2 = "INSERT INTO st(id,student_code) VALUES (?,?)";
        ps = conn.prepareStatement(sql2);
        ps.setObject(1,"8");
        ps.setObject(2,"wyzsdffasdfvs");//长度超出:Data too long for column 'student_code' at row 1
        //自动提交事务
        ps.executeUpdate();//执行动作失败,但是之前添加(9,juy)的数据添加成功,不符合事务要求

    }
}

保证所有事务都作为一个工作单元来执行,即使出现了故障(错误),都不能改变这种执行方式,当一个事务中执行多个操作的时候,要么所有的事务都被提交(commit),要么整个事务回滚数据(rollback)到最状态。

当一个连接对象(Connection)被创建的时候,默认情况下会自动提交事务,每次执行一个SQL语句的时候,如果执行成功,就会向数据库自动提交,而不能回滚数据。

为了让多个SQl语句作为一个事务执行,步骤如下:
  • 调用Connection对象的setAutoCommit(false):取消自动提交事务
  • 在所有执行SQL语句都成功的时候,手动调用commit()方法,提交事务
  • 在出现异常的时候,调用rollback(),方法回滚事务
  • 如果此时Connection没有被关闭,则需要恢复其自动提交的状态

正确代码:

package yue.Demo01;

import com.yu.DBHelper;

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

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 鱼
 * @Date: 2021/05/20/19:25
 * @Description:
 */
public class B_事务处理 {
    public static void main(String[] args) throws Exception {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        try {
            conn.setAutoCommit(false);//1.取消自动提交功能

            String sql1 = "INSERT INTO st(id,student_code) VALUES (?,?)";
            PreparedStatement ps = conn.prepareStatement(sql1);
            ps.setObject(1,"9");
            ps.setObject(2,"juy");
            ps.executeUpdate();

            String sql2 = "INSERT INTO st(id,student_code) VALUES (?,?)";
            ps = conn.prepareStatement(sql2);
            ps.setObject(1,"8");
            ps.setObject(2,"wyzsdffasdfvs");
            ps.executeUpdate();

            conn.commit();//2.手动提交事务
        } catch (SQLException e) {
            conn.rollback();//3.当有错的时候回滚数据
            e.printStackTrace();
        }

    }
}

结果:两个操作皆未成功,数据回滚

2.批量处理JDBC

情景需要:

当我们需要成批插入或者更新记录的时候,我们可以采用Java的批量更新机制,该机制允许多条SQL语句一次性提交给数据库批量处理,通常该情况下比单独提交更有效率

  • JDBC的批量处理语句包括下面两个方法:
  • addBatch(String):添加需要批量处理的SQL语句(Statement)或者参数(PrepareStatement)
  • executeBatc() :执行批量处理语句
  • 通常我们会遇到两种批量处理SQL的情况:
  • 处理多条SQL语句的批量
  • 一个SQL语句有批量的参数

Statement:处理多条SQL

public class C_批量处理语句 {
    public static void main(String[] args) throws Exception {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        try {
            conn.setAutoCommit(false);//取消自动提交功能

            long start = System.currentTimeMillis();
            Statement statement = conn.createStatement();
            //批量处理多条SQL语句
            for(int i=1;i<=1000;i++){
                String sql = "INSERT INTO st(id,student_code) VALUES ('叶凡"+i+"','789')";
                statement.addBatch(sql);
            }
            statement.executeBatch();
            long end = new Date().getTime();

            System.out.println(end-start+"ms");//

        } catch (SQLException e) {
            conn.rollback();//3.回滚数据
            e.printStackTrace();
        }
    }
}

PrepareStatement:处理批量参数

public class D_批量参数 {
    public static void main(String[] args) throws Exception {
        DBHelper dbHelper = new DBHelper();
        Connection conn = dbHelper.getConnection();
        try {
            conn.setAutoCommit(false);//取消自动提交功能

            long start = System.currentTimeMillis();
            String sql = "INSERT INTO userinfo (account,password) VALUES (?,?)";
            PreparedStatement ps = conn.prepareStatement(sql);
            //一条SQL批量处理参数
            for(int i=1;i<=1000;i++){
                ps.setObject(1,"姬明月"+i);
                ps.setObject(2,"789");
                ps.addBatch();

            }
            ps.executeBatch();
            long end = new Date().getTime();

            System.out.println(end-start+"ms");//16256ms

        } catch (SQLException e) {
            conn.rollback();//3.回滚数据
            e.printStackTrace();
        }
    }
}

一般后者效率更快

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月色夜雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值