小马哥JAVA实战营-JDBC

小马哥是一个非常牛逼的技术大牛,最近在看他的课,感兴趣也可以关注一波小马哥(不是引流,是真的很推荐):
小马哥B站

JDBC规范文档

jdbc规范文档下载链接

JDBC的主要特征

  • 面向数据表行列编程
  • 驱动程序需要数据库定制(MySQL、Oracle)
  • SQL 语法与目标数据库保持一致
  • 事务(需要数据库支持)
  • 数据库元信息 (数据库信息、表结构信息等)

JDBC 核心API

  • 数据源接口 -javaxsql.DataSource
    1 主流实现:
  1. Apache DBCP 1/2 DPCP文档 目前已经基本不使用了作为了解
    DBCP 的实现间接依赖-Apache Commons Pool(池化)里面有对象池。
    对象池的概念
    池化 资源少(线程资源,数据库资源,IO资源)比如线程池,数据库连接池 ;消费者多
    池化的特点是有借有还,其核心思想是生产者和消费者模型。
  2. C3P0
  3. Alibaba Druid(字节码提升/优化)
  4. HicariCP
    2 获取方式
    1 普通对象初始化
    Spring Bean 通过spirng来管理
    API 实现 通过api 来实例化
    2 JNDI 依赖查找 JNDI的一些资料
  • JDBC 驱动接口java.sql.Driver

  • 驱动动管理器接口 -java.sql.DriverManager(重点)
    用于管理driver,有三种获取Driver的方式

1 Class.forName 的时候,会在驱动管理器进行注册源码如下:

// classforName 的时候会加载这个类,类加载的时候静态方法执行就会注册驱动
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}

2 同时DriverManager 加载的时候也会注册驱动,通过系统属性,是JVM共享的
读取系统属性jdbc.drivers后,再经过":" 分割尝试获取多值,在通过classLoader获取实现类,意思是驱动类用:分割开

static {loadInitialDrivers();println("JDBC DriverManager initialized");
}

可以看到加载了系统属性jdbc.drivers

drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}
});

可以看到最终还是通过类加载从而注册了驱动,和第一种方式的原理几乎一样

String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}
}

3 还有一种获取方式是通过 java SPI 来获取Driver ServiceLoader通过这个类去加载,需要在静态资源目录下放入类的实现,然后加载。
在这里插入图片描述

在这里插入图片描述

那么是怎么拿到一个连接呢,方式是循环的区测试连接是否可用从而拿到一个连接,主要是通过connect方法, 源码如下:

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** When callerCl is null, we should check the application's* (which is invoking this class indirectly)* classloader, so that the JDBC driver class outside rt.jar* can be loaded from here.*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// Walk through the loaded registeredDrivers attempting to make a connection.// Remember the first exception that gets raised so we can reraise it.SQLException reason = null;for(DriverInfo aDriver : registeredDrivers) {// If the caller does not have permission to load the driver then// skip it.if(isDriverAllowed(aDriver.driver, callerCL)) {try {println("    trying " + aDriver.driver.getClass().getName());Connection con = aDriver.driver.connect(url, info);if (con != null) {// Success!println("getConnection returning " + aDriver.driver.getClass().getName());return (con);}} catch (SQLException ex) {if (reason == null) {reason = ex;}}} else {println("    skipping: " + aDriver.getClass().getName());}}// if we got here nobody could connect.if (reason != null)    {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for "+ url);throw new SQLException("No suitable driver found for "+ url, "08001");}

课堂问题:
1 当多个driver同时被加载到classLoader后,到底用那一个?
我们可以看到上面源码里面是通过url来判断的,如果尝试连接成功,则返回成功的,也就是一直尝试到第一个成功的为止。
2 java SPI加载遍历的时候应该注意配置的时候要是配置错误就会导致错误后面的Driver无法加载?
是的,因为加载的时候next() 逻辑里面也是class.forName(),所以如果抛出异常下面的while语句不在执行,后面的驱动类就无法加载

AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();/* Load these drivers, so that they can be instantiated.* It may be the case that the driver class may not be there* i.e. there may be a packaged driver with the service class* as implementation of java.sql.Driver but the actual class* may be missing. In that case a java.util.ServiceConfigurationError* will be thrown at runtime by the VM trying to locate* and load the service.** Adding a try catch block to catch those runtime errors* if driver not available in classpath but it's* packaged as service and that service is there in classpath.*/try{// 这里如果存在异常,则while循环终止了while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});

在这里插入图片描述
在这里插入图片描述
总结一下:驱动管理里面加载是同步进行的,如果系统属性jdbc.drviers 配置了或配置了spi都会进行加载,每个驱动具体的实现类在类加载的时候会向驱动管理注册驱动。

  • 数据连接接口 -java.sql.Connection
    连接可以创建Statement,Statement的主要类型
    1)普通SQL-java.sql.Statement
  1. 预编译SQL命令-java.sql.PrepareStatement
  2. 存储过程SQL命令-java.sql.CallableStatement
  • SQL 命令接口 java.sql.Statement
    DDL语句:crud 一般用executeUpdate()
    如果用execute方法
    如果成功的话不需要返回值(或者是返回false) 如果失败SQLException。也就是我们用这个方法的时候只需要关注是否抛异常,不抛出异常则是成功。
    在这里插入图片描述
    下面来借用derby 来演示一下executeUpdate() 和 execute() 的区别
public static final String DROP_USERS_TABLE_DDL_SQL = "DROP TABLE users";public static final String CREATE_USERS_TABLE_DDL_SQL = "CREATE TABLE users(" +"id INT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), " +"name VARCHAR(16) NOT NULL, " +"password VARCHAR(64) NOT NULL, " +"email VARCHAR(64) NOT NULL, " +"phoneNumber VARCHAR(64) NOT NULL" +")";public static final String INSERT_USER_DML_SQL = "INSERT INTO users(name,password,email,phoneNumber) VALUES " +"('A','******','a@gmail.com','1') , " +"('B','******','b@gmail.com','2') , " +"('C','******','c@gmail.com','3') , " +"('D','******','d@gmail.com','4') , " +"('E','******','e@gmail.com','5')";
// /db/user-platform这是一个目录,运行后java会在当前程序根目录下创建derby 内存数据库
String databaseURL = "jdbc:derby:/db/user-platform;create=true";
Connection connection = DriverManager.getConnection(databaseURL);
Statement statement = connection.createStatement();
// 删除 users 表 运行结果为false
System.out.println(statement.execute(DROP_USERS_TABLE_DDL_SQL));
// 创建 users 表 运行结果为false
System.out.println(statement.execute(CREATE_USERS_TABLE_DDL_SQL));
// 运行结果为5
System.out.println(statement.executeUpdate(INSERT_USER_DML_SQL)); 

在这里插入图片描述
关于Derby数据库:添加链接描述
ORM 的核心思想(反射),代码demo

