JDBC学习笔记(全)

概述

数据持久化

  • 持久化(persistence)把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成。
  • 持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中。

image-20200731094037043

java中的数据存储技术

  1. JDBC直接访问数据库
  2. JDO技术
  3. 第三方O/R工具,如Hibernate, mybatis 等

什么是JDBC?

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这个类库可以以一种标准的方法、方便地访问数据库资源
  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
  • 由各种数据库管理系统的开发公司提供JDBC驱动,Java程序员直接使用JDBC连接这些DBMS,大大简化了开发的流程。

image-20200731094912965

JDBC体系结构

  • 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
  • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC API

JDBC API 是一系列的接口,它使得应用程序能够进行数据库联接,执行SQL语句,并且得到返回结果。

image-20200731095224747

JDBC访问数据库的步骤

image-20200731095326656

获取数据库连接

Driver接口

java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现

步骤

  1. 获取Driver实现类对象
  2. 提供要连接的数据库
  3. 提供连接需要的用户名和密码
  4. 获取连接

下面将通过五种方式逐步进阶

方式一

 @Test
    public void test1() throws SQLException {
    
    
        // 获取Driver实现类对象
        Driver driver = new com.mysql.jdbc.Driver();
        // url:http://localhost:8080/gmall/keyboard.jpg
        // jdbc:mysql:协议
        // localhost:ip地址
        // 3306:默认mysql的端口号
        // test:test数据库
        String url = "jdbc:mysql://localhost:3306/test";
        // 将用户名和密码封装在Properties中
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","073838");
        Connection connection = driver.connect(url,info);
        System.out.println(connection);
    }

运行结果:

com.mysql.jdbc.JDBC4Connection@449b2d27

方式一出现了第三方的API,因此引入方式二进行改进,使其有更好的移植性

方式二

public void test2() throws Exception {
    
    
        // 1.获取Driver实现类对象:使用反射
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();
        // 2.提供要连接的数据库
        String url = "jdbc:mysql://localhost:3306/test";
        // 3.提供连接需要的用户名和密码
        Properties info = new Properties();
        info.setProperty("user","root");
        info.setProperty("password","073838");
        // 4.获取连接
        Connection connection = driver.connect(url,info);
        System.out.println(connection);
    }

方式三:使用DriverManager替换Driver

DriverManager 类是驱动程序管理器类,负责管理驱动程序

 public void test3() throws Exception {
    
    
        // 1.获取Driver实现类对象:使用反射
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();
        // 2.提供另外三个连接的基本信息:
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "073838";
        // 注册驱动
        DriverManager.registerDriver(driver);
        // 获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);
    }

通常不用显式调用 DriverManager 类的 registerDriver() 方法来注册驱动程序类的实

例,因为 Driver 接口的驱动程序类包含了静态代码块,在这个静态代码块中,会

调用 DriverManager.registerDriver() 方法来注册自身的一个实例,因此对方式三进行改造

方式四:可以只是加载驱动,不用显式的注册驱动

public void test4() throws Exception {
    
    
        // 1.提供另外三个连接的基本信息:
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "073838";
        // 2.加载Driver
        Class.forName("com.mysql.jdbc.Driver");
        // 3.获取连接
        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);
    }

Driver类加载的静态代码块:

static {
    
    
				try {
    
    
					java.sql.DriverManager.registerDriver(new Driver());
				} catch (SQLException E) {
    
    
					throw new RuntimeException("Can't register driver!");
				}
	   }

方式四还暴露了账户名和密码,对次进行最后的改进:将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接

方式五

配置文件:jdbc.propertoes(注意不要加空格)

user=root
password=073838
url=jdbc:mysql://localhost:3306/test
driver=com.mysql.jdbc.Driver

具体实现:

public void test5() throws Exception {
    
    
        //1.读取配置文件中的4个基本信息
        InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driver");
        //2.加载驱动
        Class.forName("driver");
        //3.获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);

    }

