JDBC-Java与数据库之间的桥梁

news/2024/11/8 19:44:22/

1、JDBC


1.1、数据的持久化

持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。大多数情况下,特别是企业级应用, 数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成

持久化的主要应用是将内存中的数据存储在关系型数据库中,当然也可以存储在磁盘文件、XML数据文件中

1.2、 Java中的数据操作技术

在Java中,数据库存取数据的技术可分为如下几类:

  • JDBC直接访问数据库
  • 第三方O/RM框架,如Hibernate,Mybatis,Spring Data JPA等

JDBC是Java访问数据库的基石,Hibernate、MyBatis、Spring Data JPA等只是更好的封装了JDBC

1.3、JDBC介绍

  • 全称:(Java DataBase Connectivity)Java数据库连接

  • JDBC就是使用Java语言操作关系型数据库的一套API

JDBC本质

  • 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口
  • 各个数据库厂商去实现这套接口,提供数据库驱动jar包
  • 面向接口编程,真正执行的代码是驱动jar包中的实现类

同一套Java代码,操作不同的关系型数据库

在这里插入图片描述

JDBC的好处

  • 各数据库厂商使用相同的接口,Java代码不需要针对不同数据库分别开发
  • 可随时替换底层数据库,访问数据库的Java代码基本不变

1.4、JDBC程序编写步骤

  • 导入数据库驱动包mysql-connector-java-8.0.28.jar
  • 加载驱动
  • 获取连接
  • 定义SQL
  • 执行SQL语句
  • 处理返回结果
  • 释放资源
public static void main(String[] args) throws ClassNotFoundException, SQLException {// 1.注册驱动Class.forName("com.mysql.cj.jdbc.Driver");// 2.获取数据库连接对象String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";String username = "root";String password = "root";Connection conn = DriverManager.getConnection(url, username, password);// 3.定义sqlString sql = "insert front_db.users values (1005,'小黑','1314',19,1)";// 4.获取执行sql的对象  StatementStatement stmt = conn.createStatement();// 5.执行sqlint affectRow = stmt.executeUpdate(sql);// 6.处理结果System.out.println("affectRow = " + affectRow);// 7.释放资源if (stmt != null) {stmt.close();}if (conn != null) {conn.close();}
}

连接四要素

  • 加载与注册JDBC驱动
  • URL
  • 用户名
  • 密码

编写jdbc.properties配置文件

# 驱动包路径
driver=com.mysql.cj.jdbc.Driver
# 协议://主机地址:端口号/数据库名?参数1&参数2&参数3&参数n
url=jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
# 用户名
username=root
# 密码
password=root

获取连接

使用配置文件的方式保存配置信息,在代码中加载配置文件

public static void main(String[] args) throws Exception {// 获取连接Connection conn = getConnection();// 获取执行SQL对象Statement stmt = conn.createStatement();// 定义SQLString sql = "select id, username, password, age, status from  front_db.users";// 执行SQLResultSet rs = stmt.executeQuery(sql);List<User> userList = new ArrayList<>();// 处理结果while (rs.next()) {int id = rs.getInt("id");String username = rs.getString("username");String password = rs.getString("password");int age = rs.getInt("age");int status = rs.getInt("status");userList.add(new User(id, username, password, age, status));}System.out.println(userList);if (rs != null) rs.close();if (stmt != null) stmt.close();if (conn != null) conn.close();
}public static Connection getConnection() throws Exception {// 1.读取配置文件中4个连接数据库的基本信息InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");Properties prop = new Properties();prop.load(is);String driver = prop.getProperty("driver");String url = prop.getProperty("url");String username = prop.getProperty("username");String password = prop.getProperty("password");System.out.println(driver + "\n" + url + "\n" + username + "\n" + password);// 2.加载驱动Class.forName(driver);// 3.获取连接return DriverManager.getConnection(url, username, password);
}

使用配置文件的好处:

①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,无需深入代码

②如果修改了配置信息,省去重新编译的过程

