快捷搜索:  汽车  科技

jdbc数据库入门教学(JDBC操作数据库实操)

jdbc数据库入门教学(JDBC操作数据库实操)<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency>如果想手动添加jar包依赖,到mysql的官网下载页dev.mysql.com/downloads/c…这里要选择与平台无关的选项,下载的压缩包解压后里面就有对应的jar包,自行导入即可public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { registerDriver(driver

java Database Connectivity (Java语言连接数据库)

jdbc是Sun公司指定的一套接口(Interface)

目的是解耦合,降低程序的耦合度,提高程序的扩展力,多态机制就是非常典型的,面向抽象编程

为什么Sun要指定一套JAVA SQL的接口?

因为每个数据库都存在差异,实现原理不同,Oracle有自己的原理,MySQL有自己的原理,通过这套实现需要厂家进行实现,开发者无需关系底层逻辑

假设现在有个需求,要将Oracle换成MySQL数据库,此时只有接口能做到切换时只需要更换数据库驱动即可无缝切换,代码层完全不用修改(或小改,可能有些语法是特有的)

所谓的数据库驱动就是Jar包,里面就是一堆JDBC的实现类,使用maven时,只需要在依赖项里添加对应依赖即可,或者手动添加jar包

开始实操

既然是操作数据库,就需要有数据库的支持,这里采用Docker安装Mysql并进行数据插入

  • 安装启动MySQL:docker run --name testDbContainer -v ~/testDb:/root -e MYSQL_ROOT_PASSWORD=12345678 -e MYSQL_DATABASE=testDb -p 3306:3306 -d mySQL:8.0.29-oracle
  • 插入数据

drop table if exists t_user; create table t_user ( id int primary key auto_increment username varchar(10) unique age int(3) gender int(1) comment '1表示男,0表示女' ); insert into t_user (username age gender) values('Jack' 15 1) ('Mike' 20 1) ('Susan' 22 0);Java配置

使用JDBC进行编程,需要先注册驱动,告诉Java程序要连接什么数据库,Oracle还是MySQL,还是H2等等,需要用到java.sql包下的DriverManager类,有一个静态方法registerDriver

public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException { registerDriver(driver null); }

方法要求传入的是接口(面向接口编程),此时就需要去找数据库厂商提供的驱动类,他们会负责Driver接口的实现类

  1. 使用maven管理的项目比较简单,只需要引入依赖即可

<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency>

  1. 如果想手动添加jar包依赖,到mysql的官网下载页dev.mysql.com/downloads/c…

jdbc数据库入门教学(JDBC操作数据库实操)(1)

这里要选择与平台无关的选项,下载的压缩包解压后里面就有对应的jar包,自行导入即可

这里有一个注意事项,新版的驱动包名已变更,如果用的还是com.mysql.jdbc包中的Driver类,在加载类时就会输出此类不再推荐使用,正确的包名是com.mysql.cj.jdbc.Driver

public class Driver extends com.mysql.cj.jdbc.Driver { public Driver() throws SQLException { } static { System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary."); } }

再看看真正的使用的Driver类

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!"); } } }

在静态代码块中,有一行DriverManager.registerDriver(new Driver());,原本应该开发者去注册的事,加载类的时候就已经做了,所以只需要让JVM去加载类即可实现注册 最简单的便是Class.forName("")方法

public class App { public static void main(String[] args) throws SQLException ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); } }第二步:获取连接

表示JVM进程和数据库之间的通道打开,使用完需要关闭通道释放资源,主要方法为DriverManager.getConnection方法,入参为数据库URL,账号,密码,返回值是Connection对象,不同数据库的URL不一样,用到什么搜什么即可

public class App { public static void main(String[] args) throws SQLException ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/testDb"; String username = "root"; String password = "12345678"; Connection connection = DriverManager.getConnection(url username password); } }第三-第四步:获取数据库操作对象,执行SQL

这个对象主要用于执行sql语句,Connection.createstatement/prepareStatement调用这两个方法,这里先演示createStatement,后面会说到区别

第五步:获取结果集

如果执行的是DQL语句,那么将会返回一个ResultSet对象,表示数据表的映射,如果是DML语句,返回的则是int,表示sql语句所影响的行数。

讲讲ResultSet这个对象的next方法,执行DQL语句调用executeQuery方法,假设执行了某张表的select * 语句,想要获取结果集的数据需要调用ResultSet.next()方法,光标会向下移动一行,如果有数据则返回true,此时操作ResultSet对象就可以获取对应的行数据,再次调用next又会将光标向下移动一行,直到没有数据了返回false

jdbc数据库入门教学(JDBC操作数据库实操)(2)

ResultSet.getXXX方法可以获取对应的列,使用方式有
  1. 索引,如果返回字段顺序为id name age,1则代表id 以此类推(JDBC中的所有操作索引从1开始),不推荐,语义不清晰,没有办法通过代码直观到看出预期
  2. 键名,直接使用字段名作为参数,如果select写了字段的别名,则需要用别名去取值
  3. 提供get数据类型的方法,比如字段id类型为int,则可以用getInt获取id,如果使用getString,不管数据类型是什么都会按照String的形式取出

public class App { public static void main(String[] args) throws SQLException ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/testDb"; String username = "root"; String password = "12345678"; Connection connection = DriverManager.getConnection(url username password); Statement statement = connection.createStatement(); String sql = "select * from t_user;"; ResultSet resultSet = statement.executeQuery(sql); // 通过while循环获取全部结果集,返回false则跳出循环 while (resultSet.next()) { System.out.println(resultSet.getString("username")); } }

示例通过列名去除了所有结果集的用户名

jdbc数据库入门教学(JDBC操作数据库实操)(3)

第六步:释放资源

当所有操作结束后,需要将ResultSet(如果有) Statement,Connection的close方法从左到右执行,释放掉资源避免不必要的浪费

public class App { public static void main(String[] args) throws SQLException ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/testDb"; String username = "root"; String password = "12345678"; Connection connection = DriverManager.getConnection(url username password); Statement statement = connection.createStatement(); String sql = "select * from t_user;"; ResultSet resultSet = statement.executeQuery(sql); while (resultSet.next()) { System.out.println(resultSet.getString("username")); } resultSet.close(); statement.close(); connection.close(); }

从JDK7开始,便支持了try with resources,只要资源实现了Autocloseable接口,作用是在块代码执行后,自动释放资源,也就是自动执行close方法。
查看类源码可以看到,ResultSet Statement,Connection三个类都实现了AutoCloseable接口,所以可以改写让代码更整洁,多个资源用分号进行隔开

public class App { public static void main(String[] args) throws SQLException ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/testDb"; String username = "root"; String password = "12345678"; String sql = "select * from t_user;"; try (Connection connection = DriverManager.getConnection(url username password); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); ) { while (resultSet.next()) { System.out.println("用户名:" resultSet.getString("username")); } } } }

可以不使用,但可以作为一个知识点了解

SQL注入问题

假设这是一个提供给外部的接口服务,会接受用户的传参数据,由于creaetStatement方法是先进行sql语句都拼接,再进行sql的编译,所以可以会将用户输入的恶意关键字一同编译,像下面代码中用户传了字符串' or '1' = '1,仿佛猜透了服务端对sql拼接规则,由于加了必定成功的条件,所以即使传的参数不对也会执行成功

// 其他代码省略 Statment st = connection.createStatment(); // 用户传入的值 String value = "' or '1' = '1"; String sql = "select * from t_user where username = '" value "'"; ResultSet rs = st.executeQuery(sql); if(rs.next()){ System.out.println(rs.getString("username"); // 这里会输出 }

这种欺骗了服务器令其执行了非预期的查询的行为,便称为SQL注入

要解决有两种方案

  1. 从参数解决,对用户的输入进行特定规则的校验
  2. 让用户的传参不参与SQL语句的编译

更应该使用第二种解决方案

PreparedStatement对象就可以完美处理注入问题,在使用时需要先将SQL语句进行编译,需要用户传参的地方用占位符替代,编译后再将值传到原本的占位符中

public static void main(String[] args) throws SQLException ClassNotFoundException { Class.forName("com.mysql.cj.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/testDb"; String username = "root"; String password = "12345678"; Connection connection = DriverManager.getConnection(url username password); String sql = "select * from t_user where username = ?"; PreparedStatement ps = connection.prepareStatement(sql); // 将原本会导致SQL注入的值通过索引传到占位符 ps.setString(1 "' or '1' = '1"); ResultSet resultSet = ps.executeQuery(); while (resultSet.next()) { System.out.println("用户名:" resultSet.getString("username")); } resultSet.close(); ps.close(); connection.close(); }

上面这段代码执行后不会有任何输出

jdbc数据库入门教学(JDBC操作数据库实操)(4)

PreparedStatement很好,但是有一种情况是无法实现的,比如要求order by字段实现动态的升降序,asc和desc不参与编译就没有办法使用,这时候只能用Statement来主观实现用户的SQL注入

对比总结两种操作对象
  • Statement存在SQL注入问题,PreparedStatement解决了注入问题
  • Statement是执行一次编译一次,而PreparedStatement是编译一次,执行多次,效率更高
  • PreparedStatement拥有静态编译检查,比如占位符要求传入String,就不能传入其他数据类型,编译器会报错,安全性更高
  • 能使用PreparedStatement尽量使用,只有当需要主观行为上的SQL注入时再使用Statement
JDBC中的事务

在JDBC中,事务行为默认自动提交,只要执行任意的DML语句,就会自动提交一次。这种行为肯定不符合实际业务开发, 一个业务中可能包含多条DML语句,必须满足同时成功或者同时失败

可以通过Connection对象的setAutoCommit(false)方法,传入false来关闭自动提交事务,在业务程序中,正常逻辑下使用Connection.commit()方法进行事务提交,用try catch代码块捕获异常后使用Connection.rollback()方法进行事务回滚

事务演示(DQL用executeQuery,DML使用executeUpdate,包含增删改)

// 其他代码省略 Connection connection = DriverManager.getConnection(url username password); connection.setAutoCommit(false); String sql = "delete from t_user where id = 3;"; PreparedStatement ps = connection.prepareStatement(sql); int i = ps.executeUpdate(); if (i == 1) { System.out.println("删除成功"); } ps.close(); connection.close();

尝试删除id为3的用户,代码执行后,控制台会输出删除成功

jdbc数据库入门教学(JDBC操作数据库实操)(5)

但是去查看数据库并没有数据删除,说明JDBC的自动提交事务被成功关闭了

jdbc数据库入门教学(JDBC操作数据库实操)(6)

正确的处理场景,在所有预期事务执行完毕后进行事务提交,catch异常后进行事务的回滚

// 其他代码省略 Connection connection = DriverManager.getConnection(url username password); connection.setAutoCommit(false); PreparedStatement ps = null; try { String sql = "delete from t_user where id = 3;"; ps = connection.prepareStatement(sql); int i = ps.executeUpdate(); if (i == 1) { System.out.println("删除成功"); } connection.commit(); } catch (Exception e) { connection.rollback(); } if (Objects.nonNull(ps)) { ps.close(); } connection.close();

在真实的开发场景中,并不会使用原生的JDBC去实现业务处理,主流的有MyBatis和JPA等ORM框架来帮助快速开发,但是底层都是基于JDBC的封装,只有熟悉JDBC才能更加清晰的掌握框架的使用

jdbc数据库入门教学(JDBC操作数据库实操)(7)


链接:https://juejin.cn/post/7099066449456529438

猜您喜欢: