- 一.JDBC概述
- 二.JDBC 环境依赖相关以及快速案例
- 1.依赖引入:
- 2.数据库环境介绍:
- 3.快速使用JDBC
- 三.JDBC相关API详解
- 1.Driver类
- 2.DriverManager类
- 3.Connection
- 4.statement与PreparedStatement的区别
- 3.1 更改操作
- 3.2 查询语句
- 5.ResultSet
- 6.ResultSetmetaData
- 四.JDBC 自定义封装工具类及方法
市面上很多人建议都是简单的学习jdbc,因为后面都会使用mybatis相关框架进行封装。虽然确实在实际开发中很少小关于jdbc的代码。但是对于一个代码热爱者来说,我们还是有必要进一步学习jdbc,首先在学习mybatis、spring等框架源码时,我们必不可少地会接触到jdbc的源码;再者,我们可以在jdbc中学习到很多知识和思维:工厂模式、装饰者模式、反射、集合框架等。
首先我们来看看本文章的思维导图:
一.JDBC概述- JDBC是java中的一个接口,各种数据库如mysql、sqlserver等数据都需要实现这个接口,才能够使得java能够使用相关产品的数据库,如果哪一个数据库不提供相关的JDBC实现类,就会被“踢出”java生态。
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
- 目前我们使用的框架如mybatis其底层就是使用的JDBC写的,所以我们现在学习JDBC也是为了以后学习mybatis源码打好基础。(当然像反射中的动态代理、设计模式等同样很重要)
二.JDBC 环境依赖相关以及快速案例 1.依赖引入:能看到本文章的可能都会对JDBC有一定的了解,接下来我们直接进入正题吧!
这里我们使用maven来管理项目:
2.数据库环境介绍:mysql mysql-connector-java 8.0.25
这里我们使用mysql数据库,并建立一张表供我们后续操作持续使用:
我们建立一个学生表
下面是建立表和插入的测试数据,
DROp TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(225) NOT NULL, `age` int DEFAULT NULL, `sex` varchar(225) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; insert into `student`(`id`,`username`,`age`,`sex`) values (1,'以某',22,'男'),(2,'小二',19,'男'),(3,'张三',12,'男'),(4,'李四',12,'男'),(5,'王五',22,'男');3.快速使用JDBC
@Test
public void demo1() throws SQLException {
Driver driver = new Driver(); //获取Driver驱动
//填写数据源相关信息
String url="jdbc:mysql://localhost:3306/zjdata";
String user="root";
String password="12345678";
DriverManager.registerDriver(driver);
//获取连接
Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
String sql="delete from student where id="+5;
statement.execute("delete from student where id=5");
//关闭连接
connection.close();
}
以上通过JDBC实现了数据库中id为5的数据的删除,当然这只是初级的使用,后面还有更高级的用法。
三.JDBC相关API详解 1.Driver类一个是java.sql包下的Driver抽象类,里面给出了Driver的各种规定方法,各种数据库厂商需要根据此抽象类编写相关的数据库驱动,我们程序员不需要面向该接口进行编程。
另一个则是相关的数据库厂商编写出来的Driver实现类,就拿mysql的驱动包举例:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
这个类就是mysql方法给我们提供的驱动类,平时我们在写jdbc时,就必须把这个类加载到内存中并注册。在我的快速案例中写的代码是:
Driver driver = new Driver(); DriverManager.registerDriver(driver);
首先这个 DriverManager.registerDriver(driver); 我们可以省略不写,同样可以成功,因为在Driver中有static代码块:
static {
try {
//static代码块中实现驱动的注册,即当Driver类加载到内存的过程中自动实现了驱动的注册
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
该静态代码块在加载时就会为我们执行Driver的注册操作。
另外对于new Driver() 其实写其的主要目的是将Driver这个驱动类加载到内存当中,我们在学习反射时,知道常用的三种方式将类加载到内存中:
Driver driver = new Driver(); //获取Driver驱动
Class.forName("com.mysql.jdbc.Driver");
ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");
上面这三种加载方式中,我们最常用的就是 Class.forName(“com.mysql.jdbc.Driver”); 以后我们加载驱动时就用该代码。
2.DriverManager类该类主要用于管理驱动的类,我们通过该类调用getConnection()方法来获得相关的数据库连接。当然在我们注册驱动(加载Driver时自动注册)之后、获取连接之前需要获取连接mysql的相关信息,这里的getConnection参数就提示了我们要填写的信息:
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
这里需要我们填写的分别是:
- url:
填写要连接的数据库地址 jdbc:mysql://localhost:3306/zjdata
其中jdbc:mysql代表协议字段,localhost:3306代表自己数据库连接地址,这里指的是本机数据库,zjdata是要连接的数据库名称 - user:数据库登录名
- password:数据库登录密码
我们将这些参数设置过后,就能正常获得我们的连接对象connection了!!
另外提一下:我们现在写的Driver路径、url、user、password都高度耦合在代码当中,这样其实是非常不好的,我们如果要修改就会很麻烦,所以建议通过properties文件来写这些需要手动输入的东西。
在resource目录下建立jdbc.xml:
url=jdbc:mysql://localhost:3306/zjdata driverClassName=com.mysql.jdbc.Driver username=root password=12345678
在java代码中,我们通过properties类来读取properties文件来加载相关信息:
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(resourceAsStream);
//从properties文件中获取信息
String username = properties.getProperty("username");
String password = properties.getProperty("password");
String driverClassName = properties.getProperty("driverClassName");
String url = properties.getProperty("url");
Class.forName(driverClassName);
Connection connection = DriverManager.getConnection(url, username, password);
以上通过文件的方式减少了代码耦合性。
3.Connection该连接对象其实表示的是mysql客户端与mysql服务端的一个连接,通常只有我们只有我们获取了对象后才能真正的使用sql相关语句和事务相关操作。获取该对象主要通过DriverManager获取或者通过数据库连接池对象(后面介绍)来获取
4.statement与PreparedStatement的区别当我们获取了连接对象后,我们就可以通过connection调用方法来获取statement对象:
- statement:不支持预编译,只能通过拼串的方式来解决占位问题,例如:
public void insert delete(String id,Statement statement){
String sql="delete from studen where id="+id;
statement.execute(sql);
}
其中进行了拼串操作,也会有一定的开销。
另外statement也会出现sql注入问题:
public void insert delete(String id,Statement statement){
String sql="select id,name,age,sex from studen where id="+id;
statement.execute(sql);
//本来查询出来的应该是一条数据。但是如果数据库攻击者将id的值设为“1 or id>-100”,
//这样实际语句就是select id,name,age,sex from studen where id=1 or id>-100,查询出来的就不是一条语句,而是所有语句。其原因就是mysql误将id中的语句or等看成了关键字,从而引起了sql注入问题。
}
所以在我们平时的开发中,不会使用statement二而是使用preparedStatement,其采用了预编译的机制,可以有效解决预编译的问题:
//通过连接对象connection获取预编译对象preparedStatement对象:
PreparedStatement ps=connection.prepareStatement("delete from student where id=?");
//sql字符串语句中,占位通过?进行占位操作
ps.setObject(1,3); //设置sql语句中?的值,最小从1开始,这里我们删除id为3的学生。
//执行:
boolean flag= ps.excute();
上面我们看到了PreparedStatement的两个方法:
- setObject(int parameterIndex, Object x) 用于设置占位符的值
- excute() 执行预编译的语句,返回true则表示使用select返回了结果集,返回false则表示返回了影响行数。
接下来我们详细介绍更改数据库和查询数据库的具体操作以及相关Api:
3.1 更改操作更改操作包括增删改:
我们调用预编译对象的excuteupdate()方法来返回影响行数。
int i=ps.excuteupde() //返回更新的影响的行数。3.2 查询语句
其实更改操作都是比较简单的,下面介绍一下相对较为复杂的查询语句:
调用预编译对象的excuteQuery() 方法,返回的结果集 ResultSet:
ResultSet resultSet = preparedStatement.executeQuery();
这个结果集对象可能包含了多行数据,我们怎么在java中通过ResultSet获取所有的查询信息呢?
首先我们要创建一个实体类对象来接受我们要查询的数据:
// 创建一个列表用于存储查询到的实体集 Listlist=new ArrayList (); // 创建一个元数据对象,该元数据对象可以获取查询出来结果的列的数量,列的名称,列的别名等,列的数据类型等 ResultSetmetaData metaData = resultSet.getmetaData(); // 获取有多少列 int len=metaData.getColumnCount(); while(resultSet.next()){ Student student = new Student(); for (int i = 0; i < len; i++) { Object object = resultSet.getObject(i + 1); //获取列别名,没有别名就列原名 String columnLabel = metaData.getColumnLabel(i + 1); // 使用反射 Field declaredField = student.getClass().getDeclaredField(columnLabel); declaredField.setAccessible(true); declaredField.set(student,object); list.add(student); } }
上面代码的思路就是首先由一个实体类Student来接受数据(注意该实体类必须对象表的各个字段类型和名称要相等),之后通过结果集获得元数据对象metadata,该对象主要是封装了返回数据的各个字段信息,包括列名、列数量等,我们获取了列名后,就可以结果集获取相应的列的数据,然后通过反射机制,强制修改student对象中的属性(即赋值),这样循环下去,我们就可以获取多个对象对应的多个数据!
5.ResultSet 6.ResultSetmetaData 四.JDBC 自定义封装工具类及方法----------------------------分割线-------------------------------------------------------------
2021/12/16 剩下文章后续继续更新,后面我们将自己建立一个工具类来封装之前的代码,增加可用性



