[TOC]

0x00 前言基础

什么是JDBC驱动?
答:数据库连接JAVA Database Connectivity java

为什么会出现JDBC?
SUN公司提供的一种数据库访问规则、规范, 由于数据库种类较多,并且java语言使用比较广泛,sun公司就提供了一种规范,让其他的数据库提供商去实现底层的访问规则。 我们的java程序只要使用sun公司提供的jdbc驱动即可。

WeiyiGeek.JDBC

WeiyiGeek.JDBC


驱动下载和使用

描述:在利用JAVA的JDBC连接到MySQL或者其它数据库的时候,需要加载其jar包到工程的lib库中;

方式1:JDBC驱动包

MySQL8.x的JDBC驱动包下载页面: https://dev.mysql.com/downloads/connector/j/8.0.html

WeiyiGeek.官网下载JDBC

WeiyiGeek.官网下载JDBC

MySQL JDBC API:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-implementation-notes.html

JAVA连接数据库的JDBC驱动关于MySQL5.x和MySQL8.X版本:
描述:MySQL 8.0 开始数据库相比常用的 5.X 版本发生了比较大的变化,我们在采用JDBC连接数据库的过程中也相应的发生变化比如驱动包版本,驱动名称等等;
直接下载地址:https://dev.mysql.com/downloads/file/?id=477058

  • 1.MySQL 版本和 mysql-connector-java 版本对应关系如下:
Connector/J version Driver Type JDBC version MySQL Server version Status
5.1 4 3.0, 4.0, 4.1, 4.2 5.6, 5.7, 8.0* General availability
8.0 4 4.2 5.6, 5.7, 8.0 General availability. Recommended version
  • 2.将 com.mysql.jdbc.Driver 更换为 com.mysql.cj.jdbc.Driver;

  • 3.MySQL 8.0 以上版本不需要建立 SSL 连接的,需要显式关闭useSSL=false,由于 MySQL 5.7 之前版本安全性做的并不够好,比如安装时生成的root空密码账号、存在任何用户都能连接上的 test 库等,导致数据库存在较大的安全隐患,所以MySQL从5.7版本开始加强安全性问题的修复到了 MySQL 8.0 以上版本已经不需要使用 SSL 进行连接加密了,但是任然保留这个接口;

  • 4.设置 CST 时区: serverTimezone=UTC,否则会启动报错

    1
    Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.

MySQL8.X的JDBC驱动连接案例:

1
2
3
4
Class.forName("com.mysql.cj.jdbc.Driver");
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test_demo?useSSL=false&serverTimezone=UTC","root","password");
// 字符串连接参数
// jdbc:mysql://localhost:3306/jdbcboot?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=true


方式2:maven依赖下载

采用maven搜索网站:https://mvnrepository.com/ ,搜寻可用的配置;

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>


工程加载Lib

新建立一个Eclipse工程并且创建一个lib目录,将下载的jar包复制到其中;右键点击jar包选择 Build Path -> Add to Bulid Path;

WeiyiGeek.Build Path

WeiyiGeek.Build Path


0x01 基础使用

描述:JDBC基本步骤流程:

  • 1.注册驱动
  • 2.建立链接
  • 3.创建statement
  • 4.构建并执行SQL
  • 5.获取结果集ResultSet
  • 6.遍历结果集
  • 7.释放资源


基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.weiyi.Jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import com.mysql.cj.jdbc.result.ResultSetMetaData;

