小马哥是一个非常牛逼的技术大牛,最近在看他的课,感兴趣也可以关注一波小马哥(不是引流,是真的很推荐):
小马哥B站
jdbc规范文档下载链接
获取方式
JDBC 驱动接口java.sql.Driver
驱动动管理器接口 -java.sql.DriverManager(重点)
用于管理driver,有三种获取Driver的方式
// 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!");
}
}
}
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);
}
}
那么是怎么拿到一个连接呢,方式是循环的区测试连接是否可用从而拿到一个连接,主要是通过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");
}
课堂问题:
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都会进行加载,每个驱动具体的实现类在类加载的时候会向驱动管理注册驱动。
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','******','[email protected]','1') , " +
"('B','******','[email protected]','2') , " +
"('C','******','[email protected]','3') , " +
"('D','******','[email protected]','4') , " +
"('E','******','[email protected]','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.class
Method 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();
}
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();
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();
}
@Override
public boolean save(User user) {
return false;
}
@Override
public boolean deleteById(Long userId) {
return false;
}
@Override
public boolean update(User user) {
return false;
}
@Override
public User getById(Long userId) {
return null;
}
@Override
public User getByNameAndPassword(String userName, String password) {
return executeQuery("SELECT id,name,password,email,phoneNumber FROM users WHERE name=? and password=?",
resultSet -> {
// TODO
return new User();
}, COMMON_EXCEPTION_HANDLER, userName, password);
}
@Override
public Collection<User> getAll() {
return executeQuery("SELECT id,name,password,email,phoneNumber FROM users", resultSet -> {
// BeanInfo -> IntrospectionException
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 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
* @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");
// long
preparedStatementMethodMappings.put(Long.class, "setLong");
// string
preparedStatementMethodMappings.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 the source type
* @param the return type
* @return the result after execution
*/
static <T, R> R execute(T t, ThrowableFunction<T, R> function) {
return function.execute(t);
}
}
Savepoint setSavepoint(String name) throws SQLException;
void rollback(Savepoint savepoint) throws SQLException;