JDBC学习笔记

JDBC学习笔记

1 JDBC概述

1.1 数据的持久化

把数据保存保存到可掉电式存储设备中以供以后使用。将内存中的数据保存到硬盘上,持久化的实现过程大多通过各种关系型数据库来完成。

1.2 JDBC介绍

JDBC是一个独立于特定数据库管理系统、通用的SQL数据库存存取和操作的公共接口

1.3 JDBC体系结构

  • JDBC接口包括两个层次
    • 面向应用的API:抽象接口,供应用程序开发人员使用
    • 面向数据库的API:供开发商开发数据库驱动使用

1.4 JDBC编写步骤

  1. 导入java.sql包
  2. 安装驱动
  3. 创建Connection对象
  4. 创建Statement对象
  5. 执行SQL语句
  6. 关闭连接

2 获取数据库连接

2.1 Driver接口实现类

创建lib文件夹 导入jar包 然后 点击Add as library

获取数据库的连接方式一:

public class ConnectionTest {

    //方式一 :
    @Test
    public void testConnection1()  throws SQLException {

        Driver driver = new com.mysql.jdbc.Driver();

        //jdbc:mysql:协议
        //localhost:ip地址
        //3306:默认mysql的端口号
        //test:数据库名
        String url = "jdbc:mysql://localhost:3306/test";
        //将用户名和密码封装在Properties中
        Properties info = new Properties();
        info.setProperty("user", "root");
        info.setProperty("password","abc123");
        Connection conn = driver.connect(url,info);

    System.out.println(conn);
    }
}

获取数据库的连接方式二:

//方式二: 对方式一的迭代:不出现第三方API ,使程序有更好的可移植性
    @Test
    public void testConnection2() 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","abc123");
        //4 获取连接
        Connection conn = driver.connect(url,info);

        System.out.println(conn);
    }

获取数据库的连接方式三:

//方式三:DriverManager替换Driver
    @Test
    public void testConnection3() 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 = "abc123";
        //注册驱动
        DriverManager.registerDriver(driver);

        //获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);

    }

获取数据库的连接方式四:

//方式四:省略了实例化和注册驱动,源码有静态代码自动注册
    @Test
    public void testConnection4() throws Exception {
        //1 获取连接基本信息
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "abc123";

        //2 加载Driver
        Class.forName("com.mysql.jdbc.Driver");

        //3 获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);

    }

获取数据库的连接方式五:

创建properties文件


//方式五:将数据库连接需要的基本信息声明在配置文件中,通过读取配置文件方式获得连接
    @Test
    public void testConnection5() throws Exception {

        //1 获取连接基本信息
        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("driverClass");

        //2 加载驱动
        Class.forName(driverClass);

        //3 获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);
    }

/*
1.实现数据和代码的分离、解耦
2.如果需要修改配置信息,可以避免程序重新打包
*/

3 使用PreparedStatement实现CRUD操作

使用Statement的弊端:需要拼写sql语句,并且存在sql注入问题

使用PreparedStatement实现CRUD操作

/**
 * PreparedStatement来替换Statement实现数据表的增删改查操作
 * @author hjx
 * @create 2023-03-07 12:41
 */
public class PreparedStatementUpdateTest {
    //向Customer表中添加一条记录
    @Test
    public void testInsert(){
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1 获取连接基本信息
            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("driverClass");

            //2 加载驱动
            Class.forName(driverClass);

            //3 获取连接
            connection = DriverManager.getConnection(url, user, password);
            System.out.println(connection);

            //4 预编译sql语句 返回PreparedStatement实例
            String sql = "insert into  customers(name,email,birth)values(?,?,?)";//占位符
            ps = connection.prepareStatement(sql);
            //5 填充占位符
            ps.setString(1,"哪吒");
            ps.setString(2,"nezhah@gmail.com");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date date = sdf.parse("1000-01-01");
            ps.setDate(3,new Date(date.getTime()));

            //6 执行sql
            ps.execute();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        } finally {
            //7 资源关闭
            try {
                if (ps != null)
                    ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            try {
                if (connection != null)
                    connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }



    }


}

编写JDBC工具类:

public class JDBCUtils {
    public static Connection getConnection() throws Exception {
        //1 获取连接基本信息
        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("driverClass");

        //2 加载驱动
        Class.forName(driverClass);

        //3 获取连接
        Connection connection = DriverManager.getConnection(url, user, password);
        System.out.println(connection);
        return connection;
    }

