JDBC是指数据库连接技术,用于java连接mySQL等数据库。本文详细介绍了尚硅谷课程中JDBC的学习内容和补充知识。

概述

  • java语言只提供规范接口,存在于java.sql.javax.sql包下,然后数据库软件根据java提供的规范实现具体的驱动代码(jar)
  • jar包是java程序打成的一种压缩包格式,只要导入就可以使用对应方法

学习思路:(可以学完再看)

  1. 六大基本步骤获取连接,包括直接输入字符串的Statement和改进版的PreparedStatement(通过占位符解决了容易SQL攻击的问题)

  2. JDBC的增删改查,其中插入数据需要考虑主键自增长批量插入效率低的问题

  3. 建立数据库事务(基本特征是关闭了自动提交,要同时完成多个操作后再一起提交,如转账过程先增加A账户的钱,再扣除B账户的钱,一定要一起提交,防止出现只成功一件事)

  4. 连接池,优化建立连接的过程(每件事都去连接很繁琐,浪费资源,Druid连接池)

    • 硬编码:通过设置参数的方式
    • 软编码:通过读取外部配置文件的方式(读取方法使用了类加载器,补充说明在末尾)
  5. 封装连接工具类(通过静态代码块的方式保证连接池只被创建一次,而不是每次调用方法都创建一个新的连接池对象。)

    • V1.0:不同方法调用拿到的是不同连接对象
    • V2.0:利用线程本地量的方法,保证同一个线程不同方法拿到的是同一个连接
  6. 完整工具类封装(连接池优化了建立连接,那么把增删改查操作也封装一下吧)

    • executeUpdate(实现数据的更新操作,包括增,删,改)
    • executeQuery(实现数据的查询操作,通过反射机制,输入模板对象,返回查询出的对象列表)

基本步骤

  1. 注册驱动,将依赖的jar包进行安装
  2. 建立连接 connection
  3. 创建发送SQL语句的对象statement
  4. statement对象去发送SQL数据到数据库获取返回结果
  5. 解析结果集
  6. 销毁资源

mySQL端 创建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','管理员');

java端获取数据

public class StatementQuery {
public static void main(String[] args) throws SQLException {
//1.注册驱动
DriverManager.registerDriver(new Driver());//8+驱动要选择带cj的
//2. 获取连接,需要数据库ip地址(127.0.0.1),数据库端口号(3306),账号(root),密码,连接数据库的名称(atguigu)
//jdbc:数据库厂商名//ip:端口号/数据库名
Connection connection=DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 创建statement
Statement statement = connection.createStatement();
//4. 发送SQL语句
String sql="select * from t_user";
ResultSet resultSet = statement.executeQuery(sql);
//5. 进行结果集更新
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();
}
}

模拟用户登录

  • 模拟用户登录
  • 键盘输入事件收集账号密码信息
  • 输入账号密码进行查询验证是否登录成功