public class Demo1 {
@SuppressWarnings("unused")
public static void main(String[] args) throws SQLException {

//JDB连接标准文档
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//Step 1.驱动注册(实际上不建议采用此类方法加载驱动-他会注册两次)
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());

//Step 2.构建连接字符串和建立链接
String urlFormat = "jdbc:mysql://%s:%d/%s?useUnicode=true&characterEncoding=UTF-8";
String connectionUrl = String.format(urlFormat, "113.xx.xx.xxx", 3306, "Demo");
conn = DriverManager.getConnection(connectionUrl, "root", "WeiyiGeek");

//Step 3.创建statement
st = conn.createStatement();


//Step 4.构建SQL语句以及执行语句
String sql = "SELECT ID AS '序号',LastName AS '名称' FROM Persons WHERE ID <> 102";
rs = st.executeQuery(sql);

//Step 5.遍历数据库
while(rs.next()){
//System.out.println("ID: " + rs.getInt("ID") + " , LastName = " + rs.getString("LastName")); //注意此处使用别名的时候(不能采用字段名进行获取数据库返回的值);
System.out.println("ID: " + rs.getInt("序号") + " , LastName = " + rs.getString("名称"));
}

//Step 6.获取元数据metadate
ResultSetMetaData rsmd = (ResultSetMetaData) rs.getMetaData();
int numCols = rsmd.getColumnCount(); //获取列元素数据量
int count = 1;
while(count <= numCols) {
System.out.printf("第 %d 列: 列名: %s , AS别名: %s , 类型:%s \n",count, rsmd.getColumnName(count), rsmd.getColumnLabel(count), rsmd.getColumnType(count));
count++;
}

} catch (SQLException sqlEx) {
// TODO: handle exception
sqlEx.printStackTrace();
} finally {
//Step 7.释放资源(非常重要) - 从后往前
System.out.println("Start-释放资源中");
//结果集对象
try {
if(rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs = null;
}

//statement对象
try {
if(st != null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
st = null;
}

//数据库连接对象
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
conn = null;
}
System.out.println("Successful-资源释放完成!");

//当然您也可以采用自己封装后的JDBC工具类(非常注意释放的顺序)
//JDBCUtil.release(conn,st,rs);
}
}
}

执行结果:

1
2
3
4
5
6
7
8
9
ID: 100 , LastName = WeiyiGeek
ID: 101 , LastName = Admin
ID: 103 , LastName = Weiyi
ID: 104 , LastName = Young
ID: 105 , LastName = bb204949-3283-11ea-b468-0242ac120002
第 1 列: 列名: ID , AS别名: 序号 , 类型:4
第 2 列: 列名: LastName , AS别名: 名称 , 类型:12
Start-释放资源中
Successful-资源释放完成!


自定义工具包类

补充:JDBC连接MySQL工具包类

  • 1.释放资源的工具类
  • 2.驱动防二次注册,因为Driver类里面有静态代码块,一上来就执行了所以等同于我们注册了两次驱动;
  • 3.构JDBC工具类的构造方法设置连接信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//该包下的类可以通过静态类 类.方法()进行调用
//该包下的类可以通过静态类 类.方法()进行调用
package com.weiyi.Jdbc;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtil {
static String connUrl = "jdbc:mysql://%s:%d/%s?useUnicode=true&characterEncoding=UTF-8";
static String connIp = "13.62.17.23";
static int connPort = 9301;
static String conndatabase = "Demo";
static String driverClass = null; //使用的时候添加
static String url = null;
static String user = null;
static String pass = null;

//静态代码块,加载类的时候执行
static {
try {
//Step1.创建一个属性配置对象
Properties prop = new Properties();

//Step2.打开配置文件
//InputStream ins = new FileInputStream("jdbc.properties");
InputStream ins = Demo3.class.getClassLoader().getResourceAsStream("jdbc.properties");

//Step3.导入输入流
prop.load(ins);

//Step4.属性读取
driverClass = prop.getProperty("jdbc.driverClassName");
url = String.format(prop.getProperty("jdbc.url"),connIp,connPort,conndatabase);
user = prop.getProperty("jdbc.username");
pass = prop.getProperty("jdbc.password");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println("Properties配置已经加载!");
}
}

public JDBCUtil() {
super();
// TODO Auto-generated constructor stub
}
public JDBCUtil(String ip,int port,String database) {
connIp = ip;
connPort = port;
conndatabase = database;
}

//注册工具方法
/**
* 驱动注册和建立链接
* @param user
* @param password
* @return Connection
* @throws SQLException
*/
public static Connection getConn() throws SQLException {
Connection conn = null;
System.out.println("连接字符串:" + url);
try {
//防止驱动二次注册自动注册驱动与建立连接
conn = DriverManager.getConnection(url, user, pass);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}


/**
* 释放工具类
* @param conn
* @param st
* @param rs
*/

// 注意点:导包需要查看是哪一个包的返回值
public static void release(Connection conn, Statement st, ResultSet rs) {
//注意点:关闭的顺序
closeRs(rs);
closeSt(st);
closeConn(conn);
System.out.println("END-资源释放完成!");
}

//私有静态方法-释放查询结果集
private static void closeRs(ResultSet rs) {
try {
if(rs != null)
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
rs = null;
}
}

//私有静态方法-释放statement对象
private static void closeSt(Statement st) {
try {
if(st != null)
st.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
st = null;
}
}

//私有静态方法-
private static void closeConn(Connection conn) {
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
conn = null;
}
}
}

