JDBC介绍及入门实例和对Proxy的介绍

JDBC

JDBC 是一种 操作 数据库的 标准 和 规范 、只有标准 而没有具体的实现

MySQL数据库驱动包

JDBC 只是一种标准,没有提供具体的实现,因此 在 使用 JDBC 操作数据库的时候,必须要引入 对应数据库的 驱动包(包含 实现 JDBC接口的 实现类)。

JDBC 操作 数据库的 步骤

  1. 加载 数据库的 驱动 类 (JDBC3 加载驱动 可以省略 )
Class.forName("com.mysql.cj.jdbc.Driver") ;   
  1. 和 数据库 建立 链接 、并 获取 连接 对象
String  url  =  "jdbc:mysql://localhost:3306/haredot" ; 
String username = "root" ; 
String password = "123456" ;

Connection  conn  =  DriverManager.getConnection(url ,  username ,  password);
  1. 通过 连接对象 、获取一个 执行 SQL 的 执行器
Statement  st  = conn.createStatement() ;
  1. 编写一个 SQL 命令
  2. 使用 执行器 、执行 SQL 命令
boolean  ret =  st.execute(sql) ;

execute 执行的结果 表示 SQL是否有结果集、如果有,返回 true, 如果没有, 返回 false, 不代表是否执行成功
6. 释放资源、关闭连接

st.close();
conn.close();

Java SPI 机制

Java官方提供了一个 ServiceLoader 类 , 该类 可以通过 load 方法 ,从 META-INF/services 文件夹下, 查找 指定的 接口 文件,从而 找到 接口文件中定义的 具体该接口的实现类,从而 获取该接口的实现类 ,并实现自己想要的 效果 。 降低 接口和实现类的 耦合。


获取插入数据自动生成的主键

  • 方式一:
    Statement st = conn.createStatement() ; 
    
    // 插入数据 省略 ...
    
    // 获取 最后插入的主键 
    String sql  =  “select  last_insert_id();
    
    st.execute(sql) ;
    
    ResultSet  rs = st.getResultSet();
    
    if (rs.next() ) {
       System.out.println("插入数据后、自动生成的主键是:" +  rs.getLong(1)) ;
    }
    
  • 方式二:
    Statement st = conn.createStatement() ; 
    
    String  sql = "insert into tb_user(name,  password) values('张三',  '123456')" ;
    
    // 执行 SQL , 并 告诉 执行器 要返回 自动生成的主键 
    st.execute(sql,  Statement.RETURN_GENERATED_KEYS) ; 
    
    // 获取 自动生成的主键 
    ResultSet  rs  = statement.getGeneratedKeys();
    
    if (rs.next() ) {
       System.out.println("插入数据后、自动生成的主键是:" +  rs.getLong(1)) ;
    }
    

Statement 执行SQL 的方法

  • execute(sql) : 能够执行 所有的 SQL 命令、 返回 一个 boolean , 代表 是否有 结果集
  • executeUpdate(sql) : 主要 负责 执行 insert into , update , delete 命令 、返回 影响的 表 行数
  • executeQuery(sql) : 执行 select 命令, 并返回 ResultSet 结果集

预编译 PreparedStatement 解决 SQL注入

在 SQL中,往往会使用到一些动态数据,而动态数据 大部分都来自于 用户的输入 , 如果用法 通过 SQL 漏洞, 进行 SQL拼接的时候,更改了 SQL语句的结构 ,就有可能会导致在 执行SQL的时候,发生 和预期不相符的 操作,这种现象 称为 SQL注入。解决 SQL注入的 最有效的办法就是采用 预编译 技术

  • executeUpdate() : 执行 增删改 SQL命令 , 返回印象行数

  • executeQuery () : 执行 查询 命令,并返回结果集

  • setXxxx(n, val) : 给 第 N 个 SQL 中的 ? 绑定 数据 val

    注意 : executeUpdate 和 executeQuery 方法在调用的时候,均不需要传入 任何参数


预编译 下 获取插入 后 自动生成的主键

String sql = "insert into tb_user(name, time,sex, age) values(?, ?, ?, ?)" ;
// 获取 预编译 执行器,并 设置 获取 插入 后 自动生成的主键
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) ; 
// 给 SQL 绑定 数据
ps.setString(1, "HHH");
ps.setObject(2, LocalDate.of(2000, 11, 10));

ps.setString(3, "f");
ps.setLong(4, 1);

// 执行 SQL
int len = ps.executeUpdate() ;

// 获取插入的主键
ResultSet resultSet = ps.getGeneratedKeys();

if (resultSet.next()) {

    long aLong = resultSet.getLong(1);

    System.out.println(aLong);
}

JDBC 批量插入 / 更新

 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/student?rewriteBatchedStatements=true", "root", "123456");