public class SatetementUserLoginPart {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//0. 获取用户输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号");
String account = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
//1.注册驱动,有两种方案
/*方案一--------------
//DriverManager.registerDriver(new Driver());//会注册两次驱动,可以考虑仅触发静态代码块
//类加载机制:类记载时刻会触发静态代码块,可以通过new,静态方法,静态属性等触发
*/
//方案二-------------
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//优点是字符串可以提取到外部的配置文件,便于更换数据驱动,会更加灵活
//2. 获取数据库连接,共有三种方法
//该方法是一个重载方法,允许开发者用不同的形式传入数据库连接的核心参数
//三个参数-------------
Connection connection=DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//jdbc:mysql:///atguigu 如果本机和3306可以省略为///
//两个参数--------------
/*
Properties info=new Properties();
info.put("user","root");
info.put("password","root");
DriverManager.getConnection("jdbc:mysql:///atguigu",info);
//一个参数--------------
//jdbc:数据库厂商名//ip:端口号/数据库名?user=root&password=root
DriverManager.getConnection("jdbc:mysql:///atguigu?user=root&password=root");
//其他可选信息,如果是sql8.0以后的版本可以省略serverTimezone=Asia/Shanghai&UseUnicode=true&characterEncoding=utf8&useSSL=true
*/
//3. 创建发送SQL语句的Statetment对象
Statement statement = connection.createStatement();
//4. 发送SQL语句
String sql="SELECT * FROM t_user WHERE account='"+account+"'AND PASSWORD='"+password+"';";
//executeUpdate用于更新数据,返回影响的函数,executeQuery负责返回查询结果,返回结果封装对象
ResultSet resultSet = statement.executeQuery(sql);
//5.查询结果集解析,resultSet给出的是逐行的对象,使用next可以逐行提取下一个,遍历完毕时返回false
/*仅查询的方案
while(resultSet.next()){
//光标已经移动,此时利用getInt\getString读取该行的数据
//getString输入可以是index(列的下角标,从左向右从1开始),可以是列名(有别名写别名)
int id = resultSet.getInt("id");
String account1 = resultSet.getString("account");
String password1 = resultSet.getString("password");
String nickname = resultSet.getString("nickname");
System.out.println(id+"--"+account+"--"+password+"--"+nickname);
}
*/
//考虑实际需求,只需要有一行数据证明就可以登录成功
if(resultSet.next()){
System.out.println("登录成功");
}else {
System.out.println("登录失败,用户名或密码错误");
}
//6. 关闭资源
resultSet.close();
statement.close();
connection.close(); }
}

存在几个问题

  1. SQL语句需要字符串拼接比较麻烦

  2. 只能拼接字符串类型,其他的数据库类型无法处理

  3. 可能发生注入攻击(动态值充当了SQL语句结构)

模拟用户登录(prepare改进)

public class SPUserLoginPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//0. 获取用户输入信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入账号");
String account = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
//1.注册驱动,有两种方案
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//2. 获取数据库连接
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 创建发送SQL语句的preparedstatetment对象
//(1). 编写SQL语句结构,动态值部分使用占位符?替代(2).创建preparedstatetment,传入动态值
//(3). 动态值 占位符 赋值? 单独赋值即可 (4). 发送
String sql="SELECT * FROM t_user where account=? and password=?;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
/*
参数一:index占位符的位置,从1开始
参数二:Object占位符的值,可以是任何类型
*/
preparedStatement.setObject(1,account);
preparedStatement.setObject(2,password);
ResultSet resultSet = preparedStatement.executeQuery();
//5. 结果集解析
if(resultSet.next()){
System.out.println("登录成功");
}else {
System.out.println("登录失败,用户名或密码错误");
}
//6. 关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}

JDBC实现增删改查

public class PSCURDPart {
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
//1.注册驱动,有两种方案
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//2. 获取数据库连接
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 创建发送SQL语句的preparedstatetment对象
String sql="insert into t_user(account,password,nickname) values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,"test");
preparedStatement.setObject(2,"test");
preparedStatement.setObject(3,"二狗");
int rows = preparedStatement.executeUpdate();
//5. 结果集解析
if(rows>0){
System.out.println("插入成功");
}else {
System.out.println("插入失败");
}
//6. 关闭资源
preparedStatement.close();
connection.close();
}
@Test
public void testUpdate() throws ClassNotFoundException, SQLException {
//1.注册驱动,有两种方案
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//2. 获取数据库连接
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 创建发送SQL语句的preparedstatetment对象
String sql="update t_user set nickname=? where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,"张三");
preparedStatement.setObject(2,3);
int rows = preparedStatement.executeUpdate();
//5. 结果集解析
if(rows>0){
System.out.println("修改成功");
}else {
System.out.println("修改失败");
}
//6. 关闭资源
preparedStatement.close();
connection.close();
}
@Test
public void testDelete() throws ClassNotFoundException, SQLException {
//1.注册驱动,有两种方案
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//2. 获取数据库连接
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 创建发送SQL语句的preparedstatetment对象
String sql="delete from t_user where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setObject(1,3);
int rows = preparedStatement.executeUpdate();
//5. 结果集解析
if(rows>0){
System.out.println("删除成功");
}else {
System.out.println("删除失败");
}
//6. 关闭资源
preparedStatement.close();
connection.close();
}
//查询所有用户数据,并封装到一个List<Map> 集合中,key=列名,value=列的内容
@Test
public void testSelect() throws ClassNotFoundException, SQLException {
//1.注册驱动,有两种方案
Class.forName("com.mysql.cj.jdbc.Driver");//触发类加载,触发静态代码块的调用
//2. 获取数据库连接
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
//3. 创建发送SQL语句的preparedstatetment对象
String sql="SELECT id,account,password,nickname FROM t_user;";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
//5. 结果集解析
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 value = resultSet.getObject(i);
String columnLabel = metaData.getColumnLabel(i);//getlabel可以得到别名,name只能别名
map.put(columnLabel,value);
}
list.add(map);
}
System.out.println(list);
//6. 关闭资源
preparedStatement.close();
connection.close();
}
}