该方式的好处:

1.实现了数据与代码的分离。实现了解耦

2.如果需要修改配置文件信息,可以避免程序重新打包。

使用Statement操作数据库的弊端

​ 获取数据库的连接后,我们可以对数据库进行增删改查操作(CRUD),在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:

Statement

​ ⇡PreparedStatement

​ ⇡CallableStatement

一般我们不使用Statement来访问数据库,因为会发生SQL注入的问题,下面通过例子来演示SQL注入问题

public void testLogin() {
    
    
		Scanner scanner = new Scanner(System.in);
		
		System.out.print("请输入用户名:");
		String user = scanner.nextLine();
		System.out.print("请输入密码:");
		String password = scanner.nextLine();
		//SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'
		String sql = "SELECT user,password FROM user_table WHERE user = '"+ user +"' AND password = '"+ password +"'";
		User returnUser = get(sql,User.class);
		if(returnUser != null){
    
    
			System.out.println("登录成功");
		}else{
    
    
			System.out.println("用户名不存在或密码错误");
		}
	}

当我们输入的用户名和密码非法时,可能不会被检测到,但却能对数据库进行访问进行非法操作。

  • SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法

  • 对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了

使用PreparedStatement进行CRUD操作

提供整个过程获取数据库连接、关闭资源的工具类

package com.deserts.util;

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