1.5、JDBC API作用

  • DriverManager(驱动管理类):注册驱动,获取数据库连接
  • Connection(数据库连接对象)
    • 获取执行 SQL 的对象
      • 普通执行SQL对象:createStatement()
      • 预编译SQL的执行SQL对象:prepareStatement(sql)
      • 执行存储过程的对象:prepareCall(sql)
    • 管理事务
      • 开启事务:setAutoCommit(boolean autoCommit);true为自动提交事务,false为手动提交事务(即开启事务)
      • 提交事务:commit()
      • 回滚事务:rollback()
  • Statement:用来执行SQL语句
    • 执行DDL、DML语句:excuteUpdate
    • 执行DQL语句:executeQuery
  • ResultSet(结果集对象):封装了SQL查询语句的结果
    • next():将光标从当前位置向后移动一行,判断当前行是否有数据
    • getXxx(参数):获取数据
  • PreparedStatement:预编译SQL语句并执行(性能更好),预防SQL注入问题(将敏感字符进行转义)

1.6、SQL注入

SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达到执行代码对服务器进行攻击的方法

使用Statement操作数据库表存在弊端:

  • 问题一:存在拼串操作,较为繁琐
  • 问题二:存在SQL注入问题
public static void main(String[] args) throws Exception {Scanner input = new Scanner(System.in);Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";String username = "root";String password = "root";Connection conn = DriverManager.getConnection(url, username, password);System.out.print("请输入用户名:");String uName = input.nextLine();System.out.print("请输入密码:");String uPwd = input.nextLine();     // ' or '1' = '1String sql = "select * from front_db.users where username = '" + uName + "' and password = '" + uPwd + "'";Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql);if (rs.next()) {System.out.println("登录成功");} else {System.out.println("登录失败");}if (rs != null) rs.close();if (stmt != null) stmt.close();if (conn != null) conn.close();
}

对于 Java 而言,要防止 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了

public static void main(String[] args) throws Exception {Scanner input = new Scanner(System.in);// MySQL5之后的驱动包,可以省略注册驱动的步骤// Class.forName("com.mysql.cj.jdbc.Driver");// useServerPrepStmts=true 参数开启预编译功能String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useServerPrepStmts=true";String username = "root";String password = "root";Connection conn = DriverManager.getConnection(url, username, password);// 预编译SQLString sql = "select * from front_db.users where username = ? and password = ?";PreparedStatement ps = conn.prepareStatement(sql);System.out.print("请输入用户名:");String uName = input.nextLine();System.out.print("请输入密码:");String uPwd = input.nextLine();		// ' or '1' = '1// 填充占位符ps.setString(1, uName);ps.setString(2, uPwd);ResultSet rs = ps.executeQuery();if (rs.next()) {System.out.println("登录成功");} else {System.out.println("登录失败");}if (rs != null) rs.close();if (ps != null) ps.close();if (conn != null) conn.close();
}

使用PreparedStatement实现CRUD操作

可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象,PreparedStatement接口是Statement的子接口,它表示一条预编译过的SQL语句

PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用PreparedStatement 对象的setXxx() 方法来设置这些参数,setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从1开始),第二个是设置的 SQL语句中参数的值

Query

public static void main(String[] args) throws Exception {Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";String username = "root";String password = "root";Connection conn = DriverManager.getConnection(url, username, password);String sql = "select * from front_db.users";PreparedStatement ps = conn.prepareStatement(sql);ResultSet rs = ps.executeQuery();List<User> userList = new ArrayList<>();while (rs.next()) {int id = rs.getInt("id");String uName = rs.getString("username");String uPwd = rs.getString("password");int age = rs.getInt("age");int status = rs.getInt("status");User user = new User(id, uName, uPwd, age, status);userList.add(user);}System.out.println(userList);if (rs != null) rs.close();if (ps != null) ps.close();if (conn != null) conn.close();
}

Insert、Update、Delete 只需修改对应的SQL语句,以及填充对应占位符

public static void main(String[] args) throws Exception {Class.forName("com.mysql.cj.jdbc.Driver");String url = "jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai";String username = "root";String password = "root";Connection conn = DriverManager.getConnection(url, username, password);// 预编译SQLString sql = "update front_db.users set username = ? ,password = ?,age = ?,status = ? where id = ?";PreparedStatement ps = conn.prepareStatement(sql);// 填充占位符ps.setString(1, "小萌");ps.setString(2, "meng@.");ps.setInt(3, 18);ps.setInt(4, 1);ps.setInt(5, 1001);int affectRow = ps.executeUpdate();if (affectRow == 0) System.out.println("修改失败");if (ps != null) ps.close();if (conn != null) conn.close();
}