灵活插入

主键回显

在多表关联插入数据时,一般主表的主键是自动生成的,所以插入数据前无法获取主键,但是从表需要再插入数据之前就绑定主表的主键,这时可以使用主键回显技术

public class PSOthePart {
//t_user 插入一条数据,并获取数据库自增长的主键
@Test
public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection= DriverManager.
getConnection("jdbc:mysql://127.0.0.1:3306/atguigu","root","mypassword");
String sql="Insert into t_user(account,password,nickname) values(?,?,?);";
//插入数据同时获取返回主键
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1,"test1");
preparedStatement.setObject(2,"123456");
preparedStatement.setObject(3,"蛋");
int i=preparedStatement.executeUpdate();
if(i>0){
System.out.println("插入成功");
//获取主键的结果集对象,一行一列,id=值
ResultSet resultSet = preparedStatement.getGeneratedKeys();
resultSet.next();
int id=resultSet.getInt(1);
System.out.println("id="+id);
}else{
System.out.println("插入失败");
}
preparedStatement.close();
connection.close();
}
}

批量插入

public void returnPrimaryKey2() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
// 1.路径后面添加允许批量操作
Connection connection= DriverManager.
getConnection("jdbc:mysql:///atguigu?rewriteBatchedStatements=true","root","mypassword");
String sql="Insert into t_user(account,password,nickname) values(?,?,?);"; PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
long start=System.currentTimeMillis();
for (int i = 1; i < 10000; i++) {
preparedStatement.setObject(1,"dd"+i);
preparedStatement.setObject(2,"dd"+i);
preparedStatement.setObject(3,"蛋"+i);
preparedStatement.addBatch();//2. 不执行,追加到values后面,批量添加addBatch
} preparedStatement.executeBatch();//3. 执行批量操作
long end = System.currentTimeMillis();
preparedStatement.close();
connection.close();
}

数据库事务

  • 允许在失败情况下,数据回归到业务之前的状态!

  • 具有原子性(不可切分)、一致性(状态不变)、隔离性(互不干扰)、持久性(永久性改变)

  • 手动提交

实现目标:实现两个账户之间的安全转账,如果一方余额不足不会出现转账错误(即一方账户增加了另一方没有减少)

业务表的创建

CREATE TABLE t_bank(
id INT PRIMARY KEY AUTO_INCREMENT,
account VARCHAR(20) NOT NULL UNIQUE COMMENT '账户',
money INT UNSIGNED COMMENT '金额,不能为负值'
); INSERT INTO t_bank(account,money) VALUES
('ergouz1',1000),('lvdandan',1000);

BankService.java(实现业务操作,即转账)

public class BankService {
@Test
public void start() throws SQLException, ClassNotFoundException {
transfer("lvdandan","ergouz1",500);
}
public void transfer(String addAccount,String subAccount,int money) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
// 1.路径后面添加允许批量操作
Connection connection= DriverManager.
getConnection("jdbc:mysql:///atguigu","root","mypassword");
BankDao bankDao=new BankDao();
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.java(业务实现细节,即账户钱增加和减少)

public class BankDao {
public void add(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
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();
}
public void sub(String account,int money,Connection connection) throws ClassNotFoundException, SQLException {
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();
}
}

连接池