public class JDBCUtils {
    
    
    /**
     * 获取数据库的连接
     * @return Connection
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
    
    
        // 1.读取配置文件中的4个基本信息
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(is);
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        String driverClass = properties.getProperty("driver");
        // 2.加载驱动
        Class.forName(driverClass);
        //3.获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        return connection;
    }

    /**
     * 关闭资源的操作
     * @param connection
     * @param ps
     */
    public static void closeResource(Connection connection, PreparedStatement ps) {
    
    
        try {
    
    
            if(ps != null)
                ps.close();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
        try {
    
    
            if(connection != null)
                connection.close();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 关闭资源的操作
     * @param connection
     * @param ps
     * @param resultSet
     */
    public static void closeResource(Connection connection, PreparedStatement ps, ResultSet resultSet) {
    
    
        try {
    
    
            if(ps != null)
                ps.close();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
        try {
    
    
            if(connection != null)
                connection.close();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
        try {
    
    
            if(resultSet != null)
                resultSet.close();
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        }
    }
}

通用的增删改操作

主要思想:

获取数据库连接→预编译sql语句,获取PrepareStatement的实例ps→根据传入的可变参数填充占位符→调用ps.excute()执行→关闭资源

package com.deserts.crud;

import com.deserts.util.JDBCUtils;
import org.junit.Test;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class PreparedStatementUpdateTest {
    
    

    //封装通用的增删改操作
    private void update(String sql, Object ...args) {
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        try {
    
    
            //1.获取数据库连接
            connection = JDBCUtils.getConnection();
            //2.预编译sql语句,获取PrepareStatement的实例
            ps = connection.prepareStatement(sql);
            //3.填充占位符
            for (int i = 0; i < args.length ; i++) {
    
    
                ps.setObject(i+1,args[i]);
            }
            //4.执行
            ps.execute();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //5.关闭资源
            JDBCUtils.closeResource(connection, ps);
        }
    }

    //修改
    @Test
    public void test1(){
    
    
        String sql = "update `order` set order_name = ? where order_id = ?";
        update(sql,"MM",2);
    }

    //插入
    @Test
    public void test2() throws ParseException {
    
    
        String sql = "insert into `order` values(?,?,?)";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        java.util.Date date = sdf.parse("2000-01-01");
        update(sql,5,"PP",new Date(date.getTime()));
    }

    //删除
    @Test
    public void test3(){
    
    
        String sql = "delete from `order` where order_id = ?";
        update(sql,5);
    }


}

对Custmoer表的PreparedStatement查询操作

说明:本过程需要提供Customer对象,用于保存从表中取出的数据

对表中单个对象(一列)的特定查询操作测试

//对单个查询的操作测试
    @Test
    public void queryTest1(){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
    
    
            //1.获取数据库连接
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id = ?";
            //2.预编译sql语句
            ps = connection.prepareStatement(sql);
            ps.setObject(1,5);
            //3.返回结果集
            resultSet = ps.executeQuery();
            //4.处理结果集
            if (resultSet.next()){
    
    
                //获取当前这条数据的各个字段值
                int id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String email = resultSet.getString(3);
                Date birth = resultSet.getDate(4);
                //将数据封装为对象
                Customer customer = new Customer(id, name, email, birth);
                System.out.println(customer);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //关闭资源
            JDBCUtils.closeResource(connection,ps,resultSet);
        }
    }

查询结果:

image-20200801090126932

对表中单个对象(一列)的通用查询操作测试

主要思想:

获取数据库连接→预编译sql语句,获取PrepareStatement的实例ps→根据传入的可变参数填充占位符→调用ps.excuteQuery(),获取结果集→根据结果集获取结果集的元数据→处理结果集→关闭资源

处理结果集的思路:

由结果集获取列值getObject()→由元数据获取列名getCulumnName()→通过反射给指定对象赋值→返回对象

package com.deserts.crud1;

import com.deserts.bean.Customer;
import com.deserts.util.JDBCUtils;
import org.junit.Test;

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

public class CustomerForQuery {
    
    
    /**
     * 针对Customer表一个对象的通用查询操作
     * @param sql
     * @param args
     * @return
     */
    public Customer queryForCustomer(String sql, Object...args){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
    
    
            //1.获取数据库连接
            connection = JDBCUtils.getConnection();
            //2.预编译sql语句
            ps = connection.prepareStatement(sql);
            //3.填充占位符,返回结果集
            for(int i = 0;i < args.length;i++){
    
    
                ps.setObject(i + 1, args[i]);
            }
            resultSet = ps.executeQuery();
            //4.获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData metaData = resultSet.getMetaData();
            //5.通过ResultSetMetaData获取结果集中的列数
            int columnCount = metaData.getColumnCount();
            //6.处理结果集
            if (resultSet.next()){
    
    
                Customer customer = new Customer();
                for (int i = 0; i < columnCount ; i++) {
    
    
                    //获取列值
                    Object columnValue = resultSet.getObject(i + 1);
                    //获取每个列的列名
                    String columnName = metaData.getColumnName(i + 1);
                    //给customer对象指定的columnName属性,赋值为columnValue:通过反射
                    Field field = customer.getClass().getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(customer,columnValue);
                }
                return customer;
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(connection,ps,resultSet);
        }
        return null;
    }

    //测试通用查询操作
    @Test
    public void queryTest2(){
    
    
        String sql = "select id,name,email,birth from customers where id = ?";
        Customer customer = queryForCustomer(sql,5);
        System.out.println(customer);
        sql = "select id,name,email from customers where id = ?";
        Customer customer1 = queryForCustomer(sql, 10);
        System.out.println(customer1);
    }
}

查询结果:

image-20200801090409714

对Order表的PreparedStatement查询操作

说明:本过程同样需要提供Order对象

主要思想:

该过程的思想与查询Customer表一致,需要注意的是,Order对象的属性名与order表的列名并不相同,这时需要在查询过程SQL语句中给列名取别名,而获取列的别名用的方法是getColumnTable()(没有别名时返回列名)

package com.deserts.crud1;

import com.deserts.bean.Order;
import com.deserts.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;

public class OrderForQuery {
    
    
    private ArrayList<Order> orderQuery(String sql, Object...args){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
    
    
            ArrayList<Order> list = new ArrayList<>();
            connection = JDBCUtils.getConnection();
            ps = connection.prepareStatement(sql);
            for (int i = 0; i < args.length ; i++) {
    
    
                ps.setObject(i+1,args[i]);
            }
            resultSet = ps.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (resultSet.next()){
    
    
                Order order = new Order();
                for (int i = 0; i < columnCount; i++) {
    
    
                    Object columnValue = resultSet.getObject(i + 1);
                    String columnLabel = metaData.getColumnLabel(i+1);
                    Field field = Order.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(order, columnValue);
                }
                list.add(order);
            }
            return list;
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(connection,ps,resultSet);
        }
        return null;
    }

    @Test
    public void orderQueryTest(){
    
    
        String sql = "select order_id orderId,order_name orderName from `order` where order_id < ?";
        ArrayList<Order> orders = orderQuery(sql, 5);
        orders.forEach(System.out::println);
    }
}

查询结果:

image-20200801092643595

对不同表的通用查询操作

由上面两个张表的查询,我们可以总结出对不同表的通用查询操作,其中查询多条记录只需将处理结果集的if(resultSet.next())改成while(resultSet.next())即可

package com.deserts.crud2;

import com.deserts.bean.Customer;
import com.deserts.bean.Order;
import com.deserts.util.JDBCUtils;
import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;

public class PreparedStatementQueryTest {
    private <T> ArrayList<T> simpleQuery(Class<T> clazz,String sql, Object...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            ArrayList<T> list = new ArrayList<>();
            connection = JDBCUtils.getConnection();
            ps = connection.prepareStatement(sql);
            for (int i = 0; i < args.length ; i++) {
                ps.setObject(i+1,args[i]);
            }
            resultSet = ps.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            while (resultSet.next()){
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    Object columnValue = resultSet.getObject(i + 1);
                    String columnLabel = metaData.getColumnLabel(i+1);
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,resultSet);
        }
        return null;
    }

    @Test
    public void queryTest(){
        String sql = "select id,name,email from customers where id < ?";
        ArrayList<Customer> customers = simpleQuery(Customer.class, sql, 10);
        customers.forEach(System.out::println);
        System.out.println();
        sql = "select order_id orderId,order_name orderName from `order` where order_id < ?";
        ArrayList<Order> orders = simpleQuery(Order.class, sql, 5);
        orders.forEach(System.out::println);
    }
}

查询结果:

image-20200801093630750

查询过程图解

image-20200801093909506

两种思想、两种技术

两种思想:

1.面向接口编程思想

2.ORM编程思想

两种技术:

1.JDBC元数据:ResultSetMetaData

2.通过反射,获取指定的属性,并赋值

Java与SQL数据类型对应表

image-20200802151920830

操作BLOB类型字段

BLOB类型

插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

MySQL中BLOB的四种类型:

image-20200802152046322

注意点:

如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

使用PreparedStatement插入和查询BLOB类型字段

package com.deserts.bolbtest;

import com.deserts.util.JDBCUtils;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;

public class Blobtest1 {
    
    
    //像表中插入含有blob类型字段的记录
    @Test
    public void test1() throws Exception {
    
    
        Connection connection = JDBCUtils.getConnection();
        String sql = "insert into customers (name,email,birth,photo)values(?,?,?,?)";
        PreparedStatement ps = connection.prepareStatement(sql);
        ps.setObject(1,"deserts");
        ps.setObject(2,"[email protected]");
        ps.setObject(3,"1986-5-30");
        InputStream is = new FileInputStream("E:\\IDEA Projects\\deserts\\deserts.jpg");
        ps.setBlob(4,is);
        ps.execute();
        JDBCUtils.closeResource(connection,ps);
        System.out.println("插入成功!");
    }
    //查询表中含有blob字段的记录
    @Test
    public void test2(){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        InputStream is = null;
        FileOutputStream fos = null;
        try {
    
    
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth,photo from customers where id = ?";
            ps = connection.prepareStatement(sql);
            ps.setInt(1,19);
            resultSet = ps.executeQuery();
            is = null;
            fos = null;
            if (resultSet.next()) {
    
    
                int id = resultSet.getInt("id");
                String name = resultSet.getString("name");
                String email = resultSet.getString("email");
                Date birth = resultSet.getDate("birth");
                Customer customer = new Customer(id,name,email,birth);
                System.out.println(customer);
                Blob photo = resultSet.getBlob("photo");
                is = photo.getBinaryStream();
                fos = new FileOutputStream("deserts1.jpg");
                byte[] buffer = new byte[1024];
                int len;
                while ((len = is.read(buffer)) != -1){
    
    
                    fos.write(buffer,0,len);
                }

            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(connection,ps,resultSet);
            if (is != null) {
    
    
                try {
    
    
                    is.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }

            if (fos != null) {
    
    
                try {
    
    
                    fos.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }
}

使用PreparedStatement批量插入

批量插入用到的方法

  • addBatch(String):添加需要批量处理的SQL语句或是参数;

  • executeBatch():执行批量处理语句;

  • clearBatch():清空缓存的数据

高效进行批量插入

方式一:使用Statement进行批量插入,效率过低

方式二:使用Preparedstament进行批量插入

 @Test
    public void test2(){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        try {
    
    
            long start = System.currentTimeMillis();
            connection = JDBCUtils.getConnection();
            String sql = "insert into goods(id,name) values(?,?)";
            ps = connection.prepareStatement(sql);
            for (int i = 1; i <= 20000; i++) {
    
    
                ps.setInt(1,i);
                ps.setString(2,"name_" + i);
                ps.execute();
            }

            long end = System.currentTimeMillis();
            System.out.println("花费的时间:" + (end - start));//718571
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(connection,ps);
        }
        

    }

花费时间为:718571毫秒

方式三:使用批量插入相关方法进行优化

@Test
    public void test3(){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        try {
    
    
            long start = System.currentTimeMillis();
            connection = JDBCUtils.getConnection();
            String sql = "insert into goods(id,name) values(?,?)";
            ps = connection.prepareStatement(sql);
            for (int i = 1; i <= 20000; i++) {
    
    
                ps.setInt(1,i);
                ps.setString(2,"name_"+i);
                ps.addBatch();
                if (i % 500 == 0){
    
    
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }

            long end = System.currentTimeMillis();
            System.out.println("花费的时间:" + (end - start));//3260
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(connection,ps);
        }

    }

花费的时间:3260毫秒

方式四:设置事务,不自动提交事务

@Test
    public void test4(){
    
    
        Connection connection = null;
        PreparedStatement ps = null;
        try {
    
    
            long start = System.currentTimeMillis();
            connection = JDBCUtils.getConnection();
            connection.setAutoCommit(false);
            String sql = "insert into goods(id,name) values(?,?)";
            ps = connection.prepareStatement(sql);
            for (int i = 1; i <= 20000; i++) {
    
    
                ps.setInt(1,i);
                ps.setString(2,"name_"+i);
                ps.addBatch();
                if (i % 500 == 0){
    
    
                    ps.executeBatch();
                    ps.clearBatch();
                }
            }
            connection.commit();
            long end = System.currentTimeMillis();
            System.out.println("花费的时间:" + (end - start));//2213
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(connection,ps);
        }

    }

花费的时间:2213毫秒

综上,我们采用方式四:使用批量提交相关方法,并设置事务不自动提交

java中使用事务

数据自动提交的情况

  • DDL操作一旦执行,都会自动提交;set autocommit = false 对DDL操作失效
  • DML默认情况下,一旦执行,就会自动提交。我们可以通过set autocommit = false的方式取消DML操作的自动提交。
  • 默认在关闭连接时,会自动的提交数据

Java中控制事务用到的方法

  • 获取当前连接的隔离级别:connection.getTransactionIsolation()
  • 设置数据库的隔离级别:Connection.TRANSACTION_READ_COMMITTED
  • 取消自动提交数据: connection.setAutoCommit(false)

考虑事务后的增删改查

通用的增删改操作—version 2.0 (考虑上事务)

 public int update(Connection connection, String sql, Object... args){
    
    
        PreparedStatement ps = null;
        try {
    
    
            ps = connection.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
    
    
                ps.setObject(i+1,args[i]);
            }
            return ps.executeUpdate();
        } catch ( Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(null,ps);
        }
        return 0;
    }

通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)

  public <T> T getInstance(Connection connection,Class<T> clazz,String sql, Object... args) throws Exception {
    
    
        PreparedStatement ps = null;
        try {
    
    
            ps = connection.prepareStatement(sql);
            ResultSet resultSet = ps.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            if (resultSet.next()) {
    
    
                T t = clazz.newInstance();
                for (int i = 0; i < columnCount; i++) {
    
    
                    Object columnValue = resultSet.getObject(i + 1);
                    String name = metaData.getColumnLabel(i + 1);
                    Field field = clazz.getDeclaredField(name);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(null,ps);
        }
        return null;
    }

DAO及其相关实现类

BaseDAO

package com.deserts.dao;

import com.deserts.util.JDBCUtils;

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.List;

public abstract class BaseDAO<T> {
    
    
    private Class<T> clazz = null;

    public BaseDAO() {
    
    
        //获取当前BaseDAO的子类继承的父类中的泛型
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) genericSuperclass;

        Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数
        clazz = (Class<T>) typeArguments[0];//泛型的第一个参数
    }

    // 通用的增删改操作---version 2.0 (考虑上事务)
    public int update(Connection conn, String sql, Object... args) {
    
    // sql中占位符的个数与可变形参的长度相同!
        PreparedStatement ps = null;
        try {
    
    
            // 1.预编译sql语句,返回PreparedStatement的实例
            ps = conn.prepareStatement(sql);
            // 2.填充占位符
            for (int i = 0; i < args.length; i++) {
    
    
                ps.setObject(i + 1, args[i]);// 小心参数声明错误!!
            }
            // 3.执行
            return ps.executeUpdate();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 4.资源的关闭
            JDBCUtils.closeResource(null, ps);

        }
        return 0;

    }

    // 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
    public T getInstance(Connection conn, String sql, Object... args) {
    
    
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
    
    

            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
    
    
                ps.setObject(i + 1, args[i]);
            }

            rs = ps.executeQuery();
            // 获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            // 通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            if (rs.next()) {
    
    
                T t = clazz.newInstance();
                // 处理结果集一行数据中的每一个列
                for (int i = 0; i < columnCount; i++) {
    
    
                    // 获取列值
                    Object columValue = rs.getObject(i + 1);

                    // 获取每个列的列名
                    // String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    // 给t对象指定的columnName属性,赋值为columValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
                return t;
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(null, ps, rs);

        }

        return null;
    }

    // 通用的查询操作,用于返回数据表中的多条记录构成的集合(version 2.0:考虑上事务)
    public List<T> getForList(Connection conn, String sql, Object... args) {
    
    
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
    
    

            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
    
    
                ps.setObject(i + 1, args[i]);
            }

            rs = ps.executeQuery();
            // 获取结果集的元数据 :ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            // 通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();
            // 创建集合对象
            ArrayList<T> list = new ArrayList<T>();
            while (rs.next()) {
    
    
                T t = clazz.newInstance();
                // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
                for (int i = 0; i < columnCount; i++) {
    
    
                    // 获取列值
                    Object columValue = rs.getObject(i + 1);

                    // 获取每个列的列名
                    // String columnName = rsmd.getColumnName(i + 1);
                    String columnLabel = rsmd.getColumnLabel(i + 1);

                    // 给t对象指定的columnName属性,赋值为columValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t, columValue);
                }
                list.add(t);
            }

            return list;
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(null, ps, rs);

        }

        return null;
    }

    //用于查询特殊值的通用的方法
    public <E> E getValue(Connection conn, String sql, Object... args) {
    
    
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
    
    
            ps = conn.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
    
    
                ps.setObject(i + 1, args[i]);

            }

            rs = ps.executeQuery();
            if (rs.next()) {
    
    
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            JDBCUtils.closeResource(null, ps, rs);

        }
        return null;

    }
}

CustomerDAO

package com.deserts.dao;

import com.deserts.bean.Customer;

import java.sql.Connection;
import java.sql.Date;
import java.util.List;

/**
 * @ClassName CustomerDAO
 * @Description 此接口用于规范针对于customers表的常用操作
 * @Author deserts
 * @Date 2020/8/2 16:53
 */
public interface CustomerDAO {
    
    
    /**
    * @Description 将customer对象添加到数据库中
    * @Date 2020/8/2 16:59
    * [connection, customer]
    * @return void
    **/
    void insert(Connection connection, Customer customer);

    /**
    * @Description // 针对指定的id,删除表中的一条记录
    * @Date 2020/8/2 17:00
    * [connection, id] 
    * @return void
    **/
    void deleteById(Connection connection,int id);

    /**
    * @Description //针对内存中的customer对象,去修改数据表中指定的记录
    * @Date 2020/8/2 17:01
    * [connection, customer] 
    * @return void
    **/
    void update(Connection connection,Customer customer);

    /**
    * @Description //查询表中指定id的customer对象
    * @Date 2020/8/2 17:01
    * [connection, id]
    * @return com.deserts.bean.Customer
    **/
    Customer getCustomerById(Connection connection,int id);

    /**
    * @Description //获取表中所有customer对象
    * @Date 2020/8/2 17:02
    * [connection]
    * @return java.util.List<com.deserts.bean.Customer>
    **/
    List<Customer> getAll(Connection connection);

    /**
    * @Description //返回表中customer对象的数目
    * @Date 2020/8/2 17:03
    * [connection]
    * @return java.lang.Long
    **/
    Long getCount(Connection connection);

    /**
    * @Description //获取最大生日
    * @Date 2020/8/2 17:04
    * [connection]
    * @return java.sql.Date
    **/
    Date getMaxBirth(Connection connection);
}

CustomerDAOImpl

package com.deserts.dao;

import com.deserts.bean.Customer;

import java.sql.Connection;
import java.sql.Date;
import java.util.List;

/**
 * @ClassName CustomerDAOImpl
 * @Description CustomerDAO的实现类,用于操作customers表
 * @Author deserts
 * @Date 2020/8/2 17:05
 */
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO{
    
    
    @Override
    public void insert(Connection connection, Customer customer) {
    
    
        String sql = "insert into customers(name,email,birth) values(?,?,?)";
        update(connection,sql,customer.getName(),customer.getEmail(),customer.getBirth());
    }

    @Override
    public void deleteById(Connection connection, int id) {
    
    
        String sql = "delete from customers where id = ?";
        update(connection,sql,id);
    }

    @Override
    public void update(Connection connection, Customer customer) {
    
    
        String sql = "update customers set name = ?,email = ?, birth = ? where id = ?";
        update(connection,sql,customer.getName(),customer.getEmail(),customer.getBirth(),customer.getId());
    }

    @Override
    public Customer getCustomerById(Connection connection, int id) {
    
    
        String sql = "select id,name,email,birth from customers where id = ?";
        return getInstance(connection, sql, id);
    }

    @Override
    public List<Customer> getAll(Connection connection) {
    
    
        String sql = "select id,name,email,birth from customers";
        return getForList(connection,sql);
    }

    @Override
    public Long getCount(Connection connection) {
    
    
        String sql = "select count(*) from customers";
        return getValue(connection,sql);
    }

    @Override
    public Date getMaxBirth(Connection connection) {
    
    
        String sql = "select max(birth) from customers";
        return getValue(connection, sql);

    }
}

数据库连接池

druid数据库连接池(重要)

 public Connection getConnection() throws Exception {
    
    

        Properties properties = new Properties();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        properties.load(is);
        DataSource source = DruidDataSourceFactory.createDataSource(properties);
        Connection connection = source.getConnection();
        return connection;

    }

C3P0数据库连接池

 public Connection getConnection() throws SQLException {
    
    
        ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");
        Connection conn = cpds.getConnection();
        return conn;
    }

DBCP数据库连接池

public Connection getConnection() throws Exception {
    
    

        Properties properties = new Properties();
        FileInputStream is = new FileInputStream(new File("src\\dbcp.properties"));
        properties.load(is);
        DataSource source = BasicDataSourceFactory.createDataSource(properties);
        Connection connection = source.getConnection();
        return connection;

    }

注意点:若要将这三种方式添加到工具类中使用,应将线程池声明到静态方法外,并用静态代码块进行初始化

Apache-DBUtils实现CRUD操作

Apache-DBUtils简介

  • commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
  • API介绍:
    • org.apache.commons.dbutils.QueryRunner
    • org.apache.commons.dbutils.ResultSetHandler
    • 工具类:org.apache.commons.dbutils.DbUtils

DBUtils类关闭资源

image-20200803005928937

增删改

以插入为例:

@Test
    public void insertTest(){
    
    
        Connection connection = null;
        try {
    
    
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "insert into customers(name,email,birth)values(?,?,?)";
            int update = runner.update(connection, sql, "lala", "[email protected]", "1995-01-01");
            System.out.println("插入了" + update + "条记录!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DbUtils.closeQuietly(connection);
        }
    }

查询

ResultSetHandler接口及实现类

  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

  • ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。

  • 主要实现类:

    • ArrayHandler:把结果集中的第一行数据转成对象数组。

    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。

    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。

    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。

    • ColumnListHandler:将结果集中某一列的数据存放到List中。

    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map

      里,其key为指定的key。

    • **MapHandler:**将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。

    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List。

    • ScalarHandler:查询单个值对象

查询单条记录

 @Test
    public void queryTest1(){
    
    
        Connection connection = null;
        try {
    
    
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id = ?";
            BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
            Customer customer = runner.query(connection, sql, handler, 19);
            System.out.println(customer);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DbUtils.closeQuietly(connection);
        }
    }

查询多条记录

 @Test
    public void queryTest2(){
    
    
        Connection connection = null;
        try {
    
    
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id < ?";
            BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
            List<Customer> list = runner.query(connection, sql, handler, 10);
            list.forEach(System.out::println);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DbUtils.closeQuietly(connection);
        }
    }

查询特殊值

@Test
    public void queryTest3(){
    
    
        Connection conn = null;
        try {
    
    
            QueryRunner runner = new QueryRunner();
            conn = JDBCUtils.getConnection();
            String sql = "select count(*) from customers";
            ScalarHandler handler = new ScalarHandler();
            long count = (long)runner.query(conn, sql, handler);
            System.out.println("总记录数:"+count);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DbUtils.closeQuietly(conn);
        }
    }

dler, 19);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtils.closeQuietly(connection);
}
}


#### 查询多条记录

```java
 @Test
    public void queryTest2(){
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnection();
            String sql = "select id,name,email,birth from customers where id < ?";
            BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
            List<Customer> list = runner.query(connection, sql, handler, 10);
            list.forEach(System.out::println);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            DbUtils.closeQuietly(connection);
        }
    }

查询特殊值

@Test
    public void queryTest3(){
    
    
        Connection conn = null;
        try {
    
    
            QueryRunner runner = new QueryRunner();
            conn = JDBCUtils.getConnection();
            String sql = "select count(*) from customers";
            ScalarHandler handler = new ScalarHandler();
            long count = (long)runner.query(conn, sql, handler);
            System.out.println("总记录数:"+count);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            DbUtils.closeQuietly(conn);
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_45834777/article/details/107755033
今日推荐