文章目录
JDBC基本使用步骤
基于statement演示查询
准备数据库数据
CREATE DATABASE atguigu;
USE atguigu;
CREATE TABLE t_user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键',
account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
PASSWORD VARCHAR(64) NOT NULL COMMENT '密码',
nickname VARCHAR(20) NOT NULL COMMENT '昵称');
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
('root','123456','经理'),('admin','666666','管理员');
-- 查询需求,查询全部用户数据
SELECT *
FROM t_user;
注册驱动
//1.注册驱动
/*
* 依赖:驱动版本8+ com.mysql.cj.jdbc.Drrver
* */
DriverManager.registerDriver(new Driver());
获取连接
java程序要和数据库创建链接
java程序连接数据库,调用某个方法,方法也需要填入连接数据库的基本信息:
数据库ip地址 127.0.0.1
数据库端口号 3306
账号
密码
连接数据库的名称
//java程序要和数据库创建链接
/*
* 参数一:url
* jdbc:数据库厂商名://ip地址:part/数据库名
* */
//java.sql 接口 = 实现类Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "lsl12345");
创建statement
//3.创建statement
Statement statement = connection.createStatement();
发送sql语句
//4.发送sql语句
String sql="select * from t_user";
ResultSet resultSet = statement.executeQuery(sql);
进行结果解析
//查看有没有下一行数据,如果有就获取
while (resultSet.next()){
int id = resultSet.getInt("id");
String account = resultSet.getString("account");
String PASSWORD = resultSet.getString("PASSWORD");
String nickname = resultSet.getString("nickname");
System.out.println(id+"--"+account+"--"+PASSWORD+"--"+nickname);
}
关闭资源
//6.关闭资源
resultSet.close();
statement.close();
connection.close();
基于statement方式问题
明确jdbc流程和详细讲解使用(注册驱动,获取连接,发送语句,结果解析)
发现问题,引出preparedstatement
package com.Liang.api.statement;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* @author Mr Liang
* @create 2023-03-15-11:46
* 模拟用户登录
* <p>
* 明确jdbc流程和详细讲解使用(注册驱动,获取连接,发送语句,结果解析)
* 发现问题,引出preparedstatement
* <p>
* 输入账号和密码
* 进行数据库信息查询(t_user)
* 反馈登录成功还是登录失败
* <p>
* 键盘输入事件,收集账号和密码信息
*/
public class StatementUserLoginPart {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// 1.获取用户输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("输入账号");
String account = scanner.nextLine();
System.out.println("输入密码");
String password = scanner.nextLine();
scanner.close();
// 2.注册驱动
/*
* DriverManager.registerDriver(new Driver());
* 问题:会注册两次驱动
* 1. DriverManager.static(DriverManager.registerDriver()) 静态代码块,也会注册一次
* 2. DriverManager.registerDriver(new Driver());方法本身会注册一次
* 解决:只想注册一次驱动
* 只触发一个静态代码块即可
* 如何触发静态代码块:
* 在类加载的时刻,会触发静态代码块
* 加载,class文件->jvm虚拟机的class对象
* 连接,验证(检查文件类型)->准备(静态变量默认值)->解析(触发静态代码块)
* 初始化,静态属性赋初值
* 触发器加载:
* 1.new 关键字
* 2.调用类的静态属性
* 3.调用静态属性
* 4.接口1.8 default 默认实现
* 5.反射
* 6子类触发器
* 7.程序的入口
* */
// 方案一 DriverManager.registerDriver(new Driver());被淘汰
// 方案二 new Driver();不灵活,只能依赖于当前驱动
// 方案三 反射 字符串->提取到外部的配置文件->更加灵活,完成数据库驱动的切换
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取数据库连接,三种方式的传递
/* getConnection(1,2,3) 是一个重载方法,允许开发者用不同的形式传入数据库连接的核心参数
* 模拟用户登录核心属性:
* 1.数据库软件所在的主机的ip地址/主机名:localhost/127.0.0.1
* 2.数据库软件所在的端口号
* 3.连接的具体库
* 4.账号,密码
* 5.可选的信息
* 三个参数
* String url :数据库软件所在信息,连接的具体库,以及其他可选信息
* 语法:具体:jdbc:mysql://127.0.0.1:3306/atgugui 或
* jdbc:mysql://localhost:3306/atgugui
* 本机的省略写法:
* jdbc:mysql://127.0.0.1:3306/atgugui = jdbc:mysql:///atguigu
* 省略了本机地址和端口号
* 必须是本机,且端口号为3306才可以省略
* 二个参数:
* String url:同上
* Properties info:存储账号和密码
* 类似于Map ,只不过key=value 都是字符串的形式
* 一个参数:
* String url: 数据库ip,端口号,具体的数据库 可选信息(账号密码)
* jdbc:数据库软件名://ip:part/数据库?key=value&key=value&key=value
* jdbc:mysql://localhost:3306/atguigu?user=root&password=lsl12345
* 携带固定的参数名: user password 传递账号和密码信息
*
* url的路径属性可选信息:
* url?user=账号&password=密码
* 路径扩展:serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
*
* */
// 三个参数
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "lsl12345");
// 两个参数
Properties info = new Properties();
info.put("user", "root");
info.put("PASSWORD", "lsl12345");
Connection connection1 = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", info);
// 一个参数
Connection connection2 = DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu?user=root&password=lsl12345");
// 3.创建发送sql语句的statement对象
Statement statement = connection.createStatement();
// 4.发送sql语句
String sql = "SELECT * FROM t_user WHERE account = '" + account + "' AND PASSWORD = '" + password + "';";
/*
* sql分类:DDL(容器创建,修改,删除),DML(插入,修改,删除),DDL(查询),DCL(权限控制),TPL(事务控制语言)
*/
// int i = statement.executeUpdate(sql);//非DQL语句
ResultSet resultSet = statement.executeQuery(sql);//结果封装对象
// 查询结果解析
/*
* 移动游标问题
* resultSet内部包含一个游标,指向当前行数据
* 默认游标指的是第一行数据之前
* 调用next方法向后移动一行游标7
* 多行数据可以使用while(next){获取每一行的数据}
* boolean = next()
*
* 获取列的数据问题(获取光标指定的行的数据)
* resultSet.get类型(String columnLabel | int columnIndex)
* columnLabel:列名 如果有别名 写别名
* columnIndex:列的下角标开始:从左向有 从1开始
* */
/* while (resultSet.next()) {
int id = resultSet.getInt(1);
String account1 = resultSet.getString("account");
int password1 = resultSet.getInt(3);
String nickname = resultSet.getString("nickname");
System.out.println(id+"--"+account+"--"+password1+"--"+nickname);
}*/
// 移动一次光标,只要有数据,就代表登录成功
if (resultSet.next()) {
System.out.println("登录成功!");
} else {
System.out.println("登录失败!");
}
// 关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
存在问题:
-
SQL语句需要字符串拼接,比较麻烦
-
只能拼接字符串类型,其他的数据库类型无法处理
-
可能发生注入攻击
动态值充当了SQL语句结构,影响了原有的查询结果!
输入一个不存在的账号
输入上述密码,发现登录成功,动态值充当了SQL语句结构,影响了原有的查询结果
基于preparedStatement方式优化
statement语句只适合进行静态sql语句的执行,如果输入动态sql语句,会发生注入攻击
利用preparedStatement解决上述案例注入攻击和SQL语句拼接问题,前置知道sql语句结构,再给对应结构的动态值部分赋值
与statement不同的地方在编写sql语句的地方
// 编写sql语句结果
/*
* 1.编写SQL语句结果 不包含动态值部分的语句,动态值部分使用占位符 ? 替代 注意:?只能提点动态值
* 2.创建preparedstatement,并传入动态值
* 3.动态值 占位符 赋值? 单独赋值即可
* 4.发送sql语句即可,并获取返回结果
* */
String sql = "select * from t_user where account= ? and password= ?;";
// 创建预编译statement并且设置sql语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 单独的占位符进行赋值
/*
* 参数一:index 占位符的位置
* 参数二:object 占位符的值,可以设置任何类型的数据,避免了我们拼接和类型更加丰富,
* */
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
// 发送sql语句,并获取返回结果
// preparedStatement.executeUpdate | executeQuery(String sql);
ResultSet resultSet = preparedStatement.executeQuery();
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwfpdVLQ-1679019192753)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20230315181350710.png)]
图解
基于preparedStatement演示curd
增添数据
public void testInsert() throws ClassNotFoundException, SQLException {
/*
* * 插入一条用户数据!
* 账号: test
* 密码: test
* 昵称: 测试
* */
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
// 3.编写sql语句结果,动态值的部分使用?代替
String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
// 4.创建预编译statement并且设置sql语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占位符赋值
preparedStatement.setObject(1, "test");
preparedStatement.setObject(2, "test");
preparedStatement.setObject(3, "测试");
// 6.发送sql语句
int rows = preparedStatement.executeUpdate();
// 7.输出结果
System.out.println(rows);
// 8.关闭资源
preparedStatement.close();
connection.close();
}
更新数据
public void testInsert() throws ClassNotFoundException, SQLException {
/*
* * 插入一条用户数据!
* 账号: test
* 密码: test
* 昵称: 测试
* */
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
// 3.编写sql语句结果,动态值的部分使用?代替
String sql = "update t_user set password='123' where id=?;";
// 4.创建预编译statement并且设置sql语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占位符赋值
preparedStatement.setObject(1, "1");
// 6.发送sql语句
int rows = preparedStatement.executeUpdate();
// 7.输出结果
System.out.println(rows);
// 8.关闭资源
preparedStatement.close();
connection.close();
}
删除数据
public void testDelete() throws ClassNotFoundException, SQLException {
/**
* 删除一条用户数据!
* 根据账号: test
*/
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
// 3.编写sql语句结果,动态值的部分使用?代替
String sql="delete from t_user where account=?";
// 4.创建预编译statement并且设置sql语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.占位符赋值
preparedStatement.setObject(1,"test");
// 6.发送sql语句
int rows = preparedStatement.executeUpdate();
// 7.输出结果
System.out.println(rows);
// 8.关闭资源
preparedStatement.close();
connection.close();
}
数据查询
public void testSelect() throws Exception {
/**
* 查询全部数据!
* 将数据存到List<Map>中
* map -> 对应一行数据
* map key -> 数据库列名或者别名
* map value -> 数据库列的值
* TODO: 思路分析
* 1.先创建一个List<Map>集合
* 2.遍历resultSet对象的行数据
* 3.将每一行数据存储到一个map对象中!
* 4.将对象存到List<Map>中
* 5.最终返回
*
* TODO:
* 初体验,结果存储!
* 学习获取结果表头信息(列名和数量等信息)
*/
// 1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=lsl12345");
// 3.编写sql语句结果,动态值的部分使用?代替
String sql="select * from t_user;";
// 4.创建preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 5.没有动态sql语句,省略占位符的赋值
// 6.发送sql语句
ResultSet resultSet = preparedStatement.executeQuery();
// 7.结果集解析
List<Map> list= new ArrayList<>();
// TODO:metaData 存储当前结果集列的信息对象(可以获取列的名称根据下角标,可以获取列的数量)
ResultSetMetaData metaData = resultSet.getMetaData();
// TODO:水平遍历列
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
Map map=new HashMap();
// 一行数据对应一个Map,纯手动写法,在后续更改中很繁琐
/* map.put("id",resultSet.getInt("id"));
map.put("account",resultSet.getString("account"));
map.put("password",resultSet.getString("password"));
map.put("nickname",resultSet.getString("nickname"));
list.add(map);*/
for (int i = 0; i < columnCount; i++) {
// 获取指定列下角标的值
Object value = resultSet.getObject(i);
// 获取指定列下角标的列的名称ResultSetMetaData
String columnLabel = metaData.getColumnLabel(i);//可以获取列的别名
map.put(columnLabel,value);
}
list.add(map);
}
System.out.println("list="+list);
//8.关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
preparedStatement使用方法总结
使用步骤
//1.注册驱动
//2.获取连接
//3.编写SQL语句
//4.创建preparedstatement并且传入SQL语句结构
//5.占位符赋值
//6.发送SQL语句,并且获取结果
//7.结果集解析
//8.关闭资源
使用API总结
//1.注册驱动
方案1: 调用静态方法,但是会注册两次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射触发
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection();
3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=账号&password=密码 )
//3.创建statement
//静态
Statement statement = connection.createStatement();
//预编译
PreparedStatement preparedstatement = connection.preparedStatement(sql语句结构);
//4.占位符赋值
preparedstatement.setObject(?的位置 从左到右 从1开始,值)
//5.发送sql语句获取结果
int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL
//6.查询结果集解析
//移动光标指向行数据 next(); if(next()) while(next())
//获取列的数据即可 get类型(int 列的下角标 从1开始 | int 列的label (别名或者列名))
//获取列的信息 getMetadata(); ResultsetMetaData对象 包含的就是列的信息
getColumnCount(); | getCloumnLebal(index)
//7.关闭资源
close();
Druid连接池技术使用
总结缺点:
(1)不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃断开,连接的利用率太低,太浪费。
(2)对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制,很容易导致数据库服务器崩溃。
我们就希望能管理连接。
- 我们可以建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始,
我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,
不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。 - 可以提高连接的使用率。当池中的现有的连接都用完了,那么连接池可以向服务器申
请新的连接放到池中。 - 直到池中的连接达到“最大连接数”,就不能在申请新的连接了,如果没有拿到连接的用户只能等待。
JDBC 的数据库连接池使用 javax.sql.DataSource接口进行规范,所有的第三方连接池 都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能
- DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,
稳定性较c3p0差一点
硬编码
直接使用代码设置连接池参数方式
- 1.创建druid连接的对象
- 2.设置连接参数
- 3.获取连接[通用方法,所有连接池都一样]
- 4.回收连接
public void testhard() throws SQLException {
// 连接池对象
DruidDataSource dataSource = new DruidDataSource();
// 设置参数
// 必须 连接数据库驱动类的全限定符【注册驱动】 | url |user | password
dataSource.setUrl("jdbc:mysql:///atguigu");
dataSource.setUsername("root");
dataSource.setPassword("lsl12345");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 非必须 初始化连接数量,最大的连接数量
dataSource.setInitialSize(5);//初始化连接数量
dataSource.setMaxActive(10);//最大的数量
// 获取连接
Connection connection = dataSource.getConnection();
// 数据curd
// 回收连接
connection.close();//连接池提供的连接被回收
}
}
软编码
软编码需要在外部添加一配置文件,存放在src/路径下
在配置文件中添加如下内容
public void testSoft() throws Exception {
// 读取外部配置文件 Properties
Properties properties = new Properties();
// src 下的文件可以使用类加载器
InputStream is = DriudUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
// 使用连接池工具类的工程模式,创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
// 数据库curd
// 回收连接
connection.close();
}
JDBC使用优化以及工具类封装
jdbc 工具类封装 v1.0
封装一个工具类,内部包含连接池对象,同时对外提供连接的方法和回收连接的方法
工具类的方法推荐写成静态,外部调用更加方便
* 实现:
* 属性:连接池对象【实例化一次】
* 单例模式
* 静态代码块
* static{
* 全局调用一次
* }
* 方法:
* 对外提供连接的方法
* 回收外部传入连接方法
public class JdbcUtils {
private static DataSource dataSource = null;//连接池对象
static {
//初始化连接池对象
Properties properties = new Properties();
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
* 对外提供连接的方法
* */
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
7
/*
* 回收连接的方法
* */
public static void freeConnection(Connection connection) throws SQLException {
connection.close();//连接池的连接,调用close就是回收!
}
}
接下来使用封装的工具类
public class jdbcCurdPart {
public void testInsert() throws Exception {
// 提供连接
Connection connection = JdbcUtils.getConnection();
// 数据库的curd
// 数据的回收
JdbcUtils.freeConnection(connection);
}
}
jdbc 工具类封装 v2.0
在1.0版本中,同一个线程的不用方法调用getConnection方法获取的不是同一个对象,即获取的是不通的连接
优化工具类v1.0版本,考虑事务的情况下!如何一个线程的不同方法获取同一个连接
ThreadLocal的介绍:
JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。
使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、
Session等
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个
ThreadLocalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的
共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,
不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal对象.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal对象.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。
* TODO:
* 利用线程本地变量,存储连接信息,确保一个线程的多个方法可以获取同一个connection
* 优势:事务操作的时候 service 和 dao 属于同一个线程,不再传递参数
* 可以调用getConnection自动获取的是相同的连接池
*/
public class JdbcUtilsV2 {
private static DataSource dataSource = null;//连接池对象
private static ThreadLocal<Connection> tl = new ThreadLocal<>();//声明线程本地变量对象,存储Connection
static {
//初始化连接池对象
Properties properties = new Properties();
InputStream is = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*
* 对外提供连接的方法
* */
public static Connection getConnection() throws SQLException {
// 线程本地变量中是否存在
Connection connection = tl.get();
// 如果第一次没有
if (connection == null) {
// 线程本地变量为空,从连接池获取
connection = dataSource.getConnection();
tl.set(connection);//存储一份Connection对象
}
return connection;
}
/*
* 回收连接的方法
* */
public static void freeConnection() throws SQLException {
Connection connection1 = tl.get();
if (connection1 != null) {
tl.remove();//清空线程本地变量状态
connection1.setAutoCommit(true);//事务状态回归
connection1.close();//回收到连接池
}
}
}
整个过程为,每次先线程本地变量取一份,没有的话,再从连接池中取,同时也存到线程本地变量一份。回收的时候从线程本地变量中拿,然后清空线程本地变量状态,将事务状态回归。
高级应用层封装BaseDao
前面的工具类,只封装了以下的三个步骤,剩余的步骤仍需要自己手动编写
使用BaseDao继续进行优化
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDao。
这些对应的Dao都继承于BaseDao,重复的方法也就自动的拥有了,不必再重复增删改查的动作。
在BaseDao中去调用工具类获取连接
BaseDao的封装
在表的操作中,将增、删、改归并为一类,查为另一类,即分为非DQL与DQL类
因此在BaseDao只需封装两个方法即可
非DQL查询方式封装
* 封装简化非DQL语句,
* sql 带占位符的值
* params 占位符的值
* return 返回执行影响的参数
public int executeUpdate(String sql,Object... params){//sql语句中的占位符是暂不可知的,这里使用可变参数列表
}
package com.Liang.api.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author Mr Liang
* @create 2023-03-16-16:15
* 封装Dao层数据库重复代码
* TODO;
* 封装两个方法 一个简化非DQL
* 一个简化DQL
*/
public abstract class BaseDao {
/*
* 封装简化非DQL语句
* sql 带占位符的值
* params 占位符的值
* return 返回执行影响的参数
* */
public int executeUpdate(String sql, Object... params) throws SQLException {//sql语句中的占位符是暂不可知的,这里使用可变参数列表
// 获取连接
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 占位符赋值
// 可变参数可以当做数组进行使用
for (int i = 0; i <= params.length; i++) {
preparedStatement.setObject(i, params[i-1]);
}
// 发送sql语句
int rows = preparedStatement.executeUpdate();
preparedStatement.close();
// 处理连接
if(connection.getAutoCommit()){
// 没有开启事务,正常回收连接
JdbcUtilsV2.freeConnection();
}
// 是否回收连接,需要考虑是不是事务!
// connection.setAutoCommit(false);//表示开启事务 不要管连接即可 业务层进行处理
return rows;
}
}
以下就将非DQL的方法封装完成,接下来通过继承BaseDao类进行中间操作
public class PSCURDPART extends BaseDao {
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
/*
* * 插入一条用户数据!
* 账号: test
* 密码: test
* 昵称: 测试
* */
String sql = "insert into t_user(account,password,nickname) values (?,?,?);";
int i = executeUpdate(sql, "测试1", "qwer", "运营员");
System.out.println("i="+i);
}
}
数据插入成功
DQL查询方式封装
非DQL语句封装方法的返回值固定为int
DQL语句封装方法的返回值是什么类型呢?
前面在执行查询语句操作时我们使用list<map>存放查询结果的返回值,在封装方法时并不适用,
map的key和value都是自定义的,不用先设定好
map 没有数据校验机制
map不支持反射操作
数据库中的数据对应到java中的实体类,多行数据对象List<Java实体类> list的一个集合,返回的为Listlist 某一个实体类的集合
通过传入Class对象确定泛型,后续通过列名反射对应的属性名进行赋值
public <T> List<T> executeQuery(Class<T> clazz,String sql,Object...params) throws Exception {
// 获取连接
Connection connection = JdbcUtilsV2.getConnection();
// 创建preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if (params != null && params.length != 0) {
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
}
// 发送sql语句
ResultSet resultSet = preparedStatement.executeQuery();
// 结果集解析
List<T> list = new ArrayList<>();
// TODO:metaData 存储当前结果集列的信息对象(可以获取列的名称根据下角标,可以获取列的数量)
ResultSetMetaData metaData = resultSet.getMetaData();
// TODO:水平遍历列
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
T t = clazz.getDeclaredConstructor().newInstance();//调用类的无参构造函数实例化对象
// 一行数据对应一个T类型的对象,
for (int i = 1; i <= columnCount; i++) {
// 对象的属性值
Object value = resultSet.getObject(i);
// 获取指定列下角标的列的名称 ResultSetMetaData
String propertyName = metaData.getColumnLabel(i);//可以获取列的别名
// 通过反射,给对象的属性值进行赋值
Field field = clazz.getDeclaredField(propertyName);
field.setAccessible(true);//属性可以设置,打破private私有修饰
/*
* 参数一:要赋值的对象 如果属性是静态,第一个参数可以为null
* 参数二:具体的属性值
* */
field.set(t,value);
}
list.add(t);
}
// 关闭资源
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
// 没有事务可以关闭
JdbcUtilsV2.freeConnection();
}
return list;
}