    public static void closeResource(Connection connection, Statement ps){
        //7 资源关闭
        try {
            if (ps != null)
                ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (connection != null)
                connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

实现修改操作:

    //修改记录
    @Test
    public void testUpdate(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1 获取数据库连接
            conn = JDBCUtils.getConnection();
            //2 预编译sql语句 返回PreparedStatement实例
            String sql = "update customers set name = ? where id = ?";
            ps = conn.prepareStatement(sql);
            //3 填充占位符
            ps.setObject(1,"莫扎特");
            ps.setObject(2,18);
            //4 执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //5 资源关闭
            JDBCUtils.closeResource(conn,ps);
        }

    }

实现通用的增删改操作:

    @Test
    public void update(String sql, Object...args){//sql占位符的个数与可变形参长度一致
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            //1 获取数据库连接
            connection = JDBCUtils.getConnection();
            //2 预编译sql语句 返回PreparedStatement实例
            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 testCommonUpdate(){
        String sql = "delete from customers where id = ? ";
        update(sql,3);
    }

实现查询操作:

public class CustomerForQuery {
    @Test
    public void testQuery1(){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "SELECT id,name,email,birth from customers where id = ? ";
            ps = connection.prepareStatement(sql);
            ps.setObject(1,1);
            //执行返回结果集
            resultSet = ps.executeQuery();
            //处理结果集
            if (resultSet.next()){//判断结果集是否有数据,有true,指针下移
                //获取当前数据各个字段值
                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);
        }


    }
}

实现一个Customer类接收结果

/**
 * ORM 对象关系映射
 * 一个数据表对应一个java类
 * 一个java类的一个对象
 * 表中一个字段对应类的一个属性
 * @author hjx
 * @create 2023-03-07 15:25
 */
public class Customer {
    private int id;
    private String name;
    private String email;
    private Date birth;

    public Customer() {
    }

    public Customer(int id, String name, String email, Date birth) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.birth = birth;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

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

    public Date getBirth() {
        return birth;
    }

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", birth=" + birth +
                '}';
    }
}

实现通用的查询操作:

public Customer queryForCustomers(String sql,Object...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = JDBCUtils.getConnection();

            ps = connection.prepareStatement(sql);

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

            rs = ps.executeQuery();
            //获取结果集元数据,
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()){
                Customer cust = new Customer();
                //处理一行数据的每一个列
                for (int i = 0; i <columnCount ; i++) {
                    //获取列值
                    Object clolumnValue = rs.getObject(i + 1);

                    //获取每个列的列名
                    String columnName = rsmd.getColumnName(i + 1);
                    //给cust对象指定的columnName属性赋值为clolumnValue---反射
                    Field field = Customer.class.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(cust,clolumnValue);
                }
                return cust;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
        return null;
    }

    @Test
    public void testqueryForCustomers(){
        String sql="select id,name,email,birth from customers where id = ? ";
        Customer customer = queryForCustomers(sql, 1);
        System.out.println(customer);
    }

实现不同表通用的查询操作(泛型–所有表通用):

public class PrepareStatementQueryTest {

    @Test
    public void testGetInstance(){
        String sql = "SELECT id,name,email,birth from customers where id = ? ";
        Customer customer = getInstance(Customer.class, sql, 12);
        System.out.println(customer);
    }
    public <T> T getInstance(Class<T> clazz,String sql,Object...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = JDBCUtils.getConnection();

            ps = connection.prepareStatement(sql);

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

            rs = ps.executeQuery();
            //获取结果集元数据,
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()){
                T t = clazz.newInstance();
                //处理一行数据的每一个列
                for (int i = 0; i <columnCount ; i++) {
                    //获取列值
                    Object clolumnValue = rs.getObject(i + 1);

                    //获取每个列的列名 getColumnLabel 别名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为clolumnValue---反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,clolumnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
        return null;
    }
}

实现不同表通用的查询操作(泛型–所有表通用),返回多个结果:

@Test
    public void testGetForList(){
        String sql = "SELECT id,name,email,birth from customers where id < ? ";
        List<Customer> list = getForList(Customer.class, sql, 12);
        list.forEach(System.out::println);
    }