注意事项:

  • 注:在实际使用中我们可以不用Class.forName()加载驱动了,在DriverManager.getConenect()方法中,在JDBC4.0版本后会在导入的数据库驱动包的Meta-INF中services里的java.sql.Driver驱动名称;
  • 注:在需要连接多个数据库的时候,为了区分最好是写上驱动名称当然不写也是可以的;
WeiyiGeek.Class.forName

WeiyiGeek.Class.forName


配置文件导入

描述:我们采用properties配置文件进行导入jdbc的驱动名称以及连接字符串和账号密码等等;

比如:在工程src目录下声明一个config.properties文件(如果是使用框架的时候一般会存放在WEB-INF里面):

1
2
3
4
driverClass=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student??useUnicode=true&characterEncoding=UTF-8
name=root
password=root

补充说明:

1
2
3
//使用类加载器去读取src底下的资源文件后面在servlet  
//对应文件位于src目录底下在编译打包后存在在bin目录同层中,使用getClassLoader()在加载字节码文件时候顺便也加载了我们制定的资源文件
//InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");

MySQL数据库的CRUD表述:

  • 1.Create
  • 2.Retrieve 读取
  • 3.Update
  • 4.Delete

MySQL的JDBC库执行SQL方法分类:

  • executeQuery(String sql)
  • executeUpdate(String sql) : 主要针对于DML的语句如insert,update,delete语句,并且返回影响的行数;

基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.weiyi.Jdbc;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import com.mysql.cj.jdbc.result.ResultSetMetaData;

import javafx.scene.control.SplitPane.Divider;

