概述
文章目录
- 一.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 自定义封装工具类及方法
- - JDBCUtils
- -JDBCMethod
- 五.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有一定的了解,接下来我们直接进入正题吧!
二.JDBC 环境依赖相关以及快速案例
1.依赖引入:
这里我们使用maven来管理项目:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
2.数据库环境介绍:
这里我们使用mysql数据库,并建立一张表供我们后续操作持续使用:
我们建立一个学生表
下面是建立表和插入的测试数据,
/*Table structure for table `student` */
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;
/*Data for the table `student` */
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!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
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获取所有的查询信息呢?
首先我们要创建一个实体类对象来接受我们要查询的数据:
// 创建一个列表用于存储查询到的实体集
List<Student> list=new ArrayList<Student>();
// 创建一个元数据对象,该元数据对象可以获取查询出来结果的列的数量,列的名称,列的别名等,列的数据类型等
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对象中的属性(即赋值),这样循环下去,我们就可以获取多个对象对应的多个数据!
----------------------------分割线-------------------------------------------------------------
2021/12/16 剩下文章后续继续更新,后面我们将自己建立一个工具类来封装之前的代码,增加可用性
2021/12/18 今天下午考完了六级,虽不是很理想,但是博客更新还是要继续的。????
----------------------------分割线-------------------------------------------------------------
5.ResultSet
ResultSet类我们前面也看见了,它是执行select语句返回出来的一个结果集,这个结果集可能只有一个数据、也可能有多个数据。ResultSet.next()这个next()方法会判断下一行是否有数据,如果有数据则返回true并将光标移向下一行。(注意:ResultSet光标最开始在第一行的前面)
既然ResultSet当前行有数据,那怎么把当前行的数据获取出来呢?
方法如下:
//通过当前行的列标签获取对应数据
Object getObject(String columnLabel);
//通过当前行的列索引获取数据(从0开始)
Object getObject(int columnIndex) ;
虽然这种方式可以获取数据,但是必须要我们事先知道查询结果的列别名或列的数量。所以又出现问题,如何获取结果集的当前行的列名和列的数量
6.ResultSetMetaData
该对象可以获取结果数据中行的各种结构信息(包括列名和列的数量):
获取ResultSetMetaData的方法:
//获取元数据对象
ResultSetMetaData metaData = resultSet.getMetaData();
这下我们获取了这个元数据对象,接下来我们继续讲解如何获取对应行的列名和列数据:
//获取列的数量
int len=metaData.getColumnCount();
//通过列的下标获取相应列的列别名(没有别名就获取原始名称)
String columnLabel = metaData.getColumnLabel(i + 1);
这样我们就可以通过循环、配合反射的方式获取每列的列名字和列属性了,具体操作如下:
//循环检测resultset结果集中是否还有数据
while(resultSet.next()){
//创建一个空的实例对象,后面通过反射对其注入值
Student student = new Student();
//该行有数据,就遍历(列的数量len)次,通过下标获取数据和列名
for (int i = 0; i < len; i++) {
//获取值
Object object = resultSet.getObject(i + 1);
//获取列别名,没有别名就列原名
String columnLabel = metaData.getColumnLabel(i + 1);
// 使用反射:将查询出来的列名注入其对应的值(这里也就间接的要求了数据库的名字必须和java中创建的实体类属性名字相同)
Field declaredField = student.getClass().getDeclaredField(columnLabel);
declaredField.setAccessible(true);
declaredField.set(student,object);
//将每次查到的数据加到列表中
list.add(student);
}
}
上面讲到了反射,如果对反射还不了解的,可以看看我写的反射博客
四.JDBC 自定义封装工具类及方法
其实很多框架如mybatis持久化框架就已经对jdbc进行了封装。这里我们不将mybatis,为了简化我们的代码,增加开发效率,这里我们写几个平常常用的工具类:
大家可以直接复制粘贴到idea中进行使用,不过要记得修改一些配置文件,以下讲解过程都会讲到
- JDBCUtils
- 关于获取连接和关闭连接的功能封装
首先是配置文件jdbc.properties
classDriverName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/zjdata
username=root
password=12345678
使用mysql时上面的除了classDriverName其他都需要根据自己的数据库进行修改。
下面创建JDBCUtils.java
//获取连接对象,前提要创建jdbc.properties文件并正确配置
public static Connection getConnection() throws IOException, ClassNotFoundException, SQLException {
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("JDBC.properties");
Properties properties = new Properties();
properties.load(resourceAsStream);
Class.forName(properties.getProperty("classDriverName"));
Connection connection = DriverManager.getConnection(properties.getProperty("url"), properties.getProperty("username"), properties.getProperty("password"));
return connection;
}
//关闭连接需要传入连接对象和Statement对象
public static void close(Connection connection, Statement statement) throws SQLException {
if(connection!=null) connection.close();
if(statement!=null) statement.close();
}
-JDBCMethod
这里提供jdbc中sql语句的封装。下面的实现首先要完成JDBCUtils的导入。
//更改通用操作,为了配合后面的事务操作和连接池操作,这里方法的参数需要传入connection连接对象。sql表示要输入的sql语句,args表示sql语句中?的占位
public static int update(Connection connection,String sql,Object...args) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
int len=args.length;
for (int i = 0; i < len; i++) {
preparedStatement.setObject(i+1,args[i]);
}
int i = preparedStatement.executeUpdate();
close(connection,preparedStatement);
return i;
}
//通用查询操作BookUser
//如果要查询自己的对象,就把下面的BookUser类改为自己的类即可。
public static List<BookUser> select(Connection connection,String sql,Object ...args) throws SQLException, NoSuchFieldException, IllegalAccessException {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
int len=args.length;
for (int i = 0; i < len; i++) {
preparedStatement.setObject(i+1,args[i]);
}
ResultSet resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
ArrayList<BookUser> list= new ArrayList<BookUser>();
while(resultSet.next()){
BookUser bookUser = new BookUser();
for (int i = 0; i < columnCount; i++) {
String columnLabel = metaData.getColumnLabel(i + 1);
Object object = resultSet.getObject(i+1);
Field field = bookUser.getClass().getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(bookUser, object);
}
list.add(bookUser);
}
return list;
}
五.JDBC事务
最后
以上就是苗条煎蛋为你收集整理的你真的会JDBC吗?一.JDBC概述二.JDBC 环境依赖相关以及快速案例三.JDBC相关API详解四.JDBC 自定义封装工具类及方法五.JDBC事务的全部内容,希望文章能够帮你解决你真的会JDBC吗?一.JDBC概述二.JDBC 环境依赖相关以及快速案例三.JDBC相关API详解四.JDBC 自定义封装工具类及方法五.JDBC事务所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复