    public <T> List<T> getForList(Class<T> clazz, String sql, Object...args){
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            connection = JDBCUtils.getConnection();

            ps = connection.prepareStatement(sql);

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

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

                    //获取每个列的列名 getColumnLabel 别名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为clolumnValue---反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,clolumnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps,rs);
        }
        return null;
    }

PreparedStatement预编译SQL语句可以解决SQL注入,可以操作Blob的数据,可以实现更高的批量操作

4 使用PreparedStatement操作Blob类型数据

向数据表插入Blob类型数据:

public class BlobTest {
    //向Customers中插入Blob类型字段
    @Test
    public void testInsert() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        String sql ="insert into customers(name,email,birth,photo)values(?,?,?,?)";
        PreparedStatement ps = connection.prepareStatement(sql);

        ps.setObject(1,"蜘蛛侠");
        ps.setObject(2,"hong@qq.com");
        ps.setObject(3,"1999-07-15");
        FileInputStream is = new FileInputStream(new File("zdx.png"));
        ps.setBlob(4,is);

        ps.execute();

        JDBCUtils.closeResource(connection,ps);
    }
}

向数据表读取Blob类型数据:

//查询Customers中Blob类型字段
    @Test
    public void testQuery() throws Exception {
        Connection connection = JDBCUtils.getConnection();
        String sql = "select id,name,email,birth,photo from customers where id = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        ps.setInt(1,20);
        InputStream is = null;
        FileOutputStream fos = null;
        ResultSet rs = ps.executeQuery();
        if (rs.next()){

            int id = rs.getInt("id");
            String name = rs.getString("name");
            String email = rs.getString("email");
            Date birth = rs.getDate("birth");

            Customer cust = new Customer(id,name,email,birth);
            System.out.println(cust);

            //下载,保存本地文件
            Blob photo = rs.getBlob("photo");
            is = photo.getBinaryStream();
            fos = new FileOutputStream("蜘蛛侠.png");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        }

        JDBCUtils.closeResource(connection,ps,rs);
        is.close();
        fos.close();
    }

5 PreparedStatement进行批量操作

批量插入:

public class InsertTest {
    @Test
    public void test1(){
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = JDBCUtils.getConnection();
            String sql = "INSERT INTO goods(name)VALUES (?)";
            ps = connection.prepareStatement(sql);

            for (int i = 0; i <=20000 ; i++) {
                ps.setObject(1,"name_" + i);
                ps.execute();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps);
        }

    }
}

优化:

开启批处理

设置了addBatch() executeBatch() clearBatch()和setAutoCommit后大大提升处理时间:

    @Test
    public void test3(){

        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = JDBCUtils.getConnection();
            //设置不允许提交数据
            connection.setAutoCommit(false);
            String sql = "INSERT INTO goods(name)VALUES (?)";
            ps = connection.prepareStatement(sql);

            for (int i = 0; i <=20000 ; i++) {
                ps.setObject(1,"name_" + i);

                //1 攒sql
                ps.addBatch();
                if (i % 500 == 0){
                    //2 执行
                    ps.executeBatch();
                    //3 清空batch
                    ps.clearBatch();
                }

            }
            //提交数据
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,ps);
        }

    }

6 数据库事务

6.1 事务处理

事务是一组逻辑操作单元(一个或者多个DML操作),使数据从一种状态变换到另一种状态