  • 每次获取新的连接消耗很大,为了节约创建和销毁连接的性能消耗
  • javax.sql.DataSource接口,规范了连接池获取连接的方法,规范了连接池回收连接的方法,连接池有多种,但都实现了该接口
  • 硬编码:通过设置参数的方式
  • 软编码:通过读取外部配置文件的方式(读取方法使用了类加载器,补充说明在末尾)
public class DruidUsePart {
//直接使用代码设置连接池连接参数等方式
@Test
public void testHard() throws SQLException {
DruidDataSource dataSource=new DruidDataSource();
//连接数据库驱动类的全限定符 注册驱动
dataSource.setUrl("jdbc:mysql:///atguigu");
dataSource.setUsername("root");
dataSource.setPassword("mypassword");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setInitialSize(5);//初始化连接数量
dataSource.setMaxActive(10);//最大的数量 DruidPooledConnection connection = dataSource.getConnection();//获取连接
//数据库操作 //
connection.close(); }
//读取外部配置文件的方法实例化连接对象
public void testSoft() throws Exception {
Properties properties = new Properties();
InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(ips); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
//数据库操作 //
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

连接工具类封装

通过静态代码块的方式保证连接池只被创建一次,而不是每次调用方法都创建一个新的连接池对象

V1.0

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();//连接池的连接执行回收
}
}

问题:不同方法调用拿到的是不同对象,考虑如何同一个线程不同方法拿到的是同一个连接

V2.0(线程本地量)

利用线程本地变量,存储连接信息 确保一个线程的多个方法可以获取同一个connection

  •  优势:事务操作的时候server 和 dao 属于同一个线程,不用再传递参数
  •  大家都可以调用getConnection自动获取的是相同的连接池
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

V2版本代码

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;
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();//连接池的连接执行回收
} }
}

此时的封装只针对了获取连接部分,还不够完善,可以创建一个更加完善的工具类,包括数据库的增删改查

完整工具类(修改和查询)

类代码

package utils;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List; /**
* 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 {//Object...可变参数传参数形式,在代码中可以作为数组使用 //获取连接
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();
//是否回收连接,需要考虑是不是事务,如果不是事务,则直接回收连接
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
*/
//第一个<T>是方法泛型,需要调用时确定
// 第三个T是指使用反射技术赋值
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);//获取属性key
field.setAccessible(true);//属性可以设置,打破private限制
field.set(t,value);//给对象赋值(赋值对象,属性值),如果是静态属性,第一个参数可以为null
} list.add(t);
} resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
JdbcUtilsV2.freeConnection();
} return list;
}
}

测试代码

package utils;

import org.junit.Test;

import java.util.List;

/**
* @create 2024-03-{DAY}-{TIME}
*/
public class JdbcCurdPart 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);
}
}
class Bank{
int id;
String account;
int money;
}

附加知识

这部分内容用于反射、泛型不熟练的进一步理解代码

获取类class对象

目的是执行类的静态代码块

  1. Class.forName(类名可以不固定)

使用 Class.forName("com.mysql.cj.jdbc.Driver") 语句是为了加载MySQL JDBC驱动程序。在Java中,类的加载是通过类加载器(ClassLoader)来完成的,Class.forName方法是一种触发类加载的方式。这种加载方式主要用于加载类的静态代码块。

  1. 动态加载驱动程序: 使用 Class.forName 允许在运行时根据需要加载特定的驱动程序类。这使得你可以在不修改代码的情况下,通过配置文件或其他方式动态更改要使用的数据库驱动。
  2. 提高灵活性: 通过将驱动程序类名硬编码在代码中,你的代码将直接依赖于特定的数据库驱动。使用反射可以将驱动程序类的选择推迟到运行时,使得代码更加灵活,更容易适应变化。

总之,Class.forName("com.mysql.cj.jdbc.Driver") 的目的是为了加载并注册MySQL数据库驱动程序,使用反射的方式允许在运行时动态选择和加载驱动程序,提高了代码的灵活性和可配置性。

  1. 固定类名

    类字面常量是在Java中引入的一种语法,用于获取类的 Class 对象。它是在编译时就被解析的,因此具有更好的类型安全性。类字面常量的语法形式为在类名后面添加 .class

以下是类字面常量的示例:

javaClass<MyClass> myClass = MyClass.class;

