本文共 18635 字,大约阅读时间需要 62 分钟。
一、JDBC概述
1.1 简介
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
JDBC可以在各种平台上使用Java,如Windows,Mac OS和各种版本的UNIX。
JDBC库包括与数据库使用相关的API。
- 连接数据库。
- 创建SQL或MySQL语句。
- 在数据库中执行SQL或MySQL查询。
- 查看和修改生成的记录。
1.2 JDBC体系结构
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成(Java程序中包含了API):
- JDBC:提供了应用程序到数据库连接规范,接口,需要实现类。
- JDBC驱动程序: 连接数据库的驱动程序的实现,对应不同的数据库公司的驱动程序,接口实现类。
JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。
1.3 JDBC核心组件
DriverManager: 此类管理数据库驱动程序列表。使用通信协议将来自java应用程序的连接请求与适当的数据库驱动程序匹配。
Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象。
Connection:该接口具有用于连接数据库的所有方法。连接对象表示通信上下文,数据库的所有通信仅通过连接对象。
Statement:使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数【常用的是更安全的PreparedStatement】
ResultSet:在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。
SQLException:此类处理数据库应用程序中发生的任何异常。
二、JDBC相关的SQL语法
2.1 使用步骤
构建JDBC应用程序涉及以下六个步骤:
- 导入JDBC驱动包:需要下载包含数据库编程所需的JDBC的jar包。
- 注册JDBC驱动程序:要求您初始化驱动程序,以便您可以打开与数据库的通信通道。
- 创建连接:需要使用 DriverManager.getConnection() 方法创建一个Connection对象,该对象表示与数据库的物理连接。
- 执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
- 从结果集中提取数据:需要使用相应的ResultSet.getXXX() 方法从结果集中检索数据。
- 释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
2.2 标准格式代码
public class Deo1 { public static void main(String[] args) throws ClassNotFoundException, SQLException { /*1.导入jar包 * 2.项目下新建lib文件夹 * 3.将jar包导入 mysql-connector-java-5.1.48-bin.jar * 4.右键jar包 Add As Library * */ /*2.注册驱动,现在可以不用写,自动注册*/ Class.forName("com.mysql.jdbc.Driver"); /*3.获取连接对象*/ //jdbc:mysql: 协议 //localhost IP地址 //3306 端口号 //useSSL=true&charatcerEncoding=utf8 使用安全协议连接并规定编码 String url = "jdbc:mysql://localhost:3306/homework3?useSSL=true&charatcerEncoding=utf8"; Connection conn = DriverManager.getConnection(url, "root", "123456"); if (null != conn) { System.out.println("连接成功"); } //4.释放资源,【建议在finally中使用】 conn.close(); }}
实际上,Class.forName()并不是注册驱动,而是通过反射调用了Driver类,通过静态代码块,类加载的时候就执行注册。如下为Driver源码,很明显通过静态代码块注册驱动。
//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() 方法直接进行注册,原因:
- 驱动程序注册两次
- java程序依赖mysql驱动包
2.3 关于其他链接方式,以及其他数据库链接字符串格式
加载驱动程序后,可以使用DriverManager.getConnection() 方法建立连接。为了方便参考,让我列出三个重载的DriverManager.getConnection()方法 :
- getConnection(String url)
- getConnection(String url,Properties prop)
- getConnection(String url,String user,String password)
RDBMS | JDBC驱动程序名称 | 连接字符串格式 |
---|---|---|
MySQL | com.mysql.jdbc.Driver | jdbc:mysql:// hostname:3306 / databaseName |
ORACLE | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@ hostname:port Number:databaseName |
DB2 | COM.ibm.db2.jdbc.net.DB2Driver | jdbc:db2: hostname:port Number / databaseName |
SYBASE | com.sybase.jdbc.SybDriver | jdbc:sybase:Tds: hostname:port Number / databaseName |
2.4 JDBC执行SQL语句
一旦获得了连接,我们可以与数据库进行交互。JDBC Statement和PreparedStatement接口定义了使您能够发送SQL命令并从数据库接收数据的方法和属性。
接口 | 使用 |
---|---|
Statement | 用于对数据库进行通用访问。在运行时使用静态SQL语句时很有用。Statement接口不能接受参数。 |
PreparedStatement(推荐使用) | 当您计划多次使用SQL语句时使用。PreparedStatement接口在运行时接受输入参数。 |
注意:在java中使用sql语句,不能忘记标点符号,如【;】【,】等
SQL语句注入导致的安全问题
就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
所以这里建议使用【PreparedStatement】,它会要求使用占位符,导致不能注入,从而保证安全性。三个执行方法
创建Statement对象后,您可以使用它来执行一个SQL语句,其中有三个执行方法:
- boolean execute(String SQL):如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQL DDL语句或需要使用真正的动态SQL时。
- int executeUpdate(String SQL):返回受SQL语句执行影响的行数。使用此方法执行预期会影响多个行的SQL语句,例如INSERT,UPDATE或DELETE语句,以及DDL语句。
- ResultSet executeQuery(String SQL):返回一个ResultSet对象。当您希望获得结果集时,请使用此方法,就像使用SELECT语句一样,需要while循环进行遍历。
以上方法执行的时候都会有返回值,该值是SQL语句中的影响行数,比如,创建数据库成功,影响了一行语句;创建表成功,影响0行语句;对表数据增删改查,影响的是多行数据,返回就是大于0。
案例一:使用statement
public class Demo2 { public static void main(String[] args) throws ClassNotFoundException, SQLException { //1.导入jar包 //2.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //3.建立连接 String url = "jdbc:mysql://localhost:3306/Demo2" + "?useSSL=true&characterEncoding=utf8"; Connection conn = DriverManager.getConnection(url, "root", "123456"); /*4.创建命令*/ Statement statement = conn.createStatement(); /*5.执行命令*/ //5.1创建表 statement.executeUpdate("" + "create table stu(" + "id int primary key ," + "name varchar(20), " + "address varchar (20))"); //5.2插入数据 statement.executeUpdate("insert into stu(id,name,address) value " + "(1,'张三','北京')," + "(2,'李四','上海')," + "(3,'王五','广州')," + "(4,'赵六','深圳');"); //5.3修改数据 statement.executeUpdate("update stu set address='北京昌平' where id=1"); //先关掉命令 statement.close(); conn.close(); System.out.println("执行完毕"); }}
案例二:ResultSet executeQuery,结果集的正序和逆序遍历
public class Demo3 { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/Demo2?useSSL=true&characterEncoding=utf8"; Connection conn = DriverManager.getConnection(url, "root", "123456"); PreparedStatement pstatement = conn.prepareStatement("select * from stu"); ResultSet resultSet = pstatement.executeQuery(); /*处理数据ResultSet*/ //没有数据返回false,指针按行移动 while (resultSet.next()) { int id = resultSet.getInt("id");//直接写出列标签 String name = resultSet.getString("name"); String address = resultSet.getString("address"); System.out.println(id + " 姓名:" + name + " 地址:" + address); } System.out.println("====先顺序,后逆序============"); while (resultSet.previous()) { int id = resultSet.getInt("id");//直接写出列标签 String name = resultSet.getString("name"); String address = resultSet.getString("address"); System.out.println(id + " 姓名:" + name + " 地址:" + address); } //关闭结果集 resultSet.close(); pstatement.close(); conn.close(); }}
案例三:登录验证案例(不安全)
public class Demo4 { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/Demo2?useSSL=true&characterEncoding=utf8"; Connection conn = DriverManager.getConnection(url, "root", "123456"); Statement statement = conn.createStatement(); Scanner input = new Scanner(System.in); System.out.println("请输入用户名"); String username = input.nextLine(); System.out.println("请输入密码"); String password = input.nextLine(); //先用sql语句写完整,然后将具体值删掉,用【"+ +"】将变量包住,去判断 ResultSet resultSet = statement.executeQuery("select * from user where username='" + username + "' and password='" + password + "'"); if (resultSet.next()) { System.out.println("登录成功"); } else { System.out.println("登录失败"); } }}//打印请输入用户名 | 请输入用户名张三 | 张三请输入密码 | 请输入密码9999 | 3登录成功 | 登录失败
用户名是【张三】,密码是9999。如果输入错误,自然登录失败。如果使用【张三’ or 1=1#】作为用户名输入,则打印结果如下。
请输入用户名张三' or 1=1#请输入密码2315登录成功
密码即使不同,也能登录,这就是SQL注入导致的问题。
代码中的executeQuery括号中的SQL语句中的被【"+ username+"】包裹的username,在SQL执行的时候会将其替换为【张三’ or 1=1#】,结果如下:SELECT * FROM USER WHERE username='张三' OR 1=1#' and password='" + password + "'
后面的密码部分被注释掉了,自然登录成功,所有,需要在声明方法的时候用占位符处理。
案例四:登录验证案例(安全)
public class Demo5 { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/Demo2?useSSL=true&characterEncoding=utf8"; Connection conn = DriverManager.getConnection(url, "root", "123456"); //?是占位符,避免sql注入,后面会赋值比对 PreparedStatement statement = conn.prepareStatement("select * from user where username=? and password=?"); Scanner input = new Scanner(System.in); System.out.println("请输入用户名"); String username = input.nextLine(); System.out.println("请输入密码"); String password = input.nextLine(); //参数赋值set后面是对应列的数据类型,parameterIndex是索引,对应前面占位符,1是username 2是password //第二个参数是前面Scanner输入的参数,进行比对 statement.setString(1, username); //如果不知道数据类型,可以使用object,任意类型 statement.setObject(2, password); //必须要有执行 ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { System.out.println("登录成功"); } else { System.out.println("登录失败"); } resultSet.close(); statement.close(); input.close(); conn.close(); }}
注意:preparedStatement使用的时候,只能处理一条SQL语句,当使用set和get方法处理完成一条【完整的SQL语句】之后,必须调用excuteQuery、excuteUpdate、addBantch等方法,完成该条语句,然后才能继续使用set和get方法进行下一条语句。
三、抽取数据库工具类
以上操作都有很多的重复性,比如:
- 注册驱动
- 获取连接
- 释放资源
- 执行命令
这些完全可以封装为一个类,这里我们称之为“DBUtils”数据库工具类,是一种静态方法,将【与数据库交互的重复而复杂的逻辑】封装。
案例:通过DBUtils实现增删改
//db.properties文件内容#驱动,不要写分号,不要写双引号,不要空格driver=com.mysql.jdbc.Driver#后面可以加入【&rewriteBatchedStatements=true】,批量处理数据的时候,可以将多个insert语句作为一条语句添加入数据库#但只能用于preparedStatementurl=jdbc:mysql://localhost:3306/demo2?useSSL=true&characterEncoding=utf8&rewriteBatchedStatements=trueuser=rootpassword=123456//domain包下的User类public class User { private Integer id; //数据库中的数据可能为null,这里使用包装类 private String username; private String password; private String email; private String phone; public User(Integer id, String username, String password, String email, String phone) { this.id = id; this.username = username; this.password = password; this.email = email; this.phone = phone; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", email='" + email + '\'' + ", phone='" + phone + '\'' + '}'; }}//utils包下的DBUtil工具类public class DBUtils { /*四个功能 * (1)注册驱动(一次) * (2)获取连接 * (3)释放资源 * (4)执行SQL (增删改) * */ //工具类,必须都是静态方法和变量,必须final修饰 private static final String DRIVER; private static final String URL; private static final String USER; private static final String PASSWORD; //静态代码块,仅注册链接执行一次 static { //获取相应的JDBC驱动属性 /* 第一种方式读取properties文件,读取的是源代码src中的,但是实际部署的是out中的,最后使用第二种方式读取 Properties properties=new Properties(); FileInputStream fis=new FileInputStream("src\\db.properties"); properties.load(fis); fis.close();*/ /*第二种方式:使用类加载器读取,加载配置文件*/ //读取文件1 Properties properties = new Properties(); InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("db.properties"); try { properties.load(is); is.close(); } catch (Exception e) { e.printStackTrace(); } //由于try中代码块不一定执行,而final修饰的变量必须初始化或者通过static代码块初始化,所以不能放到try中 DRIVER = properties.getProperty("driver"); URL = properties.getProperty("url"); USER = properties.getProperty("user"); PASSWORD = properties.getProperty("password"); try { Class.forName(DRIVER); } catch (Exception e) { System.out.println("驱动注册失败"); } } /*获取连接方法,可以对外提供该方法*/ public static Connection getConnection() { try { return DriverManager.getConnection(URL, USER, PASSWORD); } catch (SQLException e) { e.printStackTrace(); System.out.println("获取链接失败"); } return null; } /*关闭所有资源方法*/ private static void closeAll(Connection conn, Statement stat, ResultSet rs) { try { if (rs != null) { rs.close(); } if (stat != null) { stat.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } /*更新方法*/ public static int executeUpdate(String sql, Object...params) { Connection connection = getConnection(); PreparedStatement pstat = null; try { pstat = connection.prepareStatement(sql); //设置参数 if (params != null) { for (int i = 0; i < params.length; i++) { pstat.setObject(i + 1, params[i]); } } return pstat.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { //closeAll(connection, pstat, null); } return -1; } /*查询所有方法*/ public static ListexecuteQuery(String sql) { Connection connection = getConnection(); PreparedStatement statement = null; ResultSet resultSet = null; //集合不同于对象,必须先声明类型,然后才能操作 ArrayList list = new ArrayList<>(); try { statement = connection.prepareStatement(sql); resultSet = statement.executeQuery(); while (resultSet.next()) { int id = resultSet.getInt("id"); String username = resultSet.getString("username"); String password = resultSet.getString("password"); String email = resultSet.getString("email"); String phone = resultSet.getString("phone"); String data = id + ",用户名:" + username + ",密码:" + password + ",邮箱:" + email + ",电话:" + phone; list.add(new User(id, username, password, email, phone)); } } catch (SQLException e) { e.printStackTrace(); return null; } finally { closeAll(connection, statement, null); } return list; } /*通过id查找方法*/ public static User executeQueryById(String sql, Integer id) { Connection connection = getConnection(); PreparedStatement statement = null; ResultSet resultSet = null; //User必须赋null User user = null; try { statement = connection.prepareStatement(sql,id); resultSet = statement.executeQuery(); if (resultSet.next()) { String username = resultSet.getString("username"); String password = resultSet.getString("password"); String email = resultSet.getString("email"); String phone = resultSet.getString("phone"); String data = id + ",用户名:" + username + ",密码:" + password + ",邮箱:" + email + ",电话:" + phone; user = new User(id, username, password, email, phone); } } catch (SQLException e) { e.printStackTrace(); return null; } finally { closeAll(connection, statement, resultSet); } return user; }}//dao包下的UserDao类,对外公开方法public class UserDao { //查找所有用户 public List findAll() throws SQLException { List users = DBUtils.executeQuery("select * from user"); return users; } //根据ID查找用户 public User findByUserId(Integer id) throws SQLException { User user = DBUtils.executeQueryById("select * from user", id); return user; } //添加用户 public void add(User user) { Object[] params = { user.getId(),user.getUsername(),user.getPassword(),user.getEmail(),user.getPhone()}; int count = DBUtils.executeUpdate("insert into user values(?,?,?,?,?)",params); if (count > 0) { System.out.println("添加成功"); }else { System.out.println("添加失败"); } } //更新用户 public void update(User user) { Object[] params = { user.getUsername(),user.getPassword(),user.getEmail(),user.getPhone(),user.getId()}; int count = DBUtils.executeUpdate("update user set username=?,password=?,email=?,phone=? where id=?",params); if (count > 0) { System.out.println("修改成功"); }else { System.out.println("修改失败"); } } //通过id删除用户 public void delete(Integer id) { int count = DBUtils.executeUpdate("delete from user where id=?", id); if (count > 0) { System.out.println("删除成功"); } else { System.out.println("删除失败"); } }}//test包下的UserDaoTest测试类public class UserDaoTest { @Test public void testAdd() { UserDao userDao = new UserDao(); User user = new User(15, "哈哈", "12121", "12512362@163.com", "137707108084"); userDao.add(user); } @Test public void testUpdate() { UserDao userDao = new UserDao(); User user = new User(1, "张三", "45678", "166634@qq.com", "1987654321"); userDao.update(user); } @Test public void testDelete() { UserDao userDao = new UserDao(); userDao.delete(12); } @Test public void testFindAll() throws SQLException { UserDao userDao = new UserDao(); List list = userDao.findAll(); for (User user : list) { System.out.println(user); } } @Test public void testfindByUserId() throws SQLException { UserDao userDao = new UserDao(); User user = userDao.findByUserId(1); System.out.println(user); }}
转载地址:https://blog.csdn.net/qq_45337431/article/details/100084797 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!