课程笔记说明
我的笔记是来源于尚硅谷的赵伟风老师的2023年最新版jdbc的课程
视频链接:
尚硅谷JDBC实战教程(2023最新版jdbc,JDK17+MySQL8)_哔哩哔哩_bilibili
课程资料:
关注“尚硅谷教育”,后台回复JDBC,可以获得对应的资源
我将我的笔记和练习时所使用的代码全部上传到了我的Gitee仓库中,仓库链接:
Jennifer/learn_JDBC - 码云 - 开源中国 (gitee.com)
如有侵权,请及时联系 !
为什么要学习JDBC?
- java和数据库连接的纽带
- 数据库 层框架 底层原理
课程目录
JDBC技术概述
JDBC:Java Database Connectivity,Java 连接数据库技术!
通俗点说,在Java代码中,使用JDBC提供的方法,可以发送字符串类型的SQL语句到数据库管理软件(MySQL,Oracle等),并且获取语句执行结果!
进而实现数据库数据CURD操作的技术!
总结:
- jdbc是java连接数据库技术的统称
- jdbc是由两部分组成:一是Java提供的jdbc规范(接口),存储在java.sql和javax.sql包中的api,二是各个数据库厂商的实现驱动jar包
- jdbc技术是一种典型的面向接口编程
优势
- 我们只需要学习jdbc接口规定方法,即可操作所有数据库软件
- 项目中期需要切换数据库,我们只需要更新第三方驱动jar包,不需要更改代码
JDBC的核心技术
一是Java提供的jdbc规范(接口),存储在java.sql和javax.sql包中的api
二是各个数据库厂商的实现驱动jar包
涉及到的核心类和接口
DriverManager、Connection、PrepareStatement、Result
JDBC核心API
引入MySQL-JDBC驱动jar
我的MySQL版本是8.0.33
我的mysql-connector-j-8.0.33.jar
是自己下载的,从maven仓库中下载的
Maven Repository: mysql » mysql-connector-java » 8.0.33 (mvnrepository.com)
一般来说手动下载jar包从这里找
但是
注意到以下内容:
跳转
是可以下载的,就是没找到对应的src文件
将上面下载的jar包放入idea,并导入为库
效果:
JDBC基本使用步骤分析(6步)
1.注册驱动依赖的jar包进行安装
2.建立连接 connection
3.创建发送SQL语句对象的statement
4.statement对象,发送SQL语句到数据库并获取返回结果
5.解析结果集
6.销毁资源
基于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;
关键代码
package com.tencent.api.statement;
import com.mysql.cj.jdbc.Driver;
import java.sql.*;
public class StatementQuery {
public static void main(String[] args) throws SQLException {
//注册驱动
/**
* 注册驱动
* 依赖:驱动版本8+ 选择com.mysql.cj.jdbc.Driver
* 驱动版本5+ 选择com.mysql.jdbc.Driver
*/
DriverManager.registerDriver(new Driver());
/**
* java程序要和数据库建立连接
* 肯定也需要调用某个方法,方法中需要填入连接数据库的基本信息
* 数据库ip地址:127.0.0.1
* 数据库端口号:3306
* 账号:root
* 密码:1234
* 连接数据库的名称:atguigu
*/
/**
* 参数1:url
* jdbc:数据库厂商名://ip地址:port/数据库名
* 参数2:username 数据库软件的账号 root
* 参数3:password 数据库软件的密码 1234
*/
//建立连接
//java.sql 接口=实现类
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");
//创建statement声明
Statement statement = connection.createStatement();
//发送sql语句,并获取返回结果
String sql = "select id,account,PASSWORD,nickname 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);
}
//释放资源
resultSet.close();
statement.close();
connection.close();
}
}
基于statement方式的问题
数据库和上一节的一致
目标是模拟登录,控制台输入账号密码,判断是否登录成功
关键代码和注释
package com.tencent.api.statement;
import com.mysql.cj.jdbc.Driver;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* 目标:
* 1.明确jdbc的使用流程 和 详细讲解内部设计API的步骤
* 2.发现问题,引出prepareStatement
* 需求:
* 1.输入账号密码
* 2.进行数据库信息查询(atguigu u_ser)
* 3.反馈登录成功或者失败
*基本流程:
* 1.键盘输入事件,收集账号和密码信息
* 2.注册驱动
* 3.获取连接
* 4.创建Statement
* 5.发送sql语句,获取结果
* 6.结果判断,显示登录成功还是失败
* 7.关闭资源
*/
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();
/**
* 方案1:
* DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
* 问题:注册两次驱动
* DriverManager.registerDriver()方法本身会注册一次
* new Driver()注册一次,因为Driver的无参构造函数中有静态代码块,Driver.static{DriverManagger.registerDriver()}
* 解决:只想注册一次驱动
* 只触发静态代码块即可 Driver
* 触发静态代码块:
* 类加载机制:类加载的时刻,会触发静态代码块
* 加载 【class文件->jvm虚拟机的class对象】
* 连接 【验证(检查文件类型)->准备(静态变量初始值)->解析(触发静态代码块】
* 初始化(静态属性赋真实值)
* 以下7种方式会触发类加载:
* 1. new关键字
* 2. 调用静态属性
* 3. 调用静态方法
* 4. 接口 包含1.8 新特性 default关键字
* 5. 反射 【Class.forName() 类名.class】
* 6. 子类调用会触发父类的静态代码块
* 7. 触发类的入口方法main
*
*/
//2.注册驱动
//方法1:两次注册
// DriverManager.registerDriver(new Driver());
//方法2:不够优雅 这只能适合于MySQL,oracle不适用,以后换数据库得改代码
// new Driver();
//方式3:反射,触发类加载,触发静态静态代码块的调用。字符串可以提取到外部的配置文件,在不改变代码的情况下,完成数据库驱动的切换
Class.forName("com.mysql.cj.jdbc.Driver");
/**
* 重写: 为了子类扩展父类的方法!父类也间接的规范了子类方法的参数和返回!
* 重载: 重载一般应用在第三方的工具类上,为了方便用户多种方式传递参数形式!简化形式!
*/
/**
* 三个参数:
* String URL: 连接数据库地址
* String user: 连接数据库用户名
* String password: 连接数据库用户对应的密码
* 数据库URL语法:
* JDBC:
* jdbc:mysql | jdbc:oracle :// 127.0.0.1 | localhost : 3306 / 数据库名
* 举例:
* jdbc:mysql://localhost:3306/day01
* jdbc:mysql://192.168.33.45/3306/day01
* 当前电脑的省略写法! 注意:本机和端口3306
* jdbc:mysql://localhost:3306/day01 = jdbc:mysql:///day01
*
* 两个参数:
* String URL : 和三个参数的url作用一样。 jdbc:mysql://127.0.0.1:3306/gtguigu
* Properties : 就是一个参数封装容器!至少要包含 user / password key!存储连接账号信息!
* properties类似于Map,只不过key=value 都是字符串形式的
* key user : 账号信息
* key password : 密码信息
* 一个参数:
* String URL: URl可以携带目标地址,可以通过?分割,在后面key=value&key=value形式传递参数
* jdbc:mysql:///day01?user=root&password=123456
* 携带固定的参数名 user password 传递账号和密码信息
* 扩展路径参数(了解):
* serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
*
*/
//3.获取连接
//三个参数
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");
//两个参数
// Properties info = new Properties();
// info.put("uer","root");
// info.put("password","1234");
// Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", info);
//一个参数
// Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu?user=root&password=1234");
//4.创建Statement "小汽车"
Statement statement = connection.createStatement();
//5.发送sql语句,获取结果
String sql = "select account,PASSWORD,nickname from t_user where account='"+account+"' and PASSWORD='"+password+"';";
//上面有易错点:sql语句为 :select account,PASSWORD,nickname from t_user where account='root' and PASSWORD='123456';
//在用变量平替的时候,单引号不能去掉!!!!!!
/**
* SQL分类:DDL(容器创建,修改,删除),DML(插入,修改,删除);DQL(查询);DCL(权限控制); TPL(事务控制)
*
* ResultSet 结果集对象 = executeQuery(DQL语句)
*
* int 响应行数 = executeUpdate(非DQL语句)
* 情况1:DML 返回影响的行数
* 情况2:非DML,返回0
*/
// int i = statement.executeUpdate(sql);
ResultSet resultSet = statement.executeQuery(sql);
/**
*
* 你必须有面向对象的思维:Java是面向对象编程的语言 OOP!
*
* 1.需要理解ResultSet的数据结构和小海豚查询出来的是一样,需要在脑子里构建结果表!
* 2.有一个光标指向的操作数据行,默认指向第一行的上边!
* 我们需要 1.移动光标,指向行, 2.再获取列
* 1.游标移动问题:
* resultSet内部包含一个游标,指定当前数据
* 默认指在第一行数据之前
* boolean = next()
* false: 没有数据,也不移动了!
* true: 有更多行,并且移动到下一行!
* 推荐:推荐使用if 或者 while循环,嵌套next方法,循环和判断体内获取数据!
* if(next()){获取列的数据!} || while(next()){获取列的数据!}
*
* 3.获取当前行列的数据!
* resultSet.get类型(int columnIndex | String columnLabel)
* 列名获取 //label 如果没有别名,等于列名, 有别名label就是别名,他就是查询结果的标识!
* 列的角标 //从左到右 从1开始! 数据库全是从1开始!
*/
//6.结果判断,显示登录成功还是失败
// while (resultSet.next()){
// //已经指定当前行了
// int id = resultSet.getInt("id");
// String account1 = resultSet.getString("account");
// String password1 = resultSet.getString("PASSWORD");
// String nickname = resultSet.getString("nickname");
// System.out.println(id+" "+account1+" "+password1+" "+nickname);
// }
//移动一次光标,只要有数据,就代表登录成功
if(resultSet.next()){
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
//7.关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
存在问题
- SQL语句需要字符串拼接,比较麻烦
- 只能拼接字符串类型,其他的数据库类型无法处理
- SQL注入风险
基于preparedStatement方式优化
易错点:
//编写SQL语句
String sql = "select * from t_user where account = ? and password = ? ;";
//创建预编译statement并且设置SQL语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//单独的占位符进行赋值
/**
* 参数1:index 占位符的位置 从左向右数 从1开始
* 参数2:object 占位符的值 可以设置任何类型的数据,避免了拼接,使类型更加丰富
*/
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
//发送SQL语句,获取返回的结果
ResultSet resultSet = preparedStatement.executeQuery();//易错点:注意,这里不加参数!!!!!
关键代码和注释
package com.tencent.api.preparedstatement;
import java.sql.*;
import java.util.Scanner;
/**
* TODO:防止注入攻击 | 演示ps的使用流程
*
*/
public class PSUserLoginPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner scanner = new Scanner(System.in);
System.out.println("输入账号:");
String account = scanner.nextLine();
System.out.println("输入密码:");
String password = scanner.nextLine();
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");
/**
*
* statement
* 1.创建statement
* 2.拼接SQL语句
* 3.发送SQL语句,获取返回的结果
*
* preparedStatement
* 1.编写SQL语句结果 不包含动态值部分的语句,动态值部分用占位符 ? 代替, 注意:?只能替代动态值
* 2.创建preparedStatement,并传入动态值
* 3.动态值 占位符 赋值 ? 单独 赋值即可
* 4.发送SQL语句,获取返回的结果
*/
// //创建statement
// Statement statement = connection.createStatement();
//
// //发送sql语句并得到返回结果
// String sql = "select account,PASSWORD,nickname from t_user where account='"+account+"' and PASSWORD='"+password+"';";
// ResultSet resultSet = statement.executeQuery(sql);
//编写SQL语句
String sql = "select * from t_user where account = ? and password = ? ;";
//创建预编译statement并且设置SQL语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//单独的占位符进行赋值
/**
* 参数1:index 占位符的位置 从左向右数 从1开始
* 参数2:object 占位符的值 可以设置任何类型的数据,避免了拼接,使类型更加丰富
*/
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
//发送SQL语句,获取返回的结果
ResultSet resultSet = preparedStatement.executeQuery();//易错点:注意,这里不加参数!!!!!
//解析结果
if(resultSet.next()){
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
//释放资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
基于preparedStatement进行CURD 增删改查(范例)
关键代码
package com.tencent.api.preparedstatement;
import org.junit.Test;
import java.sql.*;
import java.util.*;
public class PSCURDPart {
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "1234");
//编写SQL语句,动态值的部分使用?代替
String sql ="INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
//创建preparedStatement,并且传入SQL语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//占位符赋值
preparedStatement.setObject(1,"test");
preparedStatement.setObject(2,"test");
preparedStatement.setObject(3,"二狗砸");
//发送SQL语句,并接收
int rows = preparedStatement.executeUpdate();
//解析结果
if(rows>0){
System.out.println("数据插入成功!");
}else {
System.out.println("数据插入失败!");
}
//释放资源
preparedStatement.close();
connection.close();
}
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
//update t_user set nickname='三狗子' where id = 3;
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
//编写SQL语句,动态值的部分使用?代替
String sql ="update t_user set nickname=? where account = ?";
//创建preparedStatement,并且传入SQL语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//占位符赋值
preparedStatement.setObject(1,"wangjiajia");
preparedStatement.setObject(2,"test");
//发送SQL语句,并接收
int rows = preparedStatement.executeUpdate();
//解析结果
if (rows>0){
System.out.println("更新成功!");
}else {
System.out.println("更新失败!");
}
//释放资源
preparedStatement.close();
connection.close();
}
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
//delete from t_user where account='test';
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "delete from t_user where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,"test");
int rows = preparedStatement.executeUpdate();
if (rows>0)
System.out.println("删除成功!");
else
System.out.println("删除失败!");
preparedStatement.close();
connection.close();
}
/**
* 目标:查询所用用户数据,并且封装到一个Lis<Map> list集合中
*
* 数据库 -> resultSet -> java -> 一行 - map(key=列名,value=列的内容) -> Lis<Map> list
*
* 实现思路:
* 遍历行数据,一行对应一个map,获取一行的列名和对应的列的属性,装配即可
* 将map装到一个集合就可以了
*
* 难点:
* 如何获取列的名称?
*
* @throws ClassNotFoundException
* @throws SQLException
*/
@Test
public void testSelect() throws ClassNotFoundException, SQLException {
//select id,account,PASSWORD,nickname from t_user;
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "select id,account,PASSWORD,nickname from t_user";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// preparedStatement.setObject(1,"test");
ResultSet resultSet = preparedStatement.executeQuery();
// while (resultSet.next()){
// String account = resultSet.getString("account");
// String password = resultSet.getString("password");
// String nickname = resultSet.getString("nickname");
// System.out.println(account+password+nickname);
// }
List<Map> list = new ArrayList<>();
//获取列的信息对象
//metaData装的就是当前结果集列的信息对象(可以获取列的名称根据下标,可以获取列的数量
ResultSetMetaData metaData = resultSet.getMetaData();
// 获取列的数量,有了它以后可以水平遍历列
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
Map map = new HashMap();
//一行数据对应一个map
// //纯手动取值
// map.put("id",resultSet.getString("id"));
// map.put("account",resultSet.getString("account"));
// map.put("password",resultSet.getString("password"));
// map.put("nickname",resultSet.getString("nickname"));
//自动水平遍历列,从1开始,因为数据库中下标从1开始;并且小于等于总列数
for (int i = 1; i <= columnCount; i++) {
//获取指定列下标的值
Object value = resultSet.getObject(i);
//获取指定列下标的列的名称
//getColumnLabel:会优先获取列的别名,没有别名选名称; 不要使用getColumnName:只会获取列名
String columnLabel = metaData.getColumnLabel(i);
map.put(columnLabel,value);
}
//一行数据中的所有列全部存到了map中
//将map存储到集合中即可
list.add(map);
}
System.out.println(list);
resultSet.close();
preparedStatement.close();
connection.close();
}
//自己复现一遍
@Test
public void testSelectOK() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "select * from t_user;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
List<Map> list = new ArrayList<>();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
Map map = new HashMap();
for (int i = 1; i <= columnCount; i++) {
Object object = resultSet.getObject(i);
String columnLabel = metaData.getColumnLabel(i);
map.put(columnLabel,object);
}
list.add(map);
}
System.out.println(list);
resultSet.close();
preparedStatement.close();
connection.close();
}
}
JDBC扩展提升
自增长主键回显实现
功能需求
- java程序获取插入数据时,MySQL维护自增长的主键id值,就是主键回显
- 作用:在多表关联插入数据时,一般主表的主键都是自动生成的,所以在插入数据之前无法知道这条数据的主键,但是从表需要在插入数据前就绑定主表的主键,这可以使用主键回显技术
- 简单理解:auto increment的主键的值需要返回给程序
功能实现
核心实现代码:
//创建preparedStatement的时候,告知,携带回数据库自增长的主键
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//获取司机装主键值的结果集对象,一行一列,获取对应的数据即可
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();//移动一下光标
int id = generatedKeys.getInt(1);
System.out.println("id:"+id);
继续使用前面的数据库
/**
* 练习preparedStatement的特殊使用情况
*/
public class PSOtherPart {
/**
* Todo:
* t_user插入一条数据,并且获取数据库自增长的主键
*
* Todo:使用总结
* 1.创建preparedStatement的时候,告知,携带回数据库自增长的主键(sql,Statement.RETURN_GENERATED_KEYS)
* 2.获取司机装主键值的结果集对象,一行一列,获取对应的数据即可(ResultSet generatedKeys = preparedStatement.getGeneratedKeys();)
*/
@Test
public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//创建连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
//写SQL语句
String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
//创建preparedStatement
//创建preparedStatement的时候,告知,携带回数据库自增长的主键
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
//动态绑定数据内容,占位符赋值
preparedStatement.setObject(1,"test1");
preparedStatement.setObject(2,"123456");
preparedStatement.setObject(3,"驴蛋蛋");
//发送 ,查询,得到结果
int rows = preparedStatement.executeUpdate();
//解析结果
if(rows>0){
System.out.println("插入成功!");
//可以获取回显的主键
//获取司机装主键值的结果集对象,一行一列,获取对应的数据即可
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();//移动一下光标
int id = generatedKeys.getInt(1);
System.out.println("id:"+id);
}else {
System.out.println("插入失败!");
}
//释放资源
preparedStatement.close();
connection.close();
}
批量数据数据插入性能提升
批量插入核心代码
/**
* TODO:总结批量插入
* 1.路径后面添加?rewriteBatchedStatements=true 允许批量插入
* 2.insert into values,不能是value,最后不能写;
* 3.不是执行语句每条,而是批量添加 addBatch()
* 4.遍历 添加完毕后,统一 批量执行executeBatch()
*/
//创建连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true", "root", "1234");
String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?)";//一定要写values,不能是value
//动态绑定数据内容,占位符赋值
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1,"yba"+i);
preparedStatement.setObject(2,"wxja"+i);
preparedStatement.setObject(3,"wjja"+i);
//不执行,追加到values的后面
preparedStatement.addBatch();
}
//执行批量操作
preparedStatement.executeBatch();
普通插入耗时5285ms
/**
* 使用普通的方式创建10000条数据
* @throws ClassNotFoundException
* @throws SQLException
*/
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//创建连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
//写SQL语句
String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
//创建preparedStatement
//创建preparedStatement的时候,告知,携带回数据库自增长的主键
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
//动态绑定数据内容,占位符赋值
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1,"yb"+i);
preparedStatement.setObject(2,"wxj"+i);
preparedStatement.setObject(3,"wjj"+i);
//发送 ,查询,得到结果
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
//解析结果
System.out.println("执行10000次数据消耗的时间"+(end-start));//执行10000次数据消耗的时间5285
//释放资源
preparedStatement.close();
connection.close();
}
批量插入耗时222ms
/**
* 使用批量的方式创建10000条数据
*
* TODO:总结批量插入
* 1.路径后面添加?rewriteBatchedStatements=true 允许批量插入
* 2.insert into values,不能是value,最后不能写;
* 3.不是执行语句每条,而是批量添加 addBatch()
* 4.遍历 添加完毕后,统一 批量执行executeBatch()
*/
@Test
public void testBatchInsert() throws ClassNotFoundException, SQLException {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//创建连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true", "root", "1234");
//写SQL语句
String sql = "INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?)";//一定要写values,不能是value
//创建preparedStatement
//创建preparedStatement的时候,告知,携带回数据库自增长的主键
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
//动态绑定数据内容,占位符赋值
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1,"yba"+i);
preparedStatement.setObject(2,"wxja"+i);
preparedStatement.setObject(3,"wjja"+i);
//不执行,追加到values的后面
preparedStatement.addBatch();
}
//执行批量操作
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
//解析结果
System.out.println("执行10000次数据消耗的时间"+(end-start));//执行10000次数据消耗的时间5285
//释放资源
preparedStatement.close();
connection.close();
}
JDBC中数据库事务的实现
目标
使用jdbc代码,添加数据库事务动作
- 开启事务
- 事务提交,事务回滚
事务概念回顾
对应在jdbc中的实现
try{
connection.setAutoCommit(false);//关闭自动提交
//只要是当前connection对象进行数据库操作,都不会自动 提交事务
//statement是单一的数据库动作,增删改查等
connection.commit();
}catch(Exception e){
connection.rollback;
}
数据准备
创建银行表并插入两条数据
create table t_bank(
id INT primary key auto_increment comment '账号主键',
account varchar(20) not null unique comment '单号',
money int unsigned comment '金额,不能为负值'
);
insert into t_bank(account, money) VALUES ('ergouzi',1000),('lvdandan',1000);
select * from t_bank;
代码结构设计
Database Access Object(DAO)是一种设计模式,将应用程序的业务逻辑与访问逻辑分离。DAO提供了一个抽象的接口,使应用程序可以访问数据存储。
没有设置事务出现的问题
在没有设置设置事务前,只是逻辑上控制加钱减钱,当银行账户前为负数时,会报错:
查询数据库
发现二者和不是2000了,不能保证数据库事务的一致性要求
这部分的代码实现
package com.tencent.api.transaction;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 银行卡业务方法,调用dao方法
*/
public class BankService {
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
BankDao dao = new BankDao();
dao.add(addAccount,money);
System.out.println("-------------");
dao.sub(subAccount,money);
}
@Test
public void start() throws SQLException, ClassNotFoundException {
transfer("ergouzi","lvdandan",500);
}
}
package com.tencent.api.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* bank表的数据库操作方法存储类
*/
public class BankDao {
/**
* 加钱的数据库操作方法(jdbc)
* @param account 加钱的账号
* @param money 加钱的金额
*/
public void add(String account,int money) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "update t_bank set money=money+? where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
preparedStatement.executeUpdate();
preparedStatement.close();
connection.close();
System.out.println("加钱成功!");
}
/**
* 减钱的数据库操作方法(jdbc)
* @param account 减钱的账号
* @param money 减钱的金额
*/
public void sub(String account,int money) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "update t_bank set money=money-? where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
preparedStatement.executeUpdate();
preparedStatement.close();
connection.close();
System.out.println("减钱成功!");
}
}
jdbc 事务实现
核心代码
/**
*
* TODO:
* 事务添加是在业务方法中(service)
* 利用try catch代码块,开启事务和提交事务,和事务回滚
* 将connection传入dao层即可,dao只负责使用,对connection不要用close()
*/
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
BankDao dao = new BankDao();
//一个事务的最基本的要求,必须是同一个连接对象 connection
//一个转账方法,应该属于同一个事务
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
try {
//开启事务
connection.setAutoCommit(false);//事务的自动提交功能关上!!!
//执行数据库动作
dao.add(addAccount,money,connection);
System.out.println("-------------");
dao.sub(subAccount,money,connection);
//事务提交
connection.commit();
}catch (Exception e){
//事务回滚
connection.rollback();
//抛出异常
throw e;
}finally {
connection.close();//关闭连接资源
}
}
关键代码(看注释)
package com.tencent.api.transaction;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 银行卡业务方法,调用dao方法
*/
public class BankService {
/**
*
* TODO:
* 事务添加是在业务方法中(service)
* 利用try catch代码块,开启事务和提交事务,和事务回滚
* 将connection传入dao层即可,dao只负责使用,对connection不要用close() *
* @param addAccount
* @param subAccount
* @param money
* @throws SQLException
* @throws ClassNotFoundException
*/
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
BankDao dao = new BankDao();
//一个事务的最基本的要求,必须是同一个连接对象 connection
//一个转账方法,应该属于同一个事务
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
try {
//开启事务
connection.setAutoCommit(false);//事务的自动提交功能关上!!!
//执行数据库动作
dao.add(addAccount,money,connection);
System.out.println("-------------");
dao.sub(subAccount,money,connection);
//事务提交
connection.commit();
}catch (Exception e){
//事务回滚
connection.rollback();
//抛出异常
throw e;
}finally {
connection.close();
}
}
@Test
public void start() throws SQLException, ClassNotFoundException {
transfer("ergouzi","lvdandan",500);
}
}
package com.tencent.api.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* bank表的数据库操作方法存储类
*/
public class BankDao {
/**
* 加钱的数据库操作方法(jdbc)
* @param account 加钱的账号
* @param money 加钱的金额
*/
public void add(String account,int money,Connection connection) throws SQLException {
// Class.forName("com.mysql.cj.jdbc.Driver");
// Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "update t_bank set money=money+? where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
preparedStatement.executeUpdate();
preparedStatement.close();
// connection.close();
System.out.println("加钱成功!");
}
/**
* 减钱的数据库操作方法(jdbc)
* @param account 减钱的账号
* @param money 减钱的金额
*/
public void sub(String account,int money,Connection connection) throws SQLException {
// Class.forName("com.mysql.cj.jdbc.Driver");
// Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "update t_bank set money=money-? where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
preparedStatement.executeUpdate();
preparedStatement.close();
// connection.close();
System.out.println("减钱成功!");
}
}
Druid连接池技术使用
连接池性能消耗问题分析
connection可以复用!
数据库连接池的作用
市面常见的连接池产品和对比
javax.sql.DataSource接口,规范了连接池获取连接和回收连接的方法
DataSource = 第三方连接池实现
国货之光Druid连接池的使用
记得导入Druid工具类jar
硬编码方式(了解,不推荐)
/**
* 直接使用代码设置连接池连接参数方式
* 1.创建一个Druid连接池对象
* 2.设置连接池参数 【必须 | 非必须】
* 3.获取连接 【通用方法,所有连接池都一样】
* 4.回收连接 【通用方法,所有连接池都一样】
*/
public void testHard() throws SQLException {
//连接池对象
DruidDataSource dataSource = new DruidDataSource();
//设置参数
//必须 连接数据库驱动类的全限定符 [注册驱动] 和url和user和password
//帮我们完成驱动注册和连接
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/atguigu");
dataSource.setUsername("root");
dataSource.setPassword("1234");
dataSource.setDriverClassName("com.mysql.cj.jdbc.driver");
//非必须 初始化连接数量 最大的连接数量。。。
dataSource.setInitialSize(5);//初始化连接数量
dataSource.setMaxActive(10);//最大的连接数量
//获取连接
// DruidPooledConnection connection = dataSource.getConnection();
Connection connection = dataSource.getConnection();//用父接口类接收即可
//数据库CURD
//回收链接
connection.close();//连接池提供的连接,close,就是回收连接
}
软编码方式(推荐)
外部配置
- 存放在
src/druid.properties
中
#key = value => java properties读取 (key|value)
#druid配置的key固定命名
driverClassname=com.mysql.cj.jdbc.Driver
username=root
password=1234
url=jdbc:mysql://127.0.0.1/atguigu
Druid声明
/**
* 通过读取外部配置文件的方法,实例化druid连接池对象
*/
@Test
public void testSoft() throws Exception {
//1.读取外部配置文件 properties
Properties properties = new Properties();
//src下的文件,可以使用类加载器提供的方法
InputStream ips = DruidUsedPart.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(ips);
//2.使用连接池的工具类的工程模式,创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
//数据库的CURD
connection.close();
}
JDBC使用优化以及工具类封装
jdbc工具类封装v1.0:将druid连接池对象、连接和释放方法封装到工具类
我们封装一个工具类内部包含连接池对象,同时对外提供连接的方法和回收连接的方法
外部配置文件
- 存放在
src/druid.properties
中
#key = value => java properties读取 (key|value)
#druid配置的key固定命名
driverClassname=com.mysql.cj.jdbc.Driver
username=root
password=1234
url=jdbc:mysql://127.0.0.1/atguigu
工具类代码
/**
* v1.0版本的工具类
* 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
*
* 小建议:
* 工具类的方法,推荐写成静态的,外部可以通过类名.方法()的方式调用,更加方便
*
* 实现:
* 属性:连接池对象 【实例化一次】
* 单例模式
* 静态代码块:
* static{
* 全局调用一次
* }
* 方法:
* 对外提供连接的方法
* 回收外部传入连接的方法
*/
public class JDBCUtils {
private static DataSource dataSource = null;//连接池对象
static{
//初始化连接池对象
//加载外部配置文件
Properties properties = new Properties();
InputStream ips = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(ips);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 对外提供连接的方法
* @return
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void freeConnection(Connection connection) throws SQLException {
connection.close();//连接池的回收,调用close()就是回收
}
}
jdbc工具类封装v2.0:在v1的基础上,考虑事务前提 ,引入ThreadLocal,一个线程的不同方法使用同一个connection对象
对版本1进行优化:考虑事务的前提下,一个线程的不同方法如何获得同一个连接?
ThreadLocal的介绍
使用同一个 线程,调用方法(add()、sub())的时候不需要传入connection对象
代码实现
工具类 utils/JDBCUtilsV2.java
中
/**
* v1.0版本的工具类
* 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
*
* 小建议:
* 工具类的方法,推荐写成静态的,外部可以通过类名.方法()的方式调用,更加方便
*
* 实现:
* 属性:连接池对象 【实例化一次】
* 单例模式
* 静态代码块:
* static{
* 全局调用一次
* }
* 方法:
* 对外提供连接的方法
* 回收外部传入连接的方法
*
* TODO:
* 利用线程本地变量,存储连接信息,确保一个线程的方法可以获取同一个connection
* 优势:事务操作时,service和dao属于同一个线程,不用再传递参数了
* 大家都可以调用getConnection自动获取的是相同的连接池
*/
public class JDBCUtilsV2 {
private static DataSource dataSource = null;//连接池对象
private static ThreadLocal<Connection> tl =new ThreadLocal<>();//声明线程本地变量
static{
//初始化连接池对象
//加载外部配置文件
Properties properties = new Properties();
InputStream ips = JDBCUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(ips);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 对外提供连接的方法
* @return
*/
public static Connection getConnection() throws SQLException {
//线程本地变量中是否存在
Connection connection = tl.get();
//第一次没有
if (connection == null){
//线程本地变量没有,连接池获取
connection = dataSource.getConnection();
//把刚刚获取的connection存到本地变量
tl.set(connection);
}
return connection;
}
public static void freeConnection() throws SQLException {//注意这里不需要传入connection参数
Connection connection = tl.get();
if (connection != null) {
tl.remove();//清空线程本地变量数据
connection.setAutoCommit(true);//事务状态回归
connection.close();//会收到连接池即可
}
}
}
新的事务类方法
BankDao.java
中
/**
* bank表的数据库操作方法存储类
*/
public class BankDao {
/**
* 加钱的数据库操作方法(jdbc)
* @param account 加钱的账号
* @param money 加钱的金额
*/
public void add(String account,int money) throws SQLException {
//使用新版本的JDBCUtilsV2,可以不在方法声明部分添加Connection变量,可以直接调用工具类JDBCUtilsV2的getConnection()方法
Connection connection = JDBCUtilsV2.getConnection();
String sql = "update t_bank set money=money+? where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
preparedStatement.executeUpdate();
preparedStatement.close();
// connection.close();
System.out.println("加钱成功!");
}
/**
* 减钱的数据库操作方法(jdbc)
* @param account 减钱的账号
* @param money 减钱的金额
*/
public void sub(String account,int money) throws SQLException {
Connection connection = JDBCUtilsV2.getConnection();
String sql = "update t_bank set money=money-? where account=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,money);
preparedStatement.setObject(2,account);
preparedStatement.executeUpdate();
preparedStatement.close();
// connection.close();
System.out.println("减钱成功!");
}
}
BankService
中
public class BankService {
/**
*
* TODO:
* 事务添加是在业务方法中(service)
* 利用try catch代码块,开启事务和提交事务,和事务回滚
* 将connection传入dao层即可,dao只负责使用,对connection不要用close() *
*
* @param addAccount
* @param subAccount
* @param money
* @throws SQLException
* @throws ClassNotFoundException
*/
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
BankDao dao = new BankDao();
//一个事务的最基本的要求,必须是同一个连接对象 connection
//一个转账方法,应该属于同一个事务
//下面内容也省略了
// Class.forName("com.mysql.cj.jdbc.Driver");
// Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
//新
Connection connection = JDBCUtilsV2.getConnection();
try {
//开启事务
connection.setAutoCommit(false);//事务的自动提交功能关上!!!
//执行数据库动作
// dao.add(addAccount,money,connection);不需要传递connection
dao.add(addAccount,money);
System.out.println("-------------");
// dao.sub(subAccount,money,connection);
dao.sub(subAccount,money);
//事务提交
connection.commit();
}catch (Exception e){
//事务回滚
connection.rollback();
//抛出异常
throw e;
}finally {
// connection.close();
JDBCUtilsV2.freeConnection();
}
}
@Test
public void start() throws SQLException, ClassNotFoundException {
transfer("ergouzi","lvdandan",500);
}
}
高级应用层封装BaseDAO
基本上每个数据表都应该有个对应的DAO接口及其实现类,发现对所有表的操作(增删改查)代码的重复度很高。所以可以抽取公共代码段,给这些DAO的实现类可以抽取一个公共的父类,我们称之为BaseDAO
BaseDao源码
package com.atguigu.cms.utils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 封装Dao数据库重复代码
* 封装两个方法:
* 一个简化非DQL
* 一个简化DQL
*/
public abstract class BaseDao {
/**
* 封装简化非DQL语句
* @param sql 带占位符的SQL语句
* @param params 占位符的值,注意,传入SQL语句的值和SQL语句中?的位置要一一对应
* @return 执行影响的行数
*/
public int executeUpdate(String sql,Object ... params) throws SQLException {
Connection connection = JDBCUtilsV2.getConnection();
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]);
}
}
int rows = preparedStatement.executeUpdate();
preparedStatement.close();
//是否回收连接,需要考虑是不是事务
// connection.setAutoCommit(false);//开启事务了,不要关连接了,业务层service处理
if (connection.getAutoCommit()) {
//没有开启事务,正常回收连接
JDBCUtilsV2.freeConnection();//记得调用工具类,不要直接写connection.close()
}
return rows;
}
/**
* 非DQL语句封装方法的返回值固定为int
*
* DQL语句封装方法的返回值是什么呢? List<T>
*
* 并不是list<Map> map key和value自定义,不能也不用先设定好
* map 没有校验机制
* map 不支持反射操作
*
* 数据库数据应该和java的实体类对应
*
* table
* t_user
* id
* account
* password
* nickname
* java
* User
* id
* account
* password
* nickname
* 表中一行应对应java实体类中的一个对象,多行对应List<java实体类> list
*
* <T>声明一个泛型,不确定类型
* 1.确定泛型 User.class T = class
* 2.要使用反射技术属性赋值
*
*
* 查询结果封装到一个实体类集合
* @param clazz 要接值的实体类集合的模板对象
* @param sql 查询语句,要求列名或者别名等于实体类的属性名!!!一定要格外注意
* @param parameters 占位符的值,要和?位置对应
* @param <T> 声明的结果的类型
* @return 声明的结果的类型
*/
public <T> List<T> executeQuery(Class<T> clazz,String sql,Object ... parameters) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Connection connection = JDBCUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if (parameters != null && parameters.length !=0){
for (int i = 1; i <= parameters.length; i++) {
preparedStatement.setObject(i,parameters[i-1]);
}
}
ResultSet resultSet = preparedStatement.executeQuery();
//这个结果集中不装list<Map>了,而是装T的结果集
List<T> list = new ArrayList<>();
//获取列的数据
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
//一行数据,对应一个实体类T
//调用类的无参构造函数实例化对象
T t = clazz.newInstance();
//按列遍历
for (int i = 1; i <= columnCount; i++) {
//对象的属性值
Object value = resultSet.getObject(i);
//对象的属性名
String columnLabel = metaData.getColumnLabel(i);
//反射,给对象的属性值赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);//属性可以设置,打破private的修饰限制
/**
* 参数1:要赋值的对象,如果属性是静态属性,第一个参数可以为null
* 参数2:具体的属性值
*/
field.set(t,value);
}
//将t存储到集合中
list.add(t);
}
resultSet.close();
preparedStatement.close();
if (connection.getAutoCommit()) {
JDBCUtilsV2.freeConnection();
}
return list;
}
}
测试BaseDao(utils包下面的PSCURDPart.java)
package com.tencent.api.utils;
import com.tencent.api.transaction.BankDao;
import org.junit.Test;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class PSCURDPart extends BaseDao{
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
String sql ="INSERT INTO t_user(account, PASSWORD, nickname) values (?,?,?);";
int rows = executeUpdate(sql, "test", "testpsword", "不知道对不对");
if(rows>0){
System.out.println("数据插入成功!");
}else {
System.out.println("数据插入失败!");
}
}
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
//update t_user set nickname='三狗子' where id = 3;
//编写SQL语句,动态值的部分使用?代替
String sql ="update t_user set nickname=? where account = ?";
int rows = executeUpdate(sql,"wxj1234","test");
//解析结果
if (rows>0){
System.out.println("更新成功!");
}else {
System.out.println("更新失败!");
}
}
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
String sql = "delete from t_user where account=?;";
int rows = executeUpdate(sql,"test");
if (rows>0)
System.out.println("删除成功!");
else
System.out.println("删除失败!");
}
/**
* 目标:查询所用用户数据,并且封装到一个Lis<Map> list集合中
*
* 数据库 -> resultSet -> java -> 一行 - map(key=列名,value=列的内容) -> Lis<Map> list
*
* 实现思路:
* 遍历行数据,一行对应一个map,获取一行的列名和对应的列的属性,装配即可
* 将map装到一个集合就可以了
*
* 难点:
* 如何获取列的名称?
*
* @throws ClassNotFoundException
* @throws SQLException
*/
//自己复现一遍
@Test
public void testSelectOK() throws ClassNotFoundException, SQLException {
//这里暂时用不了查询方法,因为整个体系结构中没有设置实体类
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "1234");
String sql = "select * from t_user;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
List<Map> list = new ArrayList<>();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
Map map = new HashMap();
for (int i = 1; i <= columnCount; i++) {
Object object = resultSet.getObject(i);
String columnLabel = metaData.getColumnLabel(i);
map.put(columnLabel,object);
}
list.add(map);
}
System.out.println(list);
resultSet.close();
preparedStatement.close();
connection.close();
}
}
基于CMS项目JDBC实战练习
准备工作
-
导入项目(我基本不需要改动东西)
-
创建表格
show databases ;
use atguigu;
create table t_customer(
id int primary key auto_increment comment '客户主键',
name varchar(20) comment '客户姓名',
gender varchar(4) comment '客户性别',
age int,
salary double(8,1),
phone varchar(11)
)
- idea和数据库建立连接
-
修改配置文件成自己数据库的连接方式
driverClassName=com.mysql.cj.jdbc.Driver username=root password=1234 url=jdbc:mysql:///atguigu initialSize=5
-
导入baseDao工具类和JDBCUtils
-
导入依赖
(不知道为啥课程资料给的不是视频中所述的原版资料,所以领会思想好了)
实战改造
主要是改变了CustomerService.java文件,生成了CustomerDao.java文件
CustomerService.java
public class CustomerService {
private CustomerDao customerDao = new CustomerDao();
/**
* 用途:查询数据库客户集合
* 返回:集合
*/
public List<Customer> getList() throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
return customerDao.findAll();
}
/**
* 用途:添加新客户
* 参数:customer指定要添加的客户对象
*/
public void addCustomer(Customer customer) throws SQLException {
customerDao.addCustomer(customer);
}
/**
* 用途:返回指定id的客户对象记录
* 参数: id 就是要获取的客户的id号.
* 返回:封装了客户信息的Customer对象
*/
public Customer getCustomer(int id) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
return customerDao.findById(id);
}
/**
* 修改指定id号的客户对象的信息
* @param id 客户id
* @param cust 对象
* @return 修改成功返回true, false表明指定id的客户未找到
*/
public boolean modifyCustomer(int id, Customer cust) throws SQLException {
int rows = customerDao.updateById(cust,id);
if (rows>0){
return true;
}else {
return false;
}
}
/**
* 用途:删除指定id号的的客户对象记录
* 参数: id 要删除的客户的id号
* 返回:删除成功返回true;false表示没有找到
*/
public boolean removeCustomer(int id) throws SQLException {
int rows = customerDao.removeCustomerById(id);
if (rows>0){
return true;
}else {
return false;
}
}
}
CustomerDao.java
public class CustomerDao extends BaseDao {
public List<Customer> findAll() throws SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
String sql = "select * from t_customer";
List<Customer> customers = executeQuery(Customer.class, sql);
return customers;
}
public Customer findById(int id) throws SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
String sql = "select * from t_customer where id=?";
List<Customer> customers = executeQuery(Customer.class, sql,id);
//返回的是集合,需要处理成一个
if(customers != null && customers.size()>0){
return customers.get(0);
}
return null;
}
public void addCustomer(Customer customer) throws SQLException {
String sql = "insert into t_customer(name, gender, age, salary, phone) VALUES (?,?,?,?,?)";
executeUpdate(sql, customer.getName(), customer.getGender(), customer.getAge(), customer.getSalary(), customer.getPhone());
}
public int updateById(Customer customer, int id) throws SQLException {
String sql = "update t_customer set name=?,gender=?,age=?,salary=?,phone=? where id = ?;";
int rows = executeUpdate(sql, customer.getName(), customer.getGender(), customer.getAge(), customer.getSalary(), customer.getPhone(), customer.getId());
return rows;
}
public int removeCustomerById(int id) throws SQLException {
String sql = "delete from t_customer where id = ?;";
int rows = executeUpdate(sql, id);
return rows;
}
}