public class Demo3 {
/**
* JDBC 连接参数采用 Properties配置文件进行导入
* @param args
*/
//使用静态代码块读取属性(当类加载的时候执行)
static String driverClass = null;
static String url = null;
static String user = null;
static String pass = null;

static {
try {
//Step1.创建一个属性配置对象
Properties prop = new Properties();

//Step2.打开配置文件
//InputStream ins = new FileInputStream("jdbc.properties"); //在根目录中可以采用这种方式
InputStream ins = Demo3.class.getClassLoader().getResourceAsStream("jdbc.properties");

//Step3.导入输入流
prop.load(ins);

//Step4.属性读取
driverClass = prop.getProperty("jdbc.driverClassName");
url = prop.getProperty("jdbc.url");
user = prop.getProperty("jdbc.username");
pass = prop.getProperty("jdbc.password");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
System.out.println("- #ProPerties配置文件读取完成!");
}

}

public static void main(String[] args) {
//JDB连接标准文档
Connection conn = null;
Statement st = null;
ResultSet rs = null;

try {
//1.驱动注册
Class.forName(driverClass); //实际上这句话都可以免了在JDBC4.0之后他会自动在jdbc的数据库驱动jar包中元数据进行查看
//2.建立连接
conn = DriverManager.getConnection(url, user, pass);
//3.建立statement对象
st = conn.createStatement();
//4.构建SQL语句并且执行
rs = st.executeQuery("SELECT * FROM Persons WHERE (ID = 100)"); //Retrieve
//5.获取数据
ResultSetMetaData rsmd = (ResultSetMetaData) rs.getMetaData(); //获取源数据
int len = rsmd.getColumnCount(); //字段梳理
int count = 1;
while(count <= len) {
System.out.print(rsmd.getColumnName(count) + " | ");
count++;
}
System.out.println("");
while (rs.next()) {
System.out.println(rs.getInt("ID") + "| " + rs.getString("LastName"));
}

//6.数据库的CRUD;
String sql = "INSERT INTO Persons VALUE (NULL,'Weiyi')"; //主要Primary Key 插入主键的时候可以用null代替
int result = st.executeUpdate(sql); //该方法主要执行 INSERT, UPDATE or DELETE 等语句,返回影响的行数
//如果返回值大于0则表明操作成功,否则失败;
if(result > 0) {
System.out.println("插入成功 : "+ sql);
}else {
System.out.println("插入失败");
}

// U :
String sqlUpdate = "UPDATE Persons SET LastName = 'Whoami' WHERE ID = 100";
result = st.executeUpdate(sqlUpdate);
if(result > 0) {
System.out.println("更新成功 : " + sqlUpdate);
}else {
System.out.println("更新失败");
}

// D:
String sqlDelete = "DELETE FROM Persons WHERE LastName = 'Weiyi'";
result = st.executeUpdate(sqlDelete);
if( result > 0) {
System.out.println("删除成功 : " + sqlDelete);
}else {
System.out.println("删除失败");
}

} catch (SQLException e) {
// TODO: handle exception
} finally {
//End.释放资源
JDBCUtil.release(conn, st, rs);
}
}
}

执行结果:

1
2
3
4
5
6
7
8
9
- #ProPerties配置文件读取完成!
查询成功 : SELECT * FROM Persons WHERE (ID = 100)
ID | LastName |
100| Whoami
插入成功 : INSERT INTO Persons VALUE (NULL,'Weiyi')
更新成功 : UPDATE Persons SET LastName = 'Whoami' WHERE ID = 100
删除成功 : DELETE FROM Persons WHERE LastName = 'Weiyi'
读取完成
END-资源释放完成!


junit 单元测试

描述:在写代码的时候我们常常需要对所写的代码进行测试,如果直接在别人开发code的包下测试是比较混乱,建议将里面的方法进行抽取到测试包里,然后采用junit框架进行测试;

操作步骤:

  • 1.创建一个测试包依赖包org.junit.Test,定义一个类Testxxx然后在里面定义需要测试的方法,它还可建立多个方法进行测试;
  • 2.添加junit框架的支持及引入jar包:工程右键 - build path - add Library - Junit - Junit4;
  • 3.在需要测试的方法上面加入注解标记 @Test
  • 4.光标选择所需要测试的方法名称然后右键执行单元测试或者是打开outline视图,然后选择方法右键采用junit执行;
WeiyiGeek.junit

WeiyiGeek.junit

基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.weiyi.Test;

import java.util.Date;
import org.junit.Test;

/**
* 使用junit进行单元测试
* @author WeiyiGeek
*/
public class MainTest {

//单元测试需要测试的方法
@Test
public void testPrint() {
Date date = new Date();
System.out.println("当前时间:" + date.toGMTString());
}
}

WeiyiGeek.jUnit单元测试

WeiyiGeek.jUnit单元测试


Dao 开发模式

描述:Data Access Object 数据访问对象.即使用包类对象实现一个功能;

模式建立流程;

  • 1.建立一个dao的接口,里面声明数据库访问规则;
  • 2.新建一个dao的实现类,具体实现早前定义的规则;
  • 3.建立一个调用dao接口中的方法进行展示实现类的运行结果;
