尚硅谷JDBC笔记
核心api
java.sql和javax.sql
过程
-
注册驱动
-
创建链接
-
创建发送SQL语句对象
-
发送SQL语句,获取结果
-
结果解析
-
释放资源
8.0.25版本驱动,自动识别时区 serverTimeZone=Asia/Shanghai 不用添加
8.0.25之前的版本,添加以下:
t_user表
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','管理员');
getConnection()
url:jdbc:数据库管理软件名称[nysql,oracle]😕/ip地址|主机名:端口号/数据库名?key=value&key=value 可选信息
//2.获取连接
//方式一
Connection connection = DriverManager.getConnection("jdbc:mysql//127.0.0.1:3306/atguigu", "root", "123456");
//方式二
Properties info = new Properties();
info.put("user","root");
info.put("password","123456");
Connection connection1 = DriverManager.getConnection("jdbc:mysql//127.0.0.1:3306/atguigu", info);
//方式三
Connection connection2 = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu?user=root&password=123456");
statement
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "123456");
Statement statement = connection.createStatement();
String sql = "select * from t_user";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
sout(resultSet.getString("字段名"));
sout(resultSet.getIndex(1));//根据下标,从1开始
}
close();
preparedstatement
动态值使用占位符替代,防止sql注入攻击
//1.收集用户信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号");
String account = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
//2.ps的数据库流程
//2.1注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.2获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "123456");
/**
* statement
* 1.创建statement
* 2.拼接SQL语句
* 3.发送SQL语句,并接收返回结果集
*
* preparedstatement
* 1.编写SQL语句 不包含动态值部分的语句,动态值部分使用占位符? 替代 注意: ?只能替代动态值
* 2.创建perparedstatment,并且传入动态值
* 3.动态值 占位符 赋值 ? 单独赋值即可
* 4.发送SQL语句,并返回结果集
*/
//2.3编写SQL语句结果
String sql = "select * from t_user where account = ? and password = ? ; ";
//2.4创建预编译statement,并且设置SQL语句结果
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//2.5单独的占位符进行赋值
/**
* 参数1:index 占位符的位置,从左往右数 从1开始 账号 ? 1
* 参数2:object 占位符的值 可以设置任何类型的数据,避免了拼接和类型更加丰富
*/
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
//2.6发送sql语句,并获取返回结果
//statement.executeUpdate | statement.executeQuery(Stirng sql)
ResultSet resultSet = preparedStatement.executeQuery();
//2.7解析结果集
if(resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
//2.8关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
主键回滚
@Test
public void returnPrimaryKey() throws Exception {
/**
* 插入一条数据,并获取数据库自增长的主键
* TODO:使用总结
* 1.创建preparedstatement的时候,告知,携带回自增的主键(sql,Statement.RETURN_GENERATED_KEYS)
* 2.获取带回的主键结果集对象,一行一列,获取对应的数据即可 ResultSet resultSet = preparedStatement.getGeneratedKeys();
* */
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "123456");
String sql = "insert into t_user(account,password,nickname) values(?,?,?);";
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1,"test");
preparedStatement.setObject(2,"test");
preparedStatement.setObject(3,"二狗子");
int rows = preparedStatement.executeUpdate();
if(rows > 0){
System.out.println("插入成功");
//可以获取回显的主键
ResultSet resultSet = preparedStatement.getGeneratedKeys();
resultSet.next();
int id = resultSet.getInt(1);
System.out.println("id: " + id);
}
else{
System.out.println("插入失败");
}
}
批量插入数据
普通for循环插入
/**
* 使用普通方法插入10000条数据
*/
@Test
public void testInsert() throws Exception {
/**
* 插入一条数据,并获取数据库自增长的主键
* TODO:使用总结
* 1.创建preparedstatement的时候,告知,携带回自增的主键(sql,Statement.RETURN_GENERATED_KEYS)
* 2.获取带回的主键结果集对象,一行一列,获取对应的数据即可 ResultSet resultSet = preparedStatement.getGeneratedKeys();
* */
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "123456");
String sql = "insert into t_user(account,password,nickname) values(?,?,?);";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1,"dd" + i);
preparedStatement.setObject(2,"dd" + i);
preparedStatement.setObject(3,"二狗子" + i);
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("执行10000次消耗的时间"+(end-start));//20316
preparedStatement.close();
connection.close();
}
优化插入
/**
* 使用批量方法插入10000条数据
*/
@Test
public void testBatchInsert() throws Exception {
/**
* 插入10000条数据
* TODO:使用总结
* 1.路径后面添加 ?rewriteBatchedStatements=true 允许批量插入
* 2.insert into values 语句不能添加分号结束
* 3.不是执行语句每条,是批量添加 addBatch()
* 4.遍历添加完毕,统一执行
* */
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu?rewriteBatchedStatements=true", "root", "123456");
String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1,"ddd" + i);
preparedStatement.setObject(2,"ddd" + i);
preparedStatement.setObject(3,"二狗子" + i);
preparedStatement.addBatch();//不执行,追加到values的后面
}
//追加完毕,统一执行
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("执行10000次消耗的时间"+(end-start));//254
preparedStatement.close();
connection.close();
}
事物
利用try catch开启事物和提交事物
BankService
/**
* 事物添加是在业务方法中
* 利用try catch代码块,开启事务和提交事务,和事物回滚
* 将connection传入dao层即可,dao层只负责使用,不负责关闭连接
* @throws Exception
*/
@Test
public void start() throws Exception {
transfer("lvdandan","ergouzi",500);
}
public void transfer(String addAccount,String subAccount,int money) throws Exception{
BankDao bankDao = new BankDao();
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/atguigu", "root", "123456");
try{
//开启事物
//关闭事物提交
connection.setAutoCommit(false);
//执行数据库动作
//执行数据库动作
bankDao.add(addAccount,money,connection);
System.out.println("--------------------------------");
bankDao.sub(subAccount,money,connection);
//事物提交
connection.commit();
}catch (Exception e){
//事物回滚
connection.rollback();
//抛出
throw e;
}finally {
connection.close();
}
}
bankDao
public class BankDao {
/**
* 加钱的数据库操作方法
* @param account 加钱的账号
* @param money 加钱金额
*/
public void add(String account,int money,Connection connection) throws Exception {
String sql = "update t_bank set money = money + ? where account = ?;";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1,money);
statement.setObject(2,account);
statement.executeUpdate();
statement.close();
System.out.println("加钱成功");
}
/**
* 减钱的数据库操作方法
* @param account 减钱的账号
* @param money 减钱的金额
*/
public void sub(String account,int money,Connection connection) throws Exception {
String sql = "update t_bank set money = money - ? where account = ?;";
PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1,money);
statement.setObject(2,account);
statement.executeUpdate();
statement.close();
System.out.println("减钱成功");
}
}
Druid
硬编码,不推荐
/**
* 直接使用代码设置连接池连接参数方式
* 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("123456");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");//帮助我们进行注册驱动和获取连接
//非必须 初始化连接数量 最大连接数量.....
dataSource.setInitialSize(5);//初始化连接数量
dataSource.setMaxActive(10);//最大的数量
//获取连接
Connection connection = dataSource.getConnection();
//数据库crud
//回收连接
connection.close();//连接池的close()就是回收连接
}
软编码
/**
* 通过读取外部配置文件的方式,实例化druid连接池对象
*/
public void testSoft() throws Exception {
//1.读取外部配置文件 Properties
Properties properties = new Properties();
//src下的文件可以使用类加载器
InputStream is = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.preperties");
properties.load(is);
//2.使用连接池的工具类的工厂模式,创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
//数据库crud
connection.close();
}
druid.properties
#key = value => java properties读取
#druid的key必须固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://127.0.0.1:3306/atguigu
jdbc工具类封装
JdbcUtils v1.0
package com.atguigu.api.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author qingya
* @create 2023-09-23 15:09
*
* v1.0工具类
* 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
* 小建议:
* 工具类的方法推荐声明为static,外部调用更加方便
*
* 实现
* 属性:连接池对象[只实例化一次]
* 1.单例模式
* 2.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();
}
public static void freeConnection(Connection connection) throws SQLException {
connection.close();//连接池的连接执行回收
}
}
JdbcUtils v2.0
/**
* //TODO:利用线程本地变量,存储连接信息 确保一个线程的多个方法可以获取同一个connection
* 优势:事务操作的时候server 和 dao 属于同一个线程,不用再传递参数
* 大家都可以调用getConnection自动获取的是相同的连接池
*
*
*/
public class JdbcUtilsV2 {
private static DataSource dataSource = null;//连接池对象
private static ThreadLocal<Connection> threadLocal = 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 = threadLocal.get();
if(connection == null){
//线程本地变量没有,连接池获取
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return dataSource.getConnection();
}
public static void freeConnection() throws SQLException {
Connection connection = threadLocal.get();
if(connection != null){
threadLocal.remove();//清空本地变量数据
connection.setAutoCommit(true);
connection.close();//连接池的连接执行回收
}
}
}
BaseDao
package com.atguigu.api.utils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author qingya
* @create 2023-09-23 16:09
* Description:封装dao层数据库重复代码
* TODO:封装两个方法
* 一个简化非DQL
* 一个简化DQL
*/
public class BaseDao {
/**
* 封装简化非DQL语句
* @param sql 带占位符的SQL语句
* @param params 占位符的值 注意:传入的占位符的值,必须等于SQL语句?位置的值
* @return 执行影响的行数
*/
public int executeUpdate(String sql, Object... params) throws SQLException {
//获取连接
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//可变参数可当作数组使用
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i+1, params[i-1]);
}
int rows = preparedStatement.executeUpdate();
preparedStatement.close();
//是否回收连接,需要考虑是不是事务
//开启事务 getAutoCommit 获取的返回值为false
if (connection.getAutoCommit()) {
//true -> 没有开启事物,正常回收连接
JdbcUtilsV2.freeConnection();
}
return rows;
}
/**
* 将查结果封装到一个实体类集合
* @param clazz 要接值的实体类集合的模板对象
* @param sql 查询语句,要求类名或者别名等于实体类的属性名 u_id as uId => uId
* @param params 占位符的值 要和 ? 位置对象对应
* @return
* @param <T> 声明的结果的泛型
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//获取连接
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]);
}
}
ResultSet resultSet = preparedStatement.executeQuery();
List<T> list = new ArrayList<>();
//获取列的信息
//TODO:metadata装的是列的信息(可以获取列的名称根据下角标,可以获取列的数量)
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
T t = clazz.newInstance();
//自动遍历列 注意:要从1开始,等于总列数
for (int i = 1; i <= columnCount; i++) {
//获取指定列下角标的值
Object value = resultSet.getObject(i);
//获取指定列下角标列的名称
//getColumnLabel: 会获取别名,如果没有定义别名,获取列名 不要使用getColumnName:只会获取列名
String key = metaData.getColumnLabel(i);
//反射,给对象的属性值赋值
Field field = clazz.getDeclaredField(key);
field.setAccessible(true);
field.set(t,value);
}
list.add(t);
}
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
JdbcUtilsV2.freeConnection();
}
return list;
}
}
测试代码
public class PSCURDPart extends BaseDao {
@Test
public void testInsert() throws Exception {
/**
* t_user表插入一条数据
* account test
* password test
* nickname 二狗子
*/
String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
int i = executeUpdate(sql, "测试333", "333", "ergouzi");
if (i > 0) {
System.out.println("插入成功");
} else {
System.out.println("插入失败");
}
}
@Test
public void testUpdate() throws Exception {
String sql = "update t_user set nickname = ? where id = ?;";
executeUpdate(sql, "新的nickname", "4");
}
@Test
public void testDelete() throws Exception {
String sql = "delete from t_user where id = ?;";
executeUpdate(sql, 4);
}
@Test
public void testSelect() throws Exception {
String sql = "select id,account,money from t_bank;";
List<Bank> banks = executeQuery(Bank.class, sql);
for (Bank bank : banks) {
System.out.println(bank);
}
}
@Test
public void testSelect1() throws Exception {
String sql = "select * from t_bank;";
executeQuery(Bank.class,sql);
}
}
t_customer表
create table t_customer(
id int primary key auto_increment comment '客户主键',
name varchar(20) comment '客户名称',
gender varchar(4) comment '客户性别',
age int comment '客户年龄',
salary double(8,1) comment '客户工资',
phone varchar(11) comment '客户电话');
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!