static Map<Class, String> typeMethodMappings = new HashMap<Class, String>() {{put(Long.class, "getLong");put(String.class, "getString");}};public static void main(String[] args) throws Exception {String databaseURL = "jdbc:derby:/db/user-platform;create=true";Connection connection = DriverManager.getConnection(databaseURL);Statement statement = connection.createStatement();// 执行查询语句(DML)ResultSet resultSet = statement.executeQuery("SELECT id,name,password,email,phoneNumber FROM users");// BeanInfo user这个类的属性及类型 userBeanInfo.getPropertyDescriptors() 可以通过这个方法拿到所有的属性 第二个参数是stop class 到那个父类停止 如果有继承关系会获取所有的属性包含父类的BeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);List<User> users = new ArrayList<>();// 如果存在并且游标滚动while (resultSet.next()) {User user = new User();for (PropertyDescriptor propertyDescriptor : userBeanInfo.getPropertyDescriptors()) {String fieldName = propertyDescriptor.getName();Class<?> type = propertyDescriptor.getPropertyType();// 如果存在 列名的映射也可以在这里写一个map来映射String methodName = typeMethodMappings.get(type);// long getLong(String columnLabel) throws SQLException; 里面只有一个参数且为String 所以第二个参数填String.classMethod resultMethod = resultSet.getClass().getMethod(methodName, String.class);Object resultVal = resultMethod.invoke(resultSet, fieldName);// propertyDescriptor.getWriteMethod() 就是set方法// propertyDescriptor.getReadMethod() 就是read方法Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(user, resultVal);}users.add(user);}connection.close();}```
- SQL 执行结果接口 -java.sql.ResultSetResultSet - 表元数据接口 -java.sql.ResultSetMetaData
这个接口的应用,比如逆向工程,可以生成一些sql 和 类,下面看一个小demo看如何生成sql的,如何生成类等研究源码后来补充
```java
String databaseURL = "jdbc:derby:/db/user-platform;create=true";
Connection connection = DriverManager.getConnection(databaseURL);
Statement statement = connection.createStatement();
// 执行查询语句(DML)
ResultSet resultSet = statement.executeQuery("SELECT id,name,password,email,phoneNumber FROM users");
// ResultSetMetaData 元信息
ResultSetMetaData metaData = resultSet.getMetaData();
// 获取当前的表名称 它的参数是column 填任何列的数量都是返回表名称
System.out.println("当前表的名称:" + metaData.getTableName(1));
System.out.println("当前表的列个数:" + metaData.getColumnCount());
for (int i = 1; i <= metaData.getColumnCount(); i++) {System.out.println("列名称:" + metaData.getColumnLabel(i) + ", 类型:" + metaData.getColumnClassName(i));
}
// 反向生成生成sql
StringBuilder builder = new StringBuilder("SELECT ");
for (int i = 1; i <= metaData.getColumnCount(); i++) {builder.append(metaData.getColumnLabel(i)).append(",");
}
// 去掉最后一个逗号
builder.deleteCharAt(builder.length() - 1);
builder.append(" FROM ").append(metaData.getTableName(1));
System.out.println(builder.toString());
connection.close();

在这里插入图片描述

  • SQL 执行异常 java.sql.SQLException
    基本特点:
    1)几乎所有的JDBC API操作都需要try catch java.sql.SQLException
    2) java.sql.SQLException 属于检查类型异常,继承Exception
    下面代码是直接复制的小马哥的代码,着重理解getAll() 方法,这里演示了如果自己手写JDBCTemplate,需要怎样实现并处理异常,对于我这个小菜来说收货颇多。
package org.geektimes.projects.user.repository;
import org.geektimes.context.ClassicComponentContext;
import org.geektimes.function.ThrowableFunction;
import org.geektimes.projects.user.domain.User;
import org.geektimes.projects.user.sql.DBConnectionManager;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.apache.commons.lang.ClassUtils.wrapperToPrimitive;
public class DatabaseUserRepository implements UserRepository {private static Logger logger = Logger.getLogger(DatabaseUserRepository.class.getName());/*** 通用处理方式,处理异常*/private static Consumer<Throwable> COMMON_EXCEPTION_HANDLER = e -> logger.log(Level.SEVERE, e.getMessage());public static final String INSERT_USER_DML_SQL ="INSERT INTO users(name,password,email,phoneNumber) VALUES " +"(?,?,?,?)";public static final String QUERY_ALL_USERS_DML_SQL = "SELECT id,name,password,email,phoneNumber FROM users";private final DBConnectionManager dbConnectionManager;public DatabaseUserRepository() {this.dbConnectionManager = ClassicComponentContext.getInstance().getComponent("bean/DBConnectionManager");}private Connection getConnection() {return dbConnectionManager.getConnection();}@Overridepublic boolean save(User user) {return false;}@Overridepublic boolean deleteById(Long userId) {return false;}@Overridepublic boolean update(User user) {return false;}@Overridepublic User getById(Long userId) {return null;}@Overridepublic User getByNameAndPassword(String userName, String password) {return executeQuery("SELECT id,name,password,email,phoneNumber FROM users WHERE name=? and password=?",resultSet -> {// TODOreturn new User();}, COMMON_EXCEPTION_HANDLER, userName, password);}@Overridepublic Collection<User> getAll() {return executeQuery("SELECT id,name,password,email,phoneNumber FROM users", resultSet -> {// BeanInfo -> IntrospectionExceptionBeanInfo userBeanInfo = Introspector.getBeanInfo(User.class, Object.class);List<User> users = new ArrayList<>();while (resultSet.next()) { // 如果存在并且游标滚动 // SQLExceptionUser user = new User();for (PropertyDescriptor propertyDescriptor : userBeanInfo.getPropertyDescriptors()) {String fieldName = propertyDescriptor.getName();Class fieldType = propertyDescriptor.getPropertyType();String methodName = resultSetMethodMappings.get(fieldType);// 可能存在映射关系(不过此处是相等的)String columnLabel = mapColumnLabel(fieldName);Method resultSetMethod = ResultSet.class.getMethod(methodName, String.class);// 通过放射调用 getXXX(String) 方法Object resultValue = resultSetMethod.invoke(resultSet, columnLabel);// 获取 User 类 Setter方法// PropertyDescriptor ReadMethod 等于 Getter 方法// PropertyDescriptor WriteMethod 等于 Setter 方法Method setterMethodFromUser = propertyDescriptor.getWriteMethod();// 以 id 为例,  user.setId(resultSet.getLong("id"));setterMethodFromUser.invoke(user, resultValue);}}return users;}, e -> {// 异常处理});}/*** @param sql* @param function* @param <T>* @return*/protected <T> T executeQuery(String sql, ThrowableFunction<ResultSet, T> function,Consumer<Throwable> exceptionHandler, Object... args) {Connection connection = getConnection();try {PreparedStatement preparedStatement = connection.prepareStatement(sql);for (int i = 0; i < args.length; i++) {Object arg = args[i];Class argType = arg.getClass();// 获取原生的类型 拆箱后的类型Class wrapperType = wrapperToPrimitive(argType);if (wrapperType == null) {wrapperType = argType;}// 这里也是把set方法映射一下String methodName = preparedStatementMethodMappings.get(argType);Method method = PreparedStatement.class.getMethod(methodName, wrapperType);method.invoke(preparedStatement, i + 1, arg);}ResultSet resultSet = preparedStatement.executeQuery();return function.apply(resultSet);} catch (Throwable e) {// 异常处理exceptionHandler.accept(e);}return null;}private static String mapColumnLabel(String fieldName) {return fieldName;}/*** 数据类型与 ResultSet 方法名映射*/static Map<Class, String> resultSetMethodMappings = new HashMap<>();static Map<Class, String> preparedStatementMethodMappings = new HashMap<>();static {resultSetMethodMappings.put(Long.class, "getLong");resultSetMethodMappings.put(String.class, "getString");// longpreparedStatementMethodMappings.put(Long.class, "setLong");// stringpreparedStatementMethodMappings.put(String.class, "setString");}
}

里面用到了小马哥设计的可以抛出异常的Function,小马哥dubbo 源码里面复制过来的代码如下:

@FunctionalInterface
public interface ThrowableFunction<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result* @throws Throwable if met with any error*/R apply(T t) throws Throwable;/*** Executes {@link ThrowableFunction}** @param t the function argument* @return the function result* @throws RuntimeException wrappers {@link Throwable}*/default R execute(T t) throws RuntimeException {R result = null;try {result = apply(t);} catch (Throwable e) {throw new RuntimeException(e.getCause());}return result;}/*** Executes {@link ThrowableFunction}** @param t        the function argument* @param function {@link ThrowableFunction}* @param <T>      the source type* @param <R>      the return type* @return the result after execution*/static <T, R> R execute(T t, ThrowableFunction<T, R> function) {return function.execute(t);}
}
  • 事务保护点接口 -java.sgl.Savepoint
    如果我们记录了保护点可以从那个点进行回滚
    下面这两个方法是关于事务的保存点和回滚的位于Connection接口里面
Savepoint setSavepoint(String name) throws SQLException;
void rollback(Savepoint savepoint) throws SQLException;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/18323.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

做爬虫如何选择Python和C语言

目录 优劣势分析 Python 进行爬虫的优势&#xff1a; Python 进行爬虫的劣势&#xff1a; C进行爬虫的优势&#xff1a; C进行爬虫的劣势&#xff1a; 示例代码说明 Python 示例代码&#xff1a; C语言 示例代码&#xff1a; 怎么选择 优劣势分析 Python 进行爬虫的优…

IPv6 over IPv4 之SIT隧道

一.SIT模块功能简介 SIT模块是支持ISATAP隧道和6to4隧道两种隧道模式的 ISATAP和6to4都是目前比较流行的自动建立隧道的过渡技术&#xff0c;都可以连接被IPv4隔绝的IPv6孤岛&#xff0c;都是通过将IPv4地址嵌入到IPv6地址当中&#xff0c;并将IPv6封包封装在IPv4中传送&…

ubuntu下,verdi语法错误Syntax error: “(“ unexpected

【问题】/home/EDA_TOOLS/synopsys/verdi/verdi/Verdi_O-2018.09-SP2/bin/verdi: 56: /home/EDA_TOOLS/synopsys/verdi/verdi/Verdi_O-2018.09-SP2/bin/verdi: Syntax error: "(" unexpected 【解析】 代码对于标准bash而言没有错&#xff0c;因为Ubuntu/Debian为了加…

【二分查找】35. 搜索插入位置

35. 搜索插入位置 解题思路 使用二分查找算法当找到元素之后直接返回位置即可当没找到元素&#xff0c;将该元素插入到left位置即可 class Solution {public int searchInsert(int[] nums, int target) {// 二分查找int left 0;int right nums.length - 1;while(left < …

二叉树的简单遍历

假设节点数据类如下&#xff1a; public class TreeNode {String val;TreeNode left;TreeNode right;TreeNode() { }TreeNode(String val) {this.val val;}TreeNode(String val, TreeNode left, TreeNode right) {this.val val;this.left left;this.right right;} } 二叉…

Android Java代码与JNI交互 引用类型转换(五)

🔥 Android Studio 版本 🔥 🔥 Java中基础数据类型对应Native的数据类型 🔥 * Java中基础数据类型对应Native中的数据类型* |Java |Native |* |boolean |jboolean |* |byte |jbyte |* |short |jshort |* |int |jint |* |long…

【Vscode】解决 An SSH installation couldn‘t be found

【Vscode】解决 An SSH installation couldn‘t be found 背景描述&#xff1a;在vscode中使用ssh进行连接到时候&#xff0c;已经安装了ssh romote的plugin插件&#xff0c;但是在输入了ssh连接命令之后&#xff0c;仍然出现报错&#xff1a;an ssh installation could not be…

music21 层级解析(了解次结构方可将任意曲谱与mid互相转换)

这段代码创建了一个音乐乐谱并将其保存为 MIDI 文件&#xff0c;其中包含一个乐器和多个小节。每个小节中包含四个音符或和弦&#xff0c;然后将小节添加到乐谱中。最后&#xff0c;将乐谱写入 MIDI 文件。 首先&#xff0c;通过导入 music21 库来使用它的功能。 import music2…

基于.Net Core实现的飞书所有文档一键导出服务(支持多系统)

feishu-doc-export 一个支持Windows、Mac、Linux系统的飞书文档一键导出服务&#xff0c;仅需一行命令即可将飞书知识库的全部文档同步到本地电脑。导出速度嘎嘎快&#xff0c;实测700多个文档导出只需25分钟&#xff0c;且程序是后台挂机运行&#xff0c;不影响正常工作。 动…

用html+javascript打造公文一键排版系统4:一级标题排版

完成公文标题的排版后&#xff0c;我们要进行对正文中的标题进行处理。 一般正文中的标题分为四级&#xff0c;文中结构层次序数依次可以用“一、”“&#xff08;一&#xff09;”“1.”“&#xff08;1&#xff09;”标注&#xff1b;一般第一层用黑体字、第二层用楷体字加粗…

《UNUX环境高级编程》(8)进程控制

1、引言 2、进程标识 每个进程都用一个唯一的非负整数标识&#xff0c;即为进程id&#xff1a;pid。进程ID是可以复用的&#xff0c;当一个进程终止时&#xff0c;其进程ID就可以用来标识其他进程。系统中有一些专用进程&#xff1a; 进程ID为0的是调度进程&#xff0c;也称交…

lua 请求ftp服务器数据,下载文件

1、装入ftp库 2、调用ftp的get()方法 3、get()方法参数格式&#xff1a; 4、将返回到的数据写入文件中 例如&#xff0c;本次获取专利数据系统 http://patdata1.cnipa.gov.cn/ 的ftp站点数据 local ftp require("socket.ftp")--此处我没填端口号 file,err ftp.g…