针对使用JDBC工具类出现“java.sql.SQLException: The url cannot be null”异常解决方式及原因分析,涉及静态变量与静态代码块执行顺序、类加载实例化对象执行顺序
1. 解决
1.1 贴上源代码
JDBCUtils.java
package com.jdbc.day0511.word;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
//JDBC工具类
public class JDBCUtils {
//定义连接数据库属性
private static String url = null;
private static String user = null;
private static String password = null;
private static String driver = null;
//读取配置文件,static,文件的读取,只需要一次即可拿到连接所需属性
static{
try {
//使用类加载器获取当前类路径
//获取类加载器对象
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
//通过类加载器的getResource()找到指定名称的资源
URL resource = classLoader.getResource("jdbc.properties");
String path = resource.getPath();
//加载文件
Properties pro = new Properties();
pro.load(new FileReader(path));
//根据键获取值
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
String driver = pro.getProperty("driver");
//注册驱动,类加载就会执行
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//定义获取连接方法,获取数据库连接对象Connection
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
//释放资源
public static void close(ResultSet rs, Statement stat, Connection conn){
if (rs != null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (stat != null){
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void close(Statement stat, Connection conn){
if (stat != null){
try {
stat.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
Test.java
package com.jdbc.day0511.word;
import java.sql.*;
import java.util.Scanner;
/*
需求:(创建表user(id,username,password))
1. 通过键盘录入用户名和密码
2. 判断用户是否登录成功
select * from user where username = "" and password = "";
如果这个sql有查询结果,则成功,反之,则失败
*/
public class Test {
public static void main(String[] args) {
while(true) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
boolean flag = login(username, password);
if (flag) {
System.out.println("登录成功!");
break;
} else {
System.out.println("登录失败!请仔细检查");
}
}
}
private static Boolean login(String username,String password) {
Boolean flag = null;
Connection conn = null;
PreparedStatement pstat = null;
ResultSet rs = null;
try {
//获取连接数据库对象Connection
conn = JDBCUtils.getConnection();
//定义sql
String sql = "select * from user where username = ? and password = ?";
//获取执行sql语句对象Statement
//预防sql注入,使用prepareStatment
pstat = conn.prepareStatement(sql);
pstat.setString(1,username);
pstat.setString(2,password);
//执行sql,并接收返回结果
rs = pstat.executeQuery();
//查询结果是否有值
flag = rs.next();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(rs,pstat,conn);
}
return flag;
}
}
1.2 解决方式
将静态代码块中定义变量的String删除,删除如下,它与静态成员变量中定义的变量冲突
2. 原因分析
-
静态变量和静态代码块也是有执行顺序的,与代码书写的顺序一致。
- 在静态块中可以使用静态变量,但是被使用的静态变量必须在静态代码块前面声明。
-
Java中的静态变量和静态代码块是在类加载的时候就执行的,实例化对象时,先声明并实例化变量再执行构造函数。如果子类继承父类,则先执行父类的静态变量和静态代码块(先声明的先执行),再执行子类的静态变量和静态代码块。同样,接着依次执行父类和子类变量(或非静态代码块)和构造函数。
-
实例化对象执行顺序:
- 父类静态变量/静态代码块(先声明的先执行)>子类静态变量/静态代码块(先声明的先执行)>父类变量/代码块((先声明的先执行)>父类构造函数>子类变量/代码块((先声明的先执行)>子类构造函数>