1
2
3
4
5
6
7
8
9
10
-- (1) 建立一个测试的表
CREATE TABLE user_log (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '序号',
userLogin VARCHAR(255) NOT NULL COMMENT '登陆名称',
userPass VARCHAR(255) NOT NULL COMMENT '登陆密码',
loginTime DATETIME NOT NULL DEFAULT NOW() COMMENT '登陆时间'
);

-- (2) 数据插入
INSERT INTO user_log(id,userLogin) VALUES (NULL,'WeiyiGeek');
WeiyiGeek.

WeiyiGeek.

基础示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 1.接口
package com.weiyi.dao;
public interface userDao {
/**
* Dao模型-接口定义操作数据库的方法
*/
void findAll();
}


// 2.实现接口
package com.weiyi.dao.impl;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.weiyi.Jdbc.JDBCUtil;
import com.weiyi.dao.userDao;
public class userDaoImpl implements userDao{
//JDBC连接标准文档
Connection conn = null;
Statement st = null;
ResultSet rs = null;

@Override
public void findAll() {
try {
//1.驱动加载和建立链接
conn = JDBCUtil.getConn();
//2.建立流对象statement
st = conn.createStatement();
//3.构建执行SQL语句 (这里的SQL语句是拼接的容易导致安全问题)
rs = st.executeQuery("SELECT * FROM user_log");
while(rs.next()){
System.out.println(rs.getInt("id") + " " + rs.getString("userLogin") + " " + rs.getDate("loginTime"));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtil.release(conn, st, rs);
}
}
}


// 3.调用接口
package com.weiyi.Jdbc;
import com.weiyi.dao.userDao;
import com.weiyi.dao.impl.userDaoImpl;
/**
* 调用 dao 模型 实现的接口方法
* @author WeiyiGeek
*/
public class Demo4 {
//注意:这里测试也可以采用Junit单元测试
public static void main(String[] args) {
//Step1.父类指向子类实现的接口
userDao dao = new userDaoImpl();
//Step2.直接调用其方法不用管具体的实现
dao.findAll();
}
}

运行结果:
1
2
3
4
Properties配置已经加载!
连接字符串:jdbc:mysql://11.62.17.20:3001/Demo?useUnicode=true&characterEncoding=UTF-8
1 WeiyiGeek 2020-01-14
END-资源释放完成!


0x02 JDBC安全开发

描述:做过渗透测试的人员和经验丰富的开发人员一定是知道对于数据库读取、插入数据的业务常常容易被SQL Inject,所以为了解决这一个安全问题常常采用占位绑定进行防止SQL注入,类似于PHP的PDO扩展类库中的PDO预处理语句;

注意:Statement 对象的安全问题;

  • 1.Statement对象执行SQL语句其实是将SQL语句先拼接再执行;
  • 2.比如下面的实例进行拼接了SQL语句,先拼接SQL语句然后再一起执行;但是如果变量中带有数据库的关键字,那么在进行查询的时候将会被认为是数据库查询的关键字,从而引发安全问题;
    1
    2
    3
    4
    5
    6
    7
    8
    -- 在Statement对象中存在的安全问题
    String sql = "SELECT * FROM user_log WHERE userLogin = '" + user + "' AND userPass = md5('" + pass + "')";

    -- 但是如果请求传入的参数是下面这样的边会出现问题
    http:\/\/127.0.0.1/login.jsp?user=Admin&pass=123456') or '1=1' or md5('123456

    -- 安全问题:最原始的万能登陆密码(现在基本不存在这样的漏洞)
    SELECT * FROM user_log WHERE userLogin='WeiyiGeek' AND userPass = MD5('123456') OR '1=1'
WeiyiGeek.Statement安全

WeiyiGeek.Statement安全


1.预解析

解决办法 :

