JDBC
JDBC 是一种 操作 数据库的 标准 和 规范 、只有标准 而没有具体的实现
MySQL数据库驱动包
JDBC 只是一种标准,没有提供具体的实现,因此 在 使用 JDBC 操作数据库的时候,必须要引入 对应数据库的 驱动包(包含 实现 JDBC接口的 实现类)。
JDBC 操作 数据库的 步骤
- 加载 数据库的 驱动 类 (JDBC3 加载驱动 可以省略 )
Class.forName("com.mysql.cj.jdbc.Driver") ;
- 和 数据库 建立 链接 、并 获取 连接 对象
String url = "jdbc:mysql://localhost:3306/haredot" ;
String username = "root" ;
String password = "123456" ;
Connection conn = DriverManager.getConnection(url , username , password);
- 通过 连接对象 、获取一个 执行 SQL 的 执行器
Statement st = conn.createStatement() ;
- 编写一个 SQL 命令
- 使用 执行器 、执行 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: 调用处理器 , 负责 在该处理提供的方法中编写 切面逻辑代码 和通过反射调用目标方法
- public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
- InvocationHandler : 调用处理器 中 实现 切面编程 。
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
- proxy : 代理对象,通常使用不上
- method : 代理的目标方法
- args : 代理目标方法 需要的参数列表
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
- Proxy : 该类 可以 构建一个 代理对象
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();
}
}) ;