Java使用传统JDBC操作数据库

什么是JDBC?

        JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。 使用Java程序访问数据库时,Java代码并不是直接通过直接建立TCP连接去访问数据库,而是通过JDBC接口来建立连接,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。在JDBC驱动实现类中,通过建立TCP连接来建立真正的网络通讯连接。具体的JDBC驱动是由数据库厂商提供的,他们都是实现Java提供的JDBC标准,来实现操作本公司旗下的数据库,例如Mysql数据库许需要 mysql-connector-java-版本号 的驱动包(jar包),有了驱动包之后,我们就能实现Java操作数据库了。

JDBC的相关接口(类)

加载驱动类:DriverManager驱动管理类使用该驱动类,进行连接的获取与创建JDBC驱动包中的Driver类的对象。
        不推荐:硬编码,应用程序与数据库驱动耦合
        com.mysql.cj.jdbc.Driver driver = new com.mysql.cj.jdbc.Driver();
        推荐 : 反射的方式创建Driver类的对象,高版本的JDBC驱动包,可以自动加载驱动类,该步骤可以省略
        Class.forName("com.mysql.cj.jdbc.Driver");

Connection 数据连接接口:获取并创建Connection接口的实现类对象(比如MySQL的ConnectionImpl实现类)

        参数1:数据库连接字符串(JDBC连接字符串):jdbc:数据库类型:数据库地址(服务器地址、端口号、数据库名称)?连接参数
         jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8
        参数2:数据库用户名
        参数3:数据库密码

 Statement(PreparedStatement)数据库操作接口:执行SQL语句

ResultSet 数据库结果集接口:执行select语句后,获取查询结果

JDBC查询(Query)操作

使用Statement:

步骤:

1.建立连接、设置数据库连接参数;

2.更新SQL语句;

3.创建Statement数据库操作对象,执行executeQuery(),执行后获取查询ResultSet结果集对象;

4.释放资源。

注意:由于Connection、Statement、ResultSet都是需要关闭的资源,所以这里使用try with resource,实现自动关闭。

package com.fulian.demo01;

import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