这里,MyClass.class 表示 MyClass 类的 Class 对象。与使用 Class.forName() 方法不同,类字面常量在编译时就会受到检查,如果类名不存在或有语法错误,编译器将会报错。

这种方式的优点包括:

  1. 类型安全: 编译器会在编译时检查类名的正确性,避免了在运行时可能出现的 ClassNotFoundException。
  2. 更简洁: 语法更加简洁清晰,不需要字符串形式的类名。
  3. 更高效: 类字面常量的方式更高效,因为它是在编译时就进行解析的,不需要在运行时动态加载类。

总体而言,如果在编译时就知道要加载的类,而且希望代码更加类型安全,类字面常量是一个更好的选择。如果类名是在运行时动态确定的,那么可以考虑使用 Class.forName() 方法。

读取配置文件

InputStream ips = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");

这行代码是通过类加载器(ClassLoader)获取资源文件的输入流。这种方式通常用于从类路径中加载配置文件。让我解释一下为什么这样获取配置文件是常见的做法:

  1. 相对路径的问题: 使用类加载器可以避免相对路径的问题。当你使用相对路径加载文件时,具体的相对位置可能会受到调用代码的影响,可能导致找不到文件。而使用类加载器可以相对于类路径来查找资源,这样不受调用位置的具体影响。
  2. 支持打包为JAR文件: 如果你的应用被打包为JAR文件,直接使用相对路径可能会导致找不到文件。类加载器能够在JAR文件中查找资源,并将其作为输入流返回。
  3. 类加载器的一致性: 类加载器提供了一种一致的方式来加载资源,无论是从文件系统、网络还是JAR文件中。这样的一致性可以简化代码,并使其在不同环境中都能够正常工作。
  4. getResourceAsStream方法: getResourceAsStream 方法是 ClassLoader 类的一个方法,它返回指定资源的输入流。这使得你可以直接使用输入流来读取资源,而不需要关心其具体的物理路径。

在你的例子中,通过 DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties") 获取了名为 "druid.properties" 的配置文件的输入流。

泛型方法

public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params)
  • 这是一个泛型方法的定义,<T> 表示该方法是泛型的,返回类型为 List<T>,其中 T 是在调用时确定的类型参数。这意味着,调用该方法时,需要提供一个具体的类型,例如 Bank.class,以告诉方法返回什么类型的对象列表。
  • Class<T> clazz:这个参数表示需要执行查询的对象类型。通过传入 clazz 参数,方法可以了解需要构建的对象类型,并使用反射机制来创建对象或者设置对象的属性。
  • Object... params:这个参数是可变参数,用于传递查询中需要的参数。

如何通过反射获取模板类的属性呢?

Field field = clazz.getDeclaredField(key);//获取属性key
field.setAccessible(true);//属性可以设置,打破private限制
field.set(t,value);//给对象赋值(赋值对象,属性值),如果是静态属性,第一个参数可以为null
  1. Field field = clazz.getDeclaredField(key):这行代码使用了反射技术。clazz.getDeclaredField(key) 用于获取指定名称的字段。在这里,key 应该是你查询中的一个列名或者属性名。
  2. field.setAccessible(true):这行代码打破了字段的封装性,使得即使字段是私有的,也可以通过反射来访问或者修改它的值。
  3. field.set(t, value):这行代码将指定对象 t 的属性 field 的值设置为 value。这里的 t 是一个对象实例,value 是要设置的值。

