后端关卡18 了解JDBC

一、思维导图

在这里插入图片描述

二、重点知识点

1、PreparedStatement 与 Statement 的异同?

  • PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句

  • PreparedStatement 能最大可能提高性能:

    • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
    • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
    • (语法检查,语义检查,翻译成二进制命令,缓存)
  • PreparedStatement 可以防止 SQL 注入 (通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。)

2、Druid(德鲁伊)数据库连接池

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

package com.atguigu.druid;

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

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class TestDruid {
	public static void main(String[] args) throws Exception {
		Properties pro = new Properties();		 pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
		DataSource ds = DruidDataSourceFactory.createDataSource(pro);
		Connection conn = ds.getConnection();
		System.out.println(conn);
	}
}

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

url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.cj.jdbc.Driver

initialSize=10
maxActive=20
maxWait=1000
filters=wall

3、数据库连接方式(最终版)

public void testConnection5() throws Exception {
    	//1.加载配置文件
        InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
        pros.load(is);
        
        //2.读取配置信息
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");

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

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

配置文件【jdbc.properties】

user=root
password=123456
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.cj.jdbc.Driver

使用配置文件的好处:
①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
②如果修改了配置信息,省去重新编译的过程。

4、考虑上事务的通用增删改操作

public void 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.执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            JDBCUtil.closeResource(null, ps);
        }
    }

测试事务的增删改操作