// 使用Statement执行查询
public class Test02 {
	public static void main(String[] args) {
		// 数据库连接参数
		final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
		final String DB_USER_NAME = "用户名";
		final String DB_USER_PASS = "密码";

		// 查询所有用户
		try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
			System.out.println("数据库连接成功!" + con);

			// 2.SQL
			int val1 = 1, val2 = 6;
			String sql = "SELECT * FROM user_info WHERE user_id >= " + val1 + " AND user_id <= " + val2;
			System.out.println("sql = " + sql);

			// 2.1 创建Statement数据库操作对象
			try (Statement st = con.createStatement();) {
				System.out.println(st);

				// 2.2 执行SQL(查询),执行后获取查询ResultSet结果集对象
				try (ResultSet rs = st.executeQuery(sql);) {
					System.out.println(rs);

					// 2.3
					// 获取某一行中若干字段值
					while (rs.next()) {
						// 如果rs.next()方法的返回值为true,代表存在下一行数据
						// rs则移动游标(指向标),只想下一行
						// rs.getXXX()方法,则开始读取游标只读取游标指向的行
						int userId = rs.getInt(1); // 获取当前行中的第1个字段值(user_id用户编号字段)
						String phoneNumber = rs.getString(2); // 获取当前行中的第2个字段值(login_phone_number登陆手机号码字段)
						String realName = rs.getString(3); // 获取当前行中的第3个字段值(user_real_name用户真实姓名字段)
						Date lastLogin = rs.getDate(4); // 获取当前行中的第4个字段值(last_login_date_time字段)

						System.out.println("用户编号:" + userId);
						System.out.println("手机号码:" + phoneNumber);
						System.out.println("真实姓名:" + realName);
						System.out.println("最后登录:" + lastLogin);
						System.out.println();
					}
				}
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

使用PreparedStatement

优点:1.完全避免SQL注入问题,保持数据库的安全性;

           2.PreparedStatement创建时,传入带有?占位符的SQL语句,保证每次传入的sql语句都是相同的,只是占位符的数据不同,执行时,基于MySQL预编译机制,提高数据库的执行效率。

步骤:

1.建立连接、设置数据库连接参数;

2.更新SQL语句;

3.创建PreparedStatement数据库操作对象,执行executeQuery(),执行后获取查询ResultSet结果集对象;

4.释放资源。

package com.fulian.demo01;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class Test04 {
	public static void main(String[] args) {
		// 数据库连接参数
		final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
		final String DB_USER_NAME = "用户名";
		final String DB_USER_PASS = "密码";

		// 用户登录
		try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
			System.out.println("数据库连接成功!" + con);

			// 2.SQL
			String phoneNumber = "12345678900";
			String loginPassword = "1794";
			String sql = "SELECT * FROM user_info WHERE login_phone_number = ? AND login_password = ?";
			System.out.println("sql = " + sql);

			// 2.1 创建PreparedStatement数据库操作对象
			// PreparedStatement创建时,传入带有?占位符的SQL语句,并执行
			// PreparedStatement执行时,基于MySQL预编译机制,提高数据库的执行效率
			try (PreparedStatement pst = con.prepareStatement(sql);) {
				// 执行前,处理?占位符
				pst.setString(1, phoneNumber);
				pst.setString(2, loginPassword);
				
				// 2.2 执行SQL(查询)
				try (ResultSet rs = pst.executeQuery();) {
					// 输出登录状态 
					if (rs.next()) {
						System.out.println("登录成功!");
					}else {
						System.out.println("登录失败!");
					}
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

JDBC修改(Update)操作

        在jdbc中,所有的增加、删除、修改都是update操作,步骤如下:    

        1.建立连接、设置数据库连接参数;

        2.更新SQL语句;

        3.创建PreparedStatement数据库操作对象,执行executeUpdate()方法,执行后获取修改行数;

        4.释放资源。

以增加数据为例,代码如下:

package com.fulian.demo01;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Test08 {
	public static void main(String[] args) {
		// 数据库连接参数
		final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
		final String DB_USER_NAME = "用户名";
		final String DB_USER_PASS = "密码";

		// 增加
		// 1.创建数据库连接
		try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
			// 2.SQL
			String sql = "INSERT INTO material_info(material_name,material_stock,material_unit)VALUES(?,?,?)";
			try(PreparedStatement pst = con.prepareStatement(sql);){
				pst.setString(1, "消毒棉片片");
				pst.setInt(2, 98);
				pst.setString(3, "包");
				
				int rows = pst.executeUpdate();
				System.out.println("影响行数:" + rows);
			}
			
		
			
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

那么,如果想要增加一条数据并返回新增加数据的主键呢

在创建PreparedStatement的时候,指定一个RETURN_GENERATED_KEYS标志位,表示JDBC驱动必须返回插入的自增主键。示例代码如下:

        try(PreparedStatement pst = con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);){
				pst.setString(1, "消毒玻璃水");
				pst.setInt(2, 532);
				pst.setString(3, "瓶");
				
				// 先执行添加操作
				int rows = pst.executeUpdate();
				System.out.println("影响行数:" + rows);
				
				// 在获取自动生成的主键值
				ResultSet rs = pst.getGeneratedKeys();
				if(rs.next()) {
					int newRecord = rs.getInt(1);
					System.out.println("新纪录的编号值:" + newRecord);
				}
			}

JDBC的事务与批处理

JDBC事务

        数据库事务(Transaction)是由若干个SQL语句构成的一个操作序列。数据库系统保证在一个事务中的所有SQL要么全部执行成功,要么全部不执行,即数据库事务具有ACID特性:  Atomicity:原子性 、Consistency:一致性、Isolation:隔离性、Durability:持久性。

        在JDBC中,有两种事务类型,一种是隐式事务,执行完sql语句自动提交事务,另一种是显式事务,需要在jdbc中手动关闭自动提交,每次执行完sql语句,必须得手动提交或回滚。

package com.fulian.demo02;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Test02 {
	public static void main(String[] args) {
		// 转出账户
		String sourceAccount = "张三丰";
		int sourceMoney = 5000;
		
		// 转入帐户
		String targetAccount = "李四狗";
		int targetMoney = 5000;
		
		// 数据库连接参数
		final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
		final String DB_USER_NAME = "用户名";
		final String DB_USER_PASS = "密码";

		// 数据库连接
		try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
			
			// 关闭自动提交事务
			con.setAutoCommit(false);
			
			// 转出
			String sql1 = "UPDATE bank SET currentMoney = currentMoney - ? WHERE customerName = ?";
			try (PreparedStatement pst1 = con.prepareStatement(sql1);){
				pst1.setInt(1, sourceMoney);
				pst1.setString(2, sourceAccount);
				
				int row1 = pst1.executeUpdate();
				System.out.println("转出操作影响行数:" + row1);
				
				// 转入
				String sql2 = "UPDATE bank SET currentMoney = currentMoney + ? WHERE customerName = ?";
				try (PreparedStatement pst2 = con.prepareStatement(sql2);){
					pst2.setInt(1, targetMoney);
					pst2.setString(2, targetAccount);
					
					int row2 = pst2.executeUpdate();
					System.out.println("转入操作影响行数:" + row2);
				} 
			} catch (Exception e) {
				e.printStackTrace();
				// 手动回滚事务
				con.rollback();
			}			
			
			// 手动提交事务
			con.commit();
			
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

JDBC批处理

        使用JDBC操作数据库的时候,经常会执行一些批量操作。SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch批处理执行,即批量执行,每次执行一条一句之后,addBath(),添加到内存中,等待全部加载完成后,执行executeBatch(),统一进行操作,以插入若干条数据为例,代码如下:

package com.fulian.demo02;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class Test03 {
	public static void main(String[] args) {
		List<Coupon> couponList = Arrays.asList(
				new Coupon(CouponType.CASH, 500, new Date(System.currentTimeMillis()  + 7*24*60*60*1000)),
				new Coupon(CouponType.CASH, 40, new Date(System.currentTimeMillis()  + 5*24*60*60*1000)),
				new Coupon(CouponType.DISSCOUNT, 0.66, new Date(System.currentTimeMillis()  + 8*24*60*60*1000)),
				new Coupon(CouponType.CASH, 500, new Date(System.currentTimeMillis()  + 14*24*60*60*1000)),
				new Coupon(CouponType.SCORE, 5000, new Date(System.currentTimeMillis()  + 2*24*60*60*1000)),
				new Coupon(CouponType.SCORE, 9999, new Date(System.currentTimeMillis()  + 1*24*60*60*1000)),
				new Coupon(CouponType.CASH, 100, new Date(System.currentTimeMillis()  + 5*24*60*60*1000)),
				new Coupon(CouponType.CASH, 200, new Date(System.currentTimeMillis()  + 6*24*60*60*1000)),
				new Coupon(CouponType.DISSCOUNT, 0.1, new Date(System.currentTimeMillis()  + 14*24*60*60*1000)),
				new Coupon(CouponType.DISSCOUNT, 0.95, new Date(System.currentTimeMillis()  + 14*24*60*60*1000)),
				new Coupon(CouponType.SCORE, 23450, new Date(System.currentTimeMillis()  + 30*24*60*60*1000)),
				new Coupon(CouponType.CASH, 500, new Date(System.currentTimeMillis()  + 7*24*60*60*1000))
				);
		
		// 数据库连接参数
		final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
		final String DB_USER_NAME = "用户名";
		final String DB_USER_PASS = "密码";

		// 数据库连接
		try (Connection con = DriverManager.getConnection(JDBC_URL, DB_USER_NAME, DB_USER_PASS);) {
			String sql = "INSERT INTO coupon_info(type,amount,expires)VALUES(?,?,?)";
			
			// 采用批处理
			try(PreparedStatement pst = con.prepareStatement(sql)){
				for(Coupon coupon : couponList) {
					pst.setNString(1, coupon.getType().name()); // 优惠卷类型
					pst.setDouble(2, coupon.getAmount()); // 面额
					pst.setDate(3, new java.sql.Date(coupon.getExpries().getTime())); // 过期时间
					pst.addBatch(); // 添加至批处理
				}
				
				// 整体执行批处理
				int[] rows = pst.executeBatch();
				System.out.println("影响行数:" + Arrays.toString(rows));
			}
			
			
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

JDBC连接池

        从上面的例子中,我们可以看到,每次执行JDBC操作时,都要设置数据库连接参数,调用DriverManager.getConnection()方法,因此我们可以封装一个DBUtils1工具类,代码如下:

package com.fulian.demo03;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

// 数据库工具类
public class DBUtils1 {

	// 数据库连接参数
	private static final String JDBC_URL = "jdbc:mysql://localhost:3306/my_db?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
	private static final String DB_USER_NAME = "root";
	private static final String DB_USER_PASS = "menghao0624";

	// 获取数据库连接
	public static Connection getConnection() {
		try {
			Connection con = DriverManager.getConnection(JDBC_URL,DB_USER_NAME,DB_USER_PASS);
			return con;
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}
}

        但是,在执行JDBC的增删改查的操作时,每一次操作都来必须创建并打开Connection数据库连接,执行SQL语句,最后关闭Connection连接。在这个过程中,Connection连接被频繁创建和关闭,这个操作非常消耗内存与CPU等系统资源。并且,关闭Connection需要手动完成,一旦忘记会造成数据库服务器连接耗尽。在一些高并发访问数据库的场景下,手动的创建和关闭数据库连接,有可能会造成系统性能瓶颈。

        因此,我们就可以使用数据库的连接池,数据库连接池负责分配、管理和释放数据库连接,它的核心思想就是连接复用。通过建立一个数据库连接池,并在连接池池中维护若干个连接对象。当用户想要连接数据库,就要先从连接池中获取连接对象,然后操作数据库。

        目前主流的数据库连接池有:HikariCP、 C3P0、BoneCP、Druid,这里我们使用C3P0连接池技术,需要添加:

 然后,我们就可以使用C3P0再次封装一个DBUtils2工具类(基于java代码实现)和DBUtils3工具类(基于配置文件实现):

DBUtils2:

package com.fulian.demo04;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import com.mchange.v2.c3p0.ComboPooledDataSource;

// 基于数据库连接池技术的工具类
// C3P0
public class DBUtils2 {
	// 数据库连接参数
	private static final String JDBC_URL = "jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&useSSL=false&useTimezone=true&serverTimezone=GMT%2B8";
	private static final String DB_USER_NAME = "用户名";
	private static final String DB_USER_PASS = "密码";

	//DataSource接口实现类(C3P0提供)
	// 创建连接池对象
	private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
	
	// 配置连接池(设置连接池参数)
	static {
		// 数据库连接参数
		dataSource.setJdbcUrl(JDBC_URL);
		dataSource.setUser(DB_USER_NAME);
		dataSource.setPassword(DB_USER_PASS);
		
		// 连接池参数
		dataSource.setInitialPoolSize(10); // 初始化连接数
        dataSource.setMaxPoolSize(100); // 最大连接数
        dataSource.setMinPoolSize(20); // 最小连接数
        dataSource.setMaxIdleTime(30); // 最大空闲时间(单位:秒)
        dataSource.setCheckoutTimeout(3000); // 等待时间(单位:毫秒)
        dataSource.setIdleConnectionTestPeriod(30); // 检查空闲连接的时间间隔(单位:秒)
	}
	
	// 获取数据库连接
	public static Connection getConnection() {
		// 从连接池中返回一个“空闲”连接
		try {
			Connection con = dataSource.getConnection();
			return con;
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}
}

DBUtils3:默认在src目录下读取名称为c3p0-config.xml的配置文件

package com.fulian.demo04;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import com.mchange.v2.c3p0.ComboPooledDataSource;

// 基于数据库连接池技术的工具类
// C3P0
public class DBUtils3 {

	//DataSource接口实现类(C3P0提供)
	// 创建连接池对象
	private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
	
	// 获取数据库连接
	public static Connection getConnection() {
		// 从连接池中返回一个“空闲”连接
		try {
			Connection con = dataSource.getConnection();
			return con;
		} catch (SQLException e) {
			e.printStackTrace();
			return null;
		}
	}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<!-- 默认配置,如果没有指定则使用这个配置 -->
	<default-config>
		<!-- 四项基本配置 -->
		<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/数据库名称?charset=utf8mb4&amp;useSSL=false&amp;useTimezone=true&amp;serverTimezone=GMT%2B8</property>
		<property name="user">用户名</property>
		<property name="password">密码</property>
		
		<!-- 超时时间:当连接池耗尽时,客户端调用getConnection()后等待获取新连接的时间,超时后将抛出
  			SQLException,如设为0则无限期等待。单位毫秒。Default: 0 -->
		<property name="checkoutTimeout">30000</property>
		
		<!-- 检查空闲连接的时间间隔:每隔多少秒检查连接池的空闲连接,0表示不检查-->
		<property name="idleConnectionTestPeriod">30</property>
		
		<!-- 初始化连接数 -->
		<property name="initialPoolSize">10</property>
		
		<!-- 连接的最大空闲时间,默认为0秒、不会关闭任何连接。设置30秒,30秒到期后,
			连接若未使用就会被关闭 -->
		<property name="maxIdleTime">30</property>
		
		<!-- 最大连接数 -->
		<property name="maxPoolSize">100</property>
		
		<!-- 最小连接数 -->
		<property name="minPoolSize">10</property>
	</default-config>
	
</c3p0-config>

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仙草不加料

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值