  • (1) PrepareStatement 该对象就是替换前面的statement对象, 相比较以前的statement, 预先处理给定的sql语句对其执行语法检查。
  • (2) 在sql语句里面使用 ? 占位符来替代后续要传递进来的变量; 后面进来的变量值,将会被看成是字符串,不会产生任何的关键字。
1
2
3
4
String sql = "insert into t_user values(null , ? , ?)";
ps = conn.prepareStatement(sql);
//给占位符赋值 从左到右数过来,1 代表第一个问号, 永远你是1开始。
ps.setString(1, userName);


基础实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// dao 模型接口
package com.weiyi.dao;
public interface userDao {
/** Dao模型-接口定义操作数据库的方法*/
void psLogin(String user,String pass,int x);
void psInsert(String user,String pass);
void psUpdate(String user,int id);
void psDelete(String user,String pass);
}


// dao 模型接口具体实现
package com.weiyi.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import com.weiyi.Jdbc.JDBCUtil;
import com.weiyi.dao.userDao;

public class userDaoImpl implements userDao{
//JDBC连接标准文档
Connection conn = null;
Statement st = null;
ResultSet rs = null;


/*** 创建PreparedStatement预处理对象 */
@Override
public void psLogin(String user,String pass, int x) {
try {
//1.载入数据库驱动建立链接
conn = JDBCUtil.getConn();
//2.构建SQL语句以及预处理SQL语句
String sql = "SELECT * FROM user_log WHERE userLogin = ? AND userPass = md5(?) AND id = ?"; //? 占位符合后面会将绑定的参数进行格式化后传入
PreparedStatement ps = conn.prepareStatement(sql); //关键点1:对我们的SQL语句进行预处理如果SQL语法错误的将不能通过解析
ps.setString(1, user); //关键点2:进行传入参数的位置开始绑定从,1代表第一个问号,2同理;
ps.setString(2, pass); //关键点3:注意传入的类型是可以根据需要来设置的;
ps.setInt(3, x);
//3.执行预处理好的的对象
rs = ps.executeQuery();
if(rs.next()) {
System.out.println("登陆成功!");
System.out.println("欢迎您" + rs.getString("userLogin") + "管理员, 注册时间: " + rs.getTimestamp("loginTime"));
} else {
System.out.println("登陆失败");
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
JDBCUtil.release(conn, st, rs);
}
}


@Override
public void psInsert(String user, String pass) {
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConn();
String sql = "INSERT INTO user_log(userLogin,userPass) VALUES (?,md5(?))";
ps = conn.prepareStatement(sql);
ps.setString(1, user);
ps.setString(2, pass);
int result = ps.executeUpdate();
if(result > 0) {
System.out.println("成功添加" + user + "用户!");
}else {
System.out.println("用户注册失败!");
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps);
}
}

@Override
public void psUpdate(String user, int id) {
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConn();
String sql = "UPDATE user_log SET userLogin = ? WHERE id = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, user);
ps.setInt(2, id);
int result = ps.executeUpdate();
if(result>0) {
System.out.println("更新用户ID = " + id + ", 为 " + user+"用户成功!");
}else {
System.out.println("更新失败");
}
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps);
}
}

@Override
public void psDelete(String user, String pass) {
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConn();
String sql = "DELETE FROM user_log WHERE userLogin = ? AND userPass = md5(?)";
ps = conn.prepareStatement(sql);
ps.setString(1, user);
ps.setString(2, pass);
int result = ps.executeUpdate();
if(result > 0){
System.out.println("用户 " + user +" 删除成功!");
}else {
System.out.println("删除失败");
}

} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps);
}
}
}

执行结果:

1
2
3
4
5
6
7
Properties配置已经加载!
连接字符串:jdbc:mysql://127.0.0.1:3301/Demo?useUnicode=true&characterEncoding=UTF-8
登陆成功!
欢迎您WeiyiGeek管理员, 注册时间: 2020-01-14 12:42:20.0
更新用户ID = 2, 为 Admin用户成功!
用户 Admin 删除成功!
END-资源释放完成!


0x04 完善自定义工具类

描述:主要针对于DbUtils对JDBC的封装的开源工具类库原理进行了解,并自定义写出实现主要的功能(非常重要);

关键知识点:

  • 可变参数
  • 元数据(Meata data )
  • 接口实现类对象
  • 泛型(T) : 类与方法