String sql = "insert into tb_user(name, sex, age) values(?, ?, ?)" ;
PreparedStatement ps = conn.prepareStatement(sql) ;

final int batchSize = 500 , total = 10000;

for(int i = 1, count = 0; i < total ; i++) {
  
    ps.setString(1,  "张三" + i) ;
    ps.setString(2,  'm') ; 
    ps.setInt(3, 18) ;
    
    ps.addBatch() ; // 将数据添加到 批处理中 
    
    if (++count % batchSize == 0 || count >= total) {
       ps.executeBatch();  // 执行批处理
    }
}

在 批处理中, 连接地址中可以添加 rewriteBatchedStatements = true 参数,提高 批处理的性能 。

ResultSet 结果集

获取 结果集 中的数据,必须 先调用 next() 方法, 再 通过 getXxxx 方法 获取对应的数据

如果 查询 的是 单条记录, 则在 if 中 调用 next() 方法, 否则 使用 while 遍历并获取多条数据

  • getXxxx(n) : 获取 指定查询 列 对应的 数据
  • getXxxx(name) : 根据 列名 获取 对应 列的数据
  • getDate(n) : 获取 日期,返回 java.sql.Date, 只保留 年月日
  • getTimestamp(n) : 获取 时间戳, 返回 java.sql.Timestamp , 保留 年月日 时分秒
  • getObject(n , LocalDate.class) : 获取 值 并转换 为 LocalDate 新版日期

    java.sql.Date 和 java.sql.Timestamp 均为 java.util.Date 的子类,在 代码 中 均可以使用 java.util.Date 接收 数据

ResultSetMetaData 元数据

获取 结果集元数据 : ResultSet#getMetaData() , 元数据对象中 包含了 和查询 列相关的 信息

  • getColumnCount() : 获取 查询的 列的 个数
  • getColumnLabel(n) : 获取 第 N 个 列的 名称 (如果列有别名、获取的是 别名)
  • getgetColumnType(n) : 获取 第 N 个列的 类型 ,返回一个 Int, 可以 通过 Types 中定义的常量 进行 判断 具体的 类型
  • getColumnTypeName(n) : 获取 第 N个类的类型 字符串表示形式 ,例如 VARCHAR
  • getPrecision(n) : 获取 第 N 个列的 长度
  • getScale(n) : 获取 第 N 个列的 保留的小数位

JDBC 中 操作事务

  • 获取 连接

    Connection  conn =  DriverManager.getConnection(url,  username, password) ;
    
  • 设置手动提交数据(开启事务管理)

    conn.setAutoCommit(false); 
    
  • 处理完业务 提交事务

    conn.commit();
    
  • 业务异常 回滚事务

    conn.rollback();
    
  • 业务无论成功还是失败、关闭连接

    conn.close();
    

代理 Proxy

  • 静态代理
    • 通过 编写 代理类的方式 给 每一个 需要代理的 目标类 添加 代理 ,这种行为 称为 静态代理。
    • 静态代理 要求 和 要代理的 目标对象 实现 相同的接口 , 并维护 要代理的对象 ~
  • 动态代理

    程序在运行期间、给 目标对象 动态的 生成 代理 ,底层 采用的是反射技术进行实现的。动态代理是一种 切面编程技术 AOP 。

    AOP 不是 用来 替代 OOP (面向对象编程的) 、而是对面向对象进行的扩展和补充。

    • JDK 动态代理 : Java 官方自带的、目标对象 必须 实现 接口 、且 只能代理 接口中定义的方法 。返回的代理对象 和 目标对象 是 兄弟 关系
    • CGLIB 动态代理 : 目标对象 可以 不用实现任何借口,代理的是 目标 对象中定义的公开方法、 且 返回的 代理 对象 和 目标对象 形成 父子关系 , 目标对象为 父, 代理对象为 子 。
  • JDK 动态代理

    • Proxy : 该类 可以 构建一个 代理对象
      • public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        • classLoader : 类加载器
        • interfaces : 目标对象实现的 接口列表
        • invocationHandler: 调用处理器 , 负责 在该处理提供的方法中编写 切面逻辑代码 和通过反射调用目标方法
    • InvocationHandler : 调用处理器 中 实现 切面编程 。
      • public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
        • proxy : 代理对象,通常使用不上
        • method : 代理的目标方法
        • args : 代理目标方法 需要的参数列表
Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> {

    // 编写 切面代码
    Connection connection = DBUtils.getConnection();
    // 开启事务
    connection.setAutoCommit(false);

    try {
        // 目标 对象开始 执行 业务
        Object ret = method.invoke(target, args);

        // 提交 事务
        connection.commit();
        // 返回 目标对象 执行的结果
        return  ret ;
    } catch (Exception e) {
        // 事务进行 回滚
        connection.rollback();
        throw new DaoAccessException(e);
    }finally {
        DBUtils.closeConnection();
    }
}) ;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值