public void testJDBCTransaction() {
        Connection conn = null;
        try {
            // 1.获取数据库连接
            conn = JDBCUtil.getConnection();
            // 2.开启事务,取消事务数据自动提交
            conn.setAutoCommit(false);
            // 3.进行数据库操作
            String sql1 = "update user_table set balance = balance - 100 where user = ?";
            update(conn, sql1, "AA");

            // 模拟网络异常
            //System.out.println(10 / 0);

            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            update(conn, sql2, "BB");
            // 4.若没有异常,则提交事务
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 5.若有异常,则回滚事务
            try {
                conn.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            try {
                //6.恢复每次DML操作的自动提交功能,主要针对数据库连接池的使用
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //7.关闭连接
            JDBCUtil.closeResource(conn, null, null);
        }
    }

5、考虑上事务的针对于不同表的通用的查询操作,返回一条记录

public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
        PreparedStatement ps = null;
        ResultSet rs = 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.执行,获取结果集
            rs = ps.executeQuery();
            //获取结果集的元数据:ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            //多个数据用while(rs.next())
            if (rs.next()) {
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集一行数据中的每一列
                for (int i = 0; i < columnCount; i++) {
                    //获取每一个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);
                    /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                    String columnName = rsmd.getColumnName(i + 1);*/
                    //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为columnValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            JDBCUtil.closeResource(null, ps, rs);
        }
        return null;
    }

6、考虑上事务针对于不同表的通用的查询操作,返回多条数据集合

public <T> List<T> getForList(Connection conn, Class<T> clazz, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
        PreparedStatement ps = null;
        ResultSet rs = 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.执行,获取结果集
            rs = ps.executeQuery();
            //获取结果集的元数据:ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            //创建集合对象
            ArrayList<T> list = new ArrayList<>();
            //多个数据用while(rs.next())
            while (rs.next()) {
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集一行数据中的每一列:给t对象指定的属性赋值
                for (int i = 0; i < columnCount; i++) {
                    //获取每一个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);
                    /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                    String columnName = rsmd.getColumnName(i + 1);*/
                    //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为columnValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            JDBCUtil.closeResource(null, ps, rs);
        }
        return null;
    }

7、使用PreparedStatement实现批量数据的操作(批量插入)

//update、delete本身就具有批量操作的的效果
    //1、使用 addBatch() / executeBatch() / clearBatch()
    //2、mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。?rewriteBatchedStatements=true写在配置文件的url后面
    //3、使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
    public static void testInsert(){
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtil.getConnection();
            //设置不允许自动提交数据
            conn.setAutoCommit(false);
            String sql = "insert into goods(name) values(?);";
            ps = conn.prepareStatement(sql);
            for(int i = 1;i <= 20000;i++){
                ps.setString(1, "name_" + i);
                //1.“攒”sql
                ps.addBatch();
                if (i % 500 == 0){
                    //2.执行batch
                    ps.executeBatch();
                    //3.清空batch
                    ps.clearBatch();
                }

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

8、BaseDAO.java 封装了针对数据表的通用操作,写接口以及继承BaseDAO和实现接口的实现类

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];    //泛型的第一个参数
    }

    //考虑上事务的通用增删改操作
    public void 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.执行
            ps.execute();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            JDBCUtil.closeResource(null, ps);
        }
    }

    //考虑上事务的针对于不同表的通用的查询操作,返回一条记录
    public T getInstance(Connection conn, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
        PreparedStatement ps = null;
        ResultSet rs = 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.执行,获取结果集
            rs = ps.executeQuery();
            //获取结果集的元数据:ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            //多个数据用while(rs.next())
            if (rs.next()) {
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集一行数据中的每一列
                for (int i = 0; i < columnCount; i++) {
                    //获取每一个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);
                    /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                    String columnName = rsmd.getColumnName(i + 1);*/
                    //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为columnValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            JDBCUtil.closeResource(null, ps, rs);
        }
        return null;
    }

    //考虑上事务的针对于不同表的通用的查询操作,返回多条数据构成的集合
    //list.forEach(System.out::println);
    public List<T> getForList(Connection conn, String sql, Object... args) {    //sql中占位符的个数与可变形参的长度相同!
        PreparedStatement ps = null;
        ResultSet rs = 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.执行,获取结果集
            rs = ps.executeQuery();
            //获取结果集的元数据:ResultSetMetaData
            ResultSetMetaData rsmd = rs.getMetaData();
            //通过ResultSetMetaData获取结果集中的列数
            int columnCount = rsmd.getColumnCount();

            //创建集合对象
            ArrayList<T> list = new ArrayList<>();
            //多个数据用while(rs.next())
            while (rs.next()) {
                T t = clazz.getDeclaredConstructor().newInstance();
                //处理结果集一行数据中的每一列:给t对象指定的属性赋值
                for (int i = 0; i < columnCount; i++) {
                    //获取每一个列的列值:通过ResultSet
                    Object columnValue = rs.getObject(i + 1);
                    /*//获取每一个列的列名:通过ResultSetMetaData.getColumnName,不推荐使用
                    String columnName = rsmd.getColumnName(i + 1);*/
                    //获取每一个列的别名:通过ResultSetMetaData.getColumnLabel
                    String columnLabel = rsmd.getColumnLabel(i + 1);
                    //给cust对象指定的columnName属性赋值为columnValue:通过反射
                    Field field = clazz.getDeclaredField(columnLabel);
                    //setAccessible()值为true表示反射的对象在使用时应该取消Java语言访问检查。由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
                    field.setAccessible(true);
                    field.set(t, columnValue);
                }
                list.add(t);
            }
            return list;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            JDBCUtil.closeResource(null, ps, rs);
        }
        return null;
    }

    //用于查询特殊值的通用方法
    public <E> E getValue(Connection conn, String sql, Object... args) {
        PreparedStatement ps = null;
        ResultSet rs = 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.执行,获取结果集
            rs = ps.executeQuery();
            if (rs.next()) {
                return (E) rs.getObject(1);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtil.closeResource(null, ps, rs);
        }
        return null;
    }
}

接口 【CustomerDAO】

//此接口用于规范对于customers表的常用操作
public interface CustomerDAO {
    //将cust对象添加到数据库中
    void insert(Connection conn, Customer cust);

    //根据指定的id,删除数据表中的一条记录
    void deleteById(Connection conn, int id);

    //根据内存中的cust对象,修改数据表中指定记录
    void update(Connection conn, Customer cust);

    //根据指定的id,查询得到对应的Customer对象
    Customer getCustomerById(Connection conn, int id);

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

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

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

实现类 【CustomerDAOImpl】

public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {

    @Override
    public void insert(Connection conn, Customer cust) {
        String sql = "insert into customers(name,email,birth) values(?,?,?);";
        update(conn, sql, cust.getName(), cust.getEmail(), cust.getBirth());
    }

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

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

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

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

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

    @Override
    public Date getMaxBirth(Connection conn) {
        String sql = "select max(birth) from customer;";
        return getValue(conn, sql);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值