什么是元数据?

描述数据的数据 String sql , 即描述这份sql字符串的数据叫做元数据

  • 数据库元数据 DatabaseMetaData
  • 参数元数据 ParameterMetaData
  • 结果集元数据 ResultSetMetaData

基础示例:
/User/src/top/weiyigeek/Util/ResultSetHandler.java

1
2
3
4
5
6
7
8
public interface ResultSetHandler<T> {
/**
* @用户进行接口实现类对象(实现获取查询后的数据按照调用者的意愿进行封装)
* @param rs
* @return
*/
T sethandler(ResultSet rs);
}

/User/src/top/weiyigeek/Util/DBUtilTools.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package top.weiyigeek.Util;

import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
*
* @Desc: 扩充JDBC自定义连接数据库,通用方法执行增删改查
* @author WeiyiGeek
* @CreatTime 下午9:51:05
*/
public class DBUtilTools {
/**
* @Desc: 通用方法进行 插入 删除 与 更新 (DML)
* @param sql : INSERT INTO / DELETE FROM / UPDATE
* @param args : 可变参数(重要)
* @return
*/
public static int update(String sql,Object...args) {
int flag = 0;
Connection conn = null;
PreparedStatement ps = null;
try {
conn = db1.getConn();
ps = conn.prepareStatement(sql);
//利用参数元数据获取执行SQL语句中的参数个数(即有几个问号)
ParameterMetaData metaData = ps.getParameterMetaData();
int count = metaData.getParameterCount();

//进行SQL执行的参数绑定,因为不知道是什么类型的数据所以都使用setObject来处理
for(int i = 0; i < count; i++) {
ps.setObject(i+1, args[i]);
}
//执行CURD语句()
flag = ps.executeUpdate();
return flag;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
db1.release(conn, ps, null);
}
return -1;
}

/**
* @Desc: 通用方法进行 数据库数据的查询 并让调用者自己实现数据的封装(接口实现类对象)
* @param <T>
* @param sql
* @param handler
* @param args
* @return
*/
public static <T> T query(String sql, ResultSetHandler<T> handler,Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = db1.getConn();
ps = conn.prepareStatement(sql);
//利用参数元数据获取执行SQL语句中的参数个数(即有几个问号)
ParameterMetaData metaData = ps.getParameterMetaData();
int count = metaData.getParameterCount();

//进行SQL执行的参数绑定,因为不知道是什么类型的数据所以都使用setObject来处理
for(int i = 0; i < count; i++) {
ps.setObject(i+1, args[i]);
}

//执行CURD语句
rs = ps.executeQuery();
T t = handler.sethandler(rs);
return t;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
db1.release(conn, ps, null);
}
return null;
}
}

测试代码main:/User/src/top/weiyigeek/DBUtil/DBUtilToolsDemo2.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package top.weiyigeek.DBUtil;

import java.sql.ResultSet;

import top.weiyigeek.Util.DBUtilTools;
import top.weiyigeek.Util.ResultSetHandler;

public class DBUtilToolsDemo2 {

public static void main(String[] args) {
//1.测试DML语句
//int flag = DBUtilTools.update("INSERT INTO account value(null,?,?)", "法外狂徒",50);
//System.out.println("影响条数:"+flag);

//int flag = DBUtilTools.update("UPDATE account SET name = ?,money = money + ? WHERE id = ?", "法外狂徒",150,4);
//System.out.println("影响条数:"+flag);

//2.测试QUERY语句(接口实现类对象)
Account a = DBUtilTools.query("SELECT * FROM account where id = ?", new ResultSetHandler<Account>() {
@Override
public Account sethandler(ResultSet rs) {
// TODO Auto-generated method stub
Account work = new Account();
try {
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int money = rs.getInt("money");
work.setId(id);
work.setName(name);
work.setMoney(money);
}
return work;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
}
}

}, 4);
System.out.println(a.toString());
}
}

执行结果:

WeiyiGeek.

WeiyiGeek.