1.7、使用IDEA连接数据库

连接成功后,选择数据库

双击数据库,查看相应的表

修改表中数据

打开编写代码控制台

连接失败,检查原因

1.8、数据库连接池

数据库连接—>执行完毕—>释放

连接、释放十分浪费系统资源

  • 数据库连接池是个容器,负责分配、管理数据库连接 (Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 超过最大空闲时间的数据库连接会自动归还到连接池中,来避免因为没有释放数据库连接而引起的数据库连接遗漏

好处

  • 资源重用
  • 提升系统响应速度
  • 避免数据库连接遗漏

池化技术:准备一些预先的资源,过来就连接预先准备好的

标准接口:DataSource

  • 官方(SUN) 提供的数据库连接池标准接口,由第三方组织实现此接口。该接口提供了获取连接的功能
  • 那么以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对象

常见的数据库连接池

  • C3P0
  • DBCP
  • Druid

C3P0

C3P0是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用

需要用到的jar包:c3p0-0.9.5.5.jar

  1. 编写c3p0-config.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <c3p0-config><named-config name="helloC3P0"><!--提供获取连接的四个基本信息--><property name="driverClass">com.mysql.cj.jdbc.Driver</property><!--注:在xml中 & 符号 需要使用 &amp;特殊转义--><property name="jdbcUrl">jdbc:mysql://localhost:3306/front_db?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai</property><property name="user">root</property><property name="password">root</property><!--进行数据库连接池管理的基本信息--><!--当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数--><property name="acquireIncrement">5</property><!--c3p0数据库连接池中初始化时的连接数--><property name="initialPoolSize">10</property><!--c3p0数据库连接池维护的最少连接数--><property name="minPoolSize">10</property><!--c3p0数据连接池维护的最多连接数--><property name="maxPoolSize">100</property><!--c3p0数据连接池最多维护的Statement的个数--><property name="maxStatements">50</property><!--每个连接中可以最多使用的Statement的个数--><property name="maxStatementsPerConnection">2</property></named-config>
    </c3p0-config>
    
  2. 获取连接

    @Test
    public void testC3p0Connection() throws SQLException {ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0");Connection conn = cpds.getConnection();System.out.println(conn);
    }
    

DBCP

DBCP是Apache软件基金组织下的开源连接池实现,tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持

需要用到的jar包

  • commons-dbcp-1.4.jar:连接池的实现
  • commons-pool-1.6.jar:连接池实现的依赖库
  1. 编写dbcp.properties配置文件

    # 连接四要素
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username=root
    password=root# 其他设置
    initialSize=10
    maxActive=20
    # ...
    
  2. 获取连接

    @Test
    public void testDBCPConnection() throws Exception {Properties pros = new Properties();InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");pros.load(is);DataSource ds = BasicDataSourceFactory.createDataSource(pros);Connection conn = ds.getConnection();System.out.println(conn);
    }
    

Druid(德鲁伊)

Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,且功能强大,性能优秀,是Java语言最好的数据库连接池之一

需要用到的jar包:druid-1.2.8.jar

  1. 编写druid.properties配置文件

    # 连接四要素
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/front_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username=root
    password=root# 其他设置
    # 初始化连接数量
    initialSize=10
    # 最大连接数
    maxActive=15
    # 最大等待时长
    maxWait=3000
    
  2. 获取连接

    @Test
    public void testDruidConnection() throws Exception {Properties prop = new Properties();InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");prop.load(is);DataSource ds = DruidDataSourceFactory.createDataSource(prop);Connection conn = ds.getConnection();System.out.println(conn);
    }
    

结论:无论使用什么数据源,本质还是一样的,DataSource接口不会变,方法就不会变!

1.9、DAO模式

为什么进行JDBC封装?

业务代码和数据访问代码耦合

  • 可读性差
  • 不利于后期修改和维护
  • 不利于代码复用

什么是DAO?

非常流行的数据访问模式——DAO模式

  • Data Access Object(数据访问对象)
  • 位于业务逻辑层和持久化数据层之间
  • 实现对持久化数据的访问

DAO模式的组成部分

  1. 实体类
  2. DAO接口
  3. DAO实现类
  4. 数据库连接和关闭工具类

DAO的优势

  • 隔离业务逻辑代码和数据访问代码
  • 隔离不同数据库的实现

为解决业务代码和数据访问代码的紧耦合,给修改和维护代码带来的不便,推荐使用DAO模式封装JDBC

// DAO:Data Access Object(数据访问对象)
public abstract class BaseDao<T> {private Class<T> clazz;{// 获取当前BaseDao的子类继承父类中的泛型Type genericSuperclass = this.getClass().getGenericSuperclass();ParameterizedType paramType = (ParameterizedType) genericSuperclass;Type[] typeArguments = paramType.getActualTypeArguments();      // 获取父类的泛型参数clazz = (Class<T>) typeArguments[0];   // 泛型的第一个参数}/*** 获取连接*/public Connection getConnection() {DataSource dataSource = null;try {// 1.读取配置文件中连接数据库的基本信息InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");Properties prop = new Properties();prop.load(is);// 获取数据库连接池dataSource = DruidDataSourceFactory.createDataSource(prop);return dataSource.getConnection();} catch (Exception e) {throw new RuntimeException(e);}}/*** 关闭资源 查询*/public void closeResource(Connection conn, PreparedStatement ps, ResultSet rs) {try {if (rs != null) rs.close();if (ps != null) ps.close();if (conn != null) conn.close();} catch (SQLException e) {e.printStackTrace();}}/*** 关闭资源  增、删、改** @param conn 连接对象* @param ps   执行SQL语句对象*/public void closeResource(Connection conn, PreparedStatement ps) {try {if (ps != null) ps.close();if (conn != null) conn.close();} catch (SQLException e) {e.printStackTrace();}}/*** 通用增、删、改  version 2.0(考虑到事务)** @param sql* @param args* @return*/public int modifyTbTx(Connection conn, String sql, Object... args) {PreparedStatement ps = null;try {// 1.预编译sql 返回prepareStatement对象ps = conn.prepareStatement(sql);// 2.填充占位符for (int i = 0; i < args.length; i++) {// 从1开始ps.setObject(i + 1, args[i]);}// 3.执行操作return ps.executeUpdate();} catch (Exception e) {e.printStackTrace();} finally {// 4.关闭资源closeResource(null, ps);}return 0;}/*** 通用的查询操作,返回数据表的一条记录 version 2.0(考虑到事务)** @param conn 连接对象* @param sql  sql语句* @param args 执行sql所需的填充参数* @return 单条记录*/public T getDataOneWithTx(Connection conn, String sql, Object... args) {PreparedStatement ps = null;ResultSet rs = null;try {// 预编译sqlps = conn.prepareStatement(sql);// 填充占位符for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}// 执行操作rs = ps.executeQuery();// 获取结果集元数据ResultSetMetaData rsmd = rs.getMetaData();// 获取列数int columnCount = rsmd.getColumnCount();if (rs.next()) {T t = clazz.newInstance();for (int i = 0; i < columnCount; i++) {// 获取每一个列的列值:通过ResultSetObject columnValue = rs.getObject(i + 1);// 获取每一个列的列名:通过ResultSetMetaData// getColumnLabel:获取列的别名 推荐使用String columnLabel = rsmd.getColumnLabel(i + 1);// 通过反射,将对象的指定columnLabel属性名的属性赋值为指定值columnValueField field = clazz.getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, columnValue);}return t;}} catch (Exception e) {e.printStackTrace();} finally {closeResource(null, ps, rs);}return null;}/*** 通用查询方法 返回多条记录的查询操作  version 2.0(考虑到事务)** @param sql  sql语句* @param args 执行sql所需的填充参数* @return 多条记录*/public List<T> getDataListWithTx(Connection conn, String sql, Object... args) {PreparedStatement ps = null;ResultSet rs = null;try {ps = conn.prepareStatement(sql);for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}rs = ps.executeQuery();ResultSetMetaData rsmd = rs.getMetaData();int columnCount = rsmd.getColumnCount();// 创建集合对象ArrayList<T> list = new ArrayList<>();while (rs.next()) {T t = clazz.newInstance();for (int i = 0; i < columnCount; i++) {Object columnValue = rs.getObject(i + 1);String columnLabel = rsmd.getColumnLabel(i + 1);// 通过反射,将对象的指定columnLabel属性名的属性赋值为指定值columnValueField field = clazz.getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, columnValue);}list.add(t);}return list;} catch (Exception e) {e.printStackTrace();} finally {closeResource(null, ps, rs);}return null;}/*** 用于查询特殊值的通用方法** @param conn 连接对象* @param sql  sql语句* @param args 执行sql所需的填充参数*/public <E> E getValue(Connection conn, String sql, Object... args) {PreparedStatement ps = null;ResultSet rs = null;try {ps = conn.prepareStatement(sql);for (int i = 0; i < args.length; i++) {ps.setObject(i + 1, args[i]);}rs = ps.executeQuery();if (rs.next()) {return (E) rs.getObject(1);}} catch (SQLException e) {e.printStackTrace();} finally {closeResource(null, ps, rs);}return null;}
}

http://www.ppmy.cn/news/87516.html

相关文章

Lucene(5):索引维护

1 需求 管理人员通过电商系统更改图书信息&#xff0c;这时更新的是关系数据库&#xff0c;如果使用lucene搜索图书信息&#xff0c;需要在数据库表book信息变化时及时更新lucene索引库。 2 添加索引 调用 indexWriter.addDocument&#xff08;doc&#xff09;添加索引。 参…

分布式调度XXL-JOB

分布式调度XXL-JOB 1.概述 1.1什么是任务调度 比如: 某电商平台需要每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券某银行系统需要在信用卡到期还款日的前三天进行短信提醒某财务系统需要在每天凌晨0:10分结算前一天的财务数据&#xff0c;统计汇总 以…

基于差分进化算法的微电网调度研究(Matlab代码实现)​

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

JVM笔记

Java中对象一定分配在堆空间上吗&#xff1f;判断一个对象是否还活着GCgc频繁 Java中对象一定分配在堆空间上吗&#xff1f; 逃逸分析&#xff1a;分析对象动态作用域&#xff0c;当一个对象在方法中被定义后&#xff0c;它可能被外部方法所引用&#xff0c;例如作为调用参数传…

香港VPS服务器如何屏蔽指定访客ip?

​  如果你是一个香港VPS服务器的管理员&#xff0c;你可能会遇到一些不良用户或者恶意攻击者&#xff0c;这些人会尝试通过不断的访问和攻击你的网站来破坏你的网站的运行。如何保护你的网站&#xff0c;你需要使用一些方法来屏蔽这些指定的访客IP。 首先&#xff0c;你需要…

当你在浏览器中输入 URL 时会发生什么?

下面的图解说明了步骤。 1.Bob 在浏览器中输入 URL 并按下 Enter。在这个例子中&#xff0c;URL 由 4 部分组成&#xff1a; &#x1f539; 协议 - http://. 这告诉浏览器使用 HTTP 发送连接到服务器。 &#x1f539; 域名 - example.com. 这是站点的域名。 &#x1f539; 路径…

通过计算属性来更改几个数据,针对于这几个数据有失焦事件,但是先触发了失焦事件,后获得到了计算属性怎么办,我想先获取到计算属性的值再进行失焦事件

计算属性是惰性的,只有依赖的数据发生变化时才会重新计算。这可能会导致失焦事件先触发,获取到的计算属性值还未更新的情况。要解决这个问题,有两种方法:1. 在失焦事件中加入 setTimeout,等计算属性重新计算后再获取其值。 js computed: {fullName() { /* ... */ } }, metho…

C++入门预备语法

C入门预备语法 C关键字命名空间C输入&输出初步缺省参数函数重载引用内联函数auto和范围for&#xff08;C11&#xff09;指针空值nullptr C关键字 命名空间 命名空间是一种将变量名、函数名、类名和库名称等封装到一个命名空间域中&#xff0c;与其他域的同名量相隔离&…