一文学会JDBC实现java和mySQL的数据连接(尚硅谷学习课程代码+笔记+思路总结)的更多相关文章

  1. Java对MySQL数据库进行连接、查询和修改(转)

    Java对MySQL数据库进行连接.查询和修改 0. 一般过程: (1) 调用Class.forName()方法加载驱动程序. (2) 调用DriverManager对象的getConnection( ...

  2. java与MySQL数据库的连接

    java与MySQL数据库的连接 1.数据库的安装和建立参见上一篇博客中的第1,2步骤.(http://blog.csdn.net/nuptboyzhb/article/details/8043091 ...

  3. (1)JDBC基础-java链接mysql数据库

    怎么操作数据库: 1,通过客户端(比如mac的终端,或者sql pro等专业工具)登陆数据库服务器(mysql -u root -p) 2,编写sql语句 3,发生sql语句到数据库服务器执行. JD ...

  4. Java在mysql插入数据的时候的乱码问题解决

    今天在使用hibernate的时候,插入mysql的数据中的中文总是显示乱码,之前出现过类似的问题,但是没有太在意,今天又发生了.所以向彻底的解决一下. 参考的博文: http://www.cnblo ...

  5. java 读取mysql中数据 并取出

    public static String url = null; public static String username = null; public static String password ...

  6. jdbc中java与mysql数据类型的映射

    注:这种类型匹配不是强制性标准,特定的JDBC厂商可能会改变这种类型匹配.例如Oracle中的DATE类型是包含时分秒,而java.sql.Date仅仅支持年月日.

  7. Java对MySQL数据库进行连接、查询和修改【转载】

    一般过程: (1) 调用Class.forName()方法加载驱动程序. (2) 调用DriverManager对象的getConnection()方法,获得一个Connection对象. (3) 创 ...

  8. java从mysql导出数据例子

    import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sq ...

  9. JDBC 复习2 存取mysql 大数据

    大数据也称之为LOB(Large Objects),LOB又分为:clob和blob,clob用于存储大文本,blob用于存储二进制数据 mysql的大数据分为2种 blob 和 text ,没有cl ...

  10. java向mysql插入数据乱码

    修改jdbc的链接,将原来的         jdbc:mysql://localhost:3306/demo改为        jdbc:mysql://localhost:3306/demo?us ...

随机推荐

  1. 11.1 C++ STL 应用字典与列表

    C++ STL 标准模板库提供了丰富的容器和算法,这些模板可以灵活组合使用,以满足不同场景下的需求.本章内容将对前面学习的知识进行总结,并重点讲解如何灵活使用STL中的vector和map容器,以及如 ...

  2. 本地Nuget包管理

    nuget.org有时候会抽风,VS无法自动下载程序包.这时,我们可以配置本地nuget包搜索路径. 1 下载Nuget package 以anycad rapid sdk为例,可以先从百度云盘下载最 ...

  3. Nebula Siwi:基于图数据库的智能问答助手思路分析

      本文重点分析 Nebula Siwi 智能问答思路,具体代码可参考[2],使用的数据集为 Basketballplayer[3].部分数据和 schema 如下所示: 一.智能问答可实现的功能 1 ...

  4. setjmp/longjmp使用问题

    setjmp/longjmp开启编译优化后导致出现无法正常使用

  5. 4.if语句--《Python编程:从入门到实践》

    4.1 检查多个条件   1.使用 and 检查多个条件   2.使用 or 检查多个条件 4.2 检查特定值是否包含在列表中   使用 in 检查特定值是否在列表中 >>> req ...

  6. Mac 和 windows上 好用的截图 工具 Snipaste

    Snipaste 官网:https://zh.snipaste.com/ ========================= 使用方法,比较简单,可以官网查看

  7. ArrayList中的遍历删除

    ArrayList 中的遍历删除 在代码编写过程中经常会遇到这样的要求:遍历一个线性表,要求只遍历一遍(时间复杂度\(O(n)\)),删除符合指定条件的元素,且要求空间复杂度 \(O(1)\). 例如 ...

  8. 【Unity3D】血条(HP)

    1 需求实现 ​ 人机交互Input 中实现了通过键盘控制坦克运动,通过鼠标控制坦克发射炮弹,本文将在此基础上,增加血条(HP)功能.炮弹命中后,HP 值会减少,因此需要应用到 刚体组件Rigidbo ...

  9. spring boot+bootstrap实现动态轮播图实战

    1.bootstrap轮播图 最近开发了个网站需要用到轮播图,正好前端用的是Bootstrap,这里就实战一下. 水平一般能力有限,仅供参考. 前提条件: bootstrap4.5 jquery 3张 ...

  10. Spring boot项目实战之记录应用访问日志

    1.说明 系统上线后往往我们需要知道都有哪些用户访问了应用的那些功能,以便更好的了解用户需求.防止恶意访问等.为此我们需要给应用添加记录访问日志的功能.下面就开始吧: 2.建表 CREATE TABL ...