要保证:要么都提交,要么都回滚,保证数据库数据的一致性。

  • DDL一旦执行,自动提交,不可回滚
  • DML默认提交,可以回滚
  • 关闭连接也会自动提交
    //考虑数据库事务后连接
    public int  update(Connection connection,String sql, Object ...args){//sql占位符的个数与可变形参长度一致
        PreparedStatement ps = null;
        try {
            
            //1 预编译sql语句 返回PreparedStatement实例
            ps = connection.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;

    }
    @Test
    public void testUpdateTransaction(){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();

            //取消数据自动提交功能
            connection.setAutoCommit(false);
            String sql1 = "update user_table set balance = balance -100 where user = ?";
            update(connection,sql1,"AA");
            //模拟异常
            //System.out.println(10 / 0);
            String sql2 = "update user_table set balance = balance +100 where user = ?";
            update(connection,sql2,"BB");

            System.out.println("转账成功");

            //提交数据
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            //回滚数据
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            //开启自动提交
            //针对数据库连接池
            try {
                connection.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JDBCUtils.closeResource(connection,null);
        }


    }

6.2 事务的ACID

  1. 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  2. 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  3. 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  4. 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

6.2.1 数据库的并发问题
  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

数据库提供的4种事务隔离级别:

1 Read uncommitted(读未提交数据)

允许事务读取未被其他事务提交的变更、脏读、不可重复读、幻读的问题都会出现。

2 Read committed (读已提交数据)

只允许事务读取已经被其他事务提交的变更,可以避免脏读,但是不可重复读、幻读的问题都会出现。

2.3 Repeatable read (可重复读)

确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和可重复读,但是幻读问题仍然存在。

2.4 Serializable (串行化)

数据库事务的最高隔离级别。在此级别下,事务串行执行。在这个事务持续期间,禁止其他事务对该表示执行插入、更新、删除操作,可以避免脏读、不可重复读、幻读等读现象。但是效率低下,耗费数据库性能,不推荐使用。

7 DAO及其实现类

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

BaseDAO:

/**
 * 封装了针对数据表的通用操作
 * @author hjx
 * @create 2023-03-08 11:31
 */
public abstract class BaseDAO {
    //考虑上事务---修改
    public int  update(Connection connection,String sql, Object ...args){//sql占位符的个数与可变形参长度一致
        PreparedStatement ps = null;
        try {
            //1 预编译sql语句 返回PreparedStatement实例
            ps = connection.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;
    }

    //考虑上事务--查询/返回数据表的一条记录
    public <T> T getInstance(Connection connection,Class<T> clazz,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {

            ps = connection.prepareStatement(sql);

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

            rs = ps.executeQuery();
            //获取结果集元数据,
            ResultSetMetaData rsmd = rs.getMetaData();
            //获取列数
            int columnCount = rsmd.getColumnCount();
            if (rs.next()){
                T t = clazz.newInstance();
                //处理一行数据的每一个列
                for (int i = 0; i <columnCount ; i++) {
                    //获取列值
                    Object clolumnValue = rs.getObject(i + 1);

                    //获取每个列的列名 getColumnLabel 别名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为clolumnValue---反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,clolumnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }

    //考虑上事务--查询/返回数据表的多条记录构成的集合
    public <T> List<T> getForList(Connection connection,Class<T> clazz, String sql, Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {

            ps = connection.prepareStatement(sql);

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

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

                    //获取每个列的列名 getColumnLabel 别名
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为clolumnValue---反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,clolumnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;
    }
    //用于查询特殊值的通用方法
    public <E> E getValue(Connection connection,String sql,Object...args){
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = connection.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接口:

/**
 * 此接口用于规范针对customers表的常用操作
 *
 * @author hjx
 * @create 2023-03-08 11:49
 */
public interface CustomerDAO {
    /*
    将cust数据添加到数据库中
     */
    void insert(Connection connection, Customer cust);

    /*
    针对指定ID删除表中一条记录
     */
    void deleteById(Connection connection,int id);
    /*
    针对内存中的cust对象,修改数据表中指定的记录
     */
    void updateById(Connection connection,Customer cust);

    /*
    根据指定id查询Customer对象
     */
    void getCustomerById(Connection connection,int id);

    /*
    查询表中所有记录构成的集合
     */
    List<Customer> getAll(Connection connection);

    /*
    返回数据表中数据的条目数
     */
    Long getCount(Connection connection);

    /*
    返回数据表中最大的生日
     */
    Date getMaxBirth(Connection connection);

}

实现类CustomerDAOImpl:

/**
 * 实现类
 * @author hjx
 * @create 2023-03-08 12:01
 */
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {
    @Override
    public void insert(Connection connection, Customer cust) {
        String sql = "insert into customers(name,email,birth)values(?,?,?)";
        update(connection,sql,cust.getName(),cust.getEmail(),cust.getBirth());
    }

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

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

    }

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

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

    @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);
    }
}

生成测试单元,测试方法或者代码是否有误:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j6uusTx0-1678271629571)(C:\Users\洪健翔\AppData\Roaming\Typora\typora-user-images\image-20230308122132872.png)]

例:测试添加功能

public class CustomerDAOImplTest {

    CustomerDAOImpl dao = new CustomerDAOImpl();

    @Test
    public void insert(){
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            Customer cust = new Customer(1,"小明","xiaoming@126.com",new Date(435345435345L));
            dao.insert(connection,cust);
            System.out.println("添加成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }

    }
}

8 数据库连接池

8.1 数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。

  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个

  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

8.3 多种开源的数据库连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
    • C3P0 是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用
    • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。

推荐使用Druid数据库连接池

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。

  • 详细配置参数:
配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

 /*
    使用德鲁伊数据库连接池技术
     */
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import javax.xml.transform.Source;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

/**
 * @author hjx
 * @create 2023-03-08 16:57
 */
public class DruidTest {

    @Test
    public void getConnection() throws Exception {

        Properties pros = new Properties();

        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
        pros.load(is);

        DataSource source = DruidDataSourceFactory.createDataSource(pros);
        Connection connection = source.getConnection();
        System.out.println(connection);
    }
}

其中,src下的配置文件为:【druid.properties】:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-73qqxIZs-1678271629571)(C:\Users\洪健翔\AppData\Roaming\Typora\typora-user-images\image-20230308171815844.png)]

9 Apache-DBUtils实现CRUD操作

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

9.1 QueryRunner类测试

QueryRunner类的主要方法:

  • 更新
    • public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
  • 插入
    • public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
  • 批处理
    • public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
    • public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
  • 查询
    • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

1.插入

public class QueryRunnerTest {
    @Test
    public void testInsert(){
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();

            connection = JDBCUtils.getConnectionDruid();
            String sql ="insert into customers(name,email,birth)values(?,?,?)";

            int count = runner.update(connection, sql, "徐凤年", "xufn@126.com", "1998-07-15");
            System.out.println("添加了 "+ count+"条记录");
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }


    }
}

2.查询->返回一条记录

 @Test
    public void testQuery() throws SQLException {
        QueryRunner runner = new QueryRunner();
        Connection connection = JDBCUtils.getConnectionDruid();
        String sql = "select id,name,email,birth from customers where id =? ";
        //BeanHandler是ResultSetHandler接口的实现类,用于封装表中一条记录。
        BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
        Customer customer = runner.query(connection, sql, handler, 23);
        System.out.println(customer);
    }

3.查询->返回多条记录

    @Test
    public void testQuery2(){
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnectionDruid();
            String sql = "select id,name,email,birth from customers where id < ? ";
            //BeanListHandler是ResultSetHandler接口的实现类,用于封装表中记录构成的集合。
            BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
            List<Customer> list = runner.query(connection, sql, handler, 23);
            list.forEach(System.out::println);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

4.查询->特殊值

@Test
//查询表中所有记录
    public void testQuery3(){
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnectionDruid();
            String sql = "select count(*) from customers";
            //ScalarHandler用于查询特殊值
            ScalarHandler handler = new ScalarHandler();
            Long count = (Long) runner.query(connection, sql, handler);
            System.out.println(count);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

9.2 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:**查询单个值对象

自定义ResultSetHandler接口的实现类

 /*
    自定义ResultSetHandler接口的实现类
     */
    @Test
    public void testQuery4(){
        Connection connection = null;
        try {
            QueryRunner runner = new QueryRunner();
            connection = JDBCUtils.getConnectionDruid();
            String sql = "select id,name,email,birth from customers where id = ?";
            ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
                @Override
                public Customer handle(ResultSet rs) throws SQLException {
                    if(rs.next()){
                        int id = rs.getInt("id");
                        String name = rs.getString("name");
                        String email = rs.getString("email");
                        Date birth = rs.getDate("birth");

                        return new Customer(id, name, email, birth);
                    }
                    return null;

                }

            };
            Customer customer = runner.query(connection, sql, handler, 23);
            System.out.println(customer);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResource(connection,null);
        }
    }

使用dbutils提供的jar包中提供的工具类实现资源的关闭

    //使用dbutils提供的jar包中提供的工具类实现资源的关闭
    public static void closeResource1(Connection connection, Statement ps,ResultSet rs){
        // 方式1
//        try {
//            DbUtils.close(connection);
//        } catch (SQLException e) {
//            e.printStackTrace();
//        }
//        try {
//            DbUtils.close(ps);
//        } catch (SQLException e) {
//            e.printStackTrace();
//        }
//        try {
//            DbUtils.close(rs);
//        } catch (SQLException e) {
//            e.printStackTrace();
//        }
        // 方式2
        DbUtils.closeQuietly(connection);
        DbUtils.closeQuietly(ps);
        DbUtils.closeQuietly(rs);
    }

keep going!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值