1. 下载MySQL的JDBC驱动jar包
进入MySQL官网
https://www.mysql.com/
然后按图操作
2. 下载Oracle的JDBC驱动jar包
按图提示操作:
2.1引用Oracle的JDBC驱动jar包
2.2 Oracle中URl连接的格式
格式一
jdbc:oracle:thin:@host:port:SID
例如: jdbc:oracle:thin:@localhost:1521:orcl
格式二
jdbc:oracle:thin:@//host:port/service_name
例如: jdbc:oracle:thin:@//localhost:1521/orcl.city.com
jdbc:oracle:thin:@//IP地址|主机名:端口号/oracledb
格式三
我在谷歌上找了一些资源,要实现这种连接方式首先要建立tnsnames.ora文件,然后通过System.setProperty指明这个文件路径。再通过上面URL中的@符号指定文件中的要使用到的资源。
这种格式我现在水平几乎没见过,对于我来说用得到这种的情况并不多吧。当然既然是通过配置文件来读取指定资源肯定也可以直接将资源拿出来放在URL中,直接放在URL中的URL模版是下面这样的(tnsnames.ora这个文件中放的就是@符号后面的那一段代码,当然用文件的好处就是可以配置多个,便于管理):
jdbc:oracle:thin:@TNSName
例如: jdbc:oracle:thin:@TNS_ALIAS_NAME
jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.16.91)(PORT=1521)))(CONNECT_DATA=(SERVICE_NAME=orcl)))
以上三个格式为网上找到的介绍,在学习的过程中主要使用格式二,需要结合实际产品来查看。
3. 引入JDBC
按图操作
新建名为lib的目录,将下载好的JDBC文件解压后,并复制到此目录下
添加后并没有与项目产生关联,需要右键点击添加为库(应该将文件夹下的jar包添加为库,而不是整个文件目录)
点击确定
添加完后如下图
4. 使用JDBC
4.1 准备数据
CREATE DATABASE atguigu;
USE atguigu;
CREATE TABLE t_user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户主键',
account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
PASSWORD VARCHAR(64) NOT NULL COMMENT '密码',
nickname VARCHAR(20) NOT NULL COMMENT '昵称');
INSERT INTO t_user(account,PASSWORD,nickname) VALUES
('root','123456','经理'),('admin','666666','管理员');
4.2 注册驱动
// 1. 注册驱动
/**
* 驱动版本 8+ com.mysql.cj.jdbc.Driver
* 驱动版本 5+ com.sql.jdbc.Driver
*/
// DriverManager.registerDriver(new Driver());
/**
* 问题:
* 由于DriverManager.registerDriver(new Driver());本身会注册一次,new Driver()也会调用一次DriverManager.registerDriver(new Driver());,所以不推荐使用次方式进行注册
* Driver类的静代码块会调用注册,但是使用new Driver() 在代码里写死了是注册MySQL的驱动,不利于代码灵活性
* 解决:只注册一次驱动,触发一次静态代码块
* 类加载的机制:
* 1. 加载(加载class文件 -> jvm虚拟机的class对象)
* 2. 连接(验证【检查文件类型】 -> 准备【静态变量赋默认值】 -> 解析【触发静态代码块】)
* 3. 初始化(静态属性赋真实值)
* 以下7种方式会触发类加载,类加载后就会触发静态代码块:
* 1. new关键字(静态代码块跟new几次没有关系,只会触发一次)
* 2. 调用静态属性
* 3. 调用静态方法
* 4. 接口 1.8 新特性 default 默认实现
* 5. 反射
* 6. 子类触发父类(子类被实例化会触发父类的静态代码块)
* 7. 触发类的入口方法main()
*/
/**
* 触发类加载,进而触发静态代码块的调用
* 将字符串配置到配置文件中,可以实现在不改变代码的情况下,完成数据库驱动的切换,我们只需要修改配置文件即可
*/
Class.forName("com.mysql.cj.jdbc.Driver");
4.3 建立连接 connection
// 2. 获取连接
/**
* java程序连接数据库,肯定是调用某个方法,方法也需要填入连接数据库的基本信息:
* 数据库ip地址:192.168.101.130
* 数据库端口号:3306(默认)
* 账号:root
* 密码:root
* 数据库名称:atguigu
* url具体格式:jdbc:数据库种类[mysql|oracle等]://ip地址[或者主机名]:端口号/数据库名?key=value&key=value[可选信息]
* 默认本机安装,默认端口时,可省略地址和端口号,例子:jdbc:mysql:///数据库名?key=value&key=value[可选信息]
*
* 扩展路径参数(了解):8.0.25版本以后不需要添加,会自动识别
* serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
*/
/**
* 方式1
*/
Connection connection = DriverManager.getConnection("jdbc:mysql//192.168.101.130:3306/atguigu", account, password);
/**
* 方式2
*/
Properties properties = new Properties();
properties.put("user", "root");
properties.put("password", "root");
Connection connection1 = DriverManager.getConnection("jdbc:mysql//192.168.101.130:3306/atguigu",properties);
/**
* 方式3
*/
Connection connection2 = DriverManager.getConnection("jdbc:mysql//192.168.101.130:3306/atguigu?user=root&password=root");
4.4 创建发送SQL语句对象
// 3. 创建statement
/**
* 在使用Statement时,发现有几个问题:
* 1. 拼接字符串比较麻烦
* 2. 只能拼接字符串类型的数据,其他的数据库类型无法处理
* 3. 最为严重的一点为可能发生注入攻击,比如传入的字符串的动态值充当了SQL语句,影响其结构和原有的查询结果
* 例如字符串拼接的最后一个条件输入为 ' or '1' = '1 则完整的SQL变为了:SELECT * FROM t_user WHERE account = 'xxx' AND password = '' or '1' = '1';
*
* preparedstatement
* 1. 编写SQL语句结构中不包含动态值部分的语句,动态值部分用?替代,注意,?只能替代动态值
* 2. 创建preparedstatement,并且传入动态值
* 3. 动态值部分用?占位符,单独去赋值
*/
Statement statement = connection.createStatement();
Statement statement1 = connection1.createStatement();
Statement statement2 = connection2.createStatement();
String sql1 = "SELECT * FROM t_user WHERE account = ? AND password = ?;"
PreparedStatement preparedStatement = connection.prepareStatement(sql1);
/**
* 参数1:index 占位符的位置 从左向右数 从1开始
* 参数2:object 占位符的值 可以设置任何类型的数据 避免了字符串拼接并且支持的类型更加丰富
/
preparedStatement.setObject(2,password);
preparedStatement.setObject(1,account);
4.5 发送SQL语句,获取结果
// 4. 发送SQL语句,并获取返回结果
/**
* SQL分类:
* DDL(数据定义语言) - Create、Alter、Drop 这些语句自动提交,无需用Commit提交。(Data Definition Language)
* DQL(数据查询语言) - Select 查询语句不存在提交问题。
* DML(数据操纵语言) - Insert、Update、Delete 这些语句需要Commit才能提交。(Data Manipulation Language)
* DTL(事务控制语言) - Commit、Rollback 事务提交与回滚语句。
* DCL(数据控制语言) - Grant、Revoke 授予权限与回收权限语句。
*
* int i = statement.executeUpdate(sql);
* 参数:非DQL
* 返回:int
* 情况1:DML语句,返回影响的行数
* 情况2:非DML语句,返回0
*
* ResultSet resultSet = statement.executeQuery(sql);
* 参数:DQL
* 返回:ResultSet 结果封装对象
*/
String sql = "SELECT * FROM t_user WHERE account = '" + account + "' AND password = '" + password + "';";
int i = statement.executeUpdate(sql);
ResultSet resultSet = statement.executeQuery(sql);
ResultSet resultSet1 = preparedStatement.executeQuery();
4.6 结果解析
// 5. 结果集解析
/**
* Java是一种面向对象的语言,将查询结果封装为了ResultSet对象,我们应该理解,内部一定也是有行有列的,与数据库管理软件查询出来的一样的
*
* ResultSet解析 -> 逐行获取数据 -> 在获取一行中的每列
*
* A ResultSet object maintains a cursor pointing to its current row of data.
* Initially the cursor is positioned before the first row.
* The next method moves the cursor to the next row,
* and because it returns false when there are no more rows in the ResultSet object,
* it can be used in a while loop to iterate through the result set.
*
* 想要进行数据解析,我们需要做两件事情:
* 1. 移动游标到指定的行
* 2. 获取指定行的列数据
*
* 1. 游标的移动问题:
* ResultSet内部包含一个游标,指定当前行数据
* 默认游标指定的是第一行的数据
* 我们可以调用next()方法向后移动一行游标
* 如果我们有很多行数据,我们可以用while(ResultSet.next()){获取每一行数据}
* true:有更多行数据,游标进行下移一行
* false:没有更多航数据,不移动
* 移动光标的方法有很多,只需要记住next()方法,配合while循环使用
*
* 2. 获取当前行中列的数据问题:
* ResultSet.get类型(String columnLabel | int colnmuIndex);
* columnLabel:为列名或别名
* columnIndex:列的下角标,从1开始
*/
while (resultSet.next()) {
int id = resultSet.getInt(1);
String account = resultSet.getString("account");
String password = resultSet.getString("PASSWORD");
String nickname = resultSet.getString("nickname");
System.out.println("id:" + id + "\taccount:" + account + "\tpassword:" + password + "\tnickname:" + nickname);
}
/**
* 上述写法是纯手动去解析结果文件,我们需要一种自动的解析方法
* 并且ResultSet类型我们在Java程序中使用并不是很方便,我们希望将ResultSet对象转换为list<map>对象
* 思路:map中存放一行数据,map中的key为查询的列名或者别名,map中的value为查询出来的值,最后将map添加到集合中
*
* 使用ResultSetMetaData metaData = resultSet.getMetaData();来获取当前行的列信息对象(可以根据下标获取列的名或别名,可以获取列的数量)
*/
List<Map> list = new ArrayList<Map>();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
Map<String, Object> map = new HashMap<String, Object>();
for (int i = 1; i <= columnCount; i++) {
map.put(metaData.getColumnLabel(i), resultSet.getObject(i));
}
list.add(map);
}
System.out.println(list + "list");
4.7 释放资源
// 6. 关闭资源
/**
* 从内往外关
*/
resultSet.close();
statement.close();
connection.close();
4.8 获取自增主键
在调用connection.prepareStatement方法时,传入Statement.RETURN_GENERATED_KEYS参数来获取自增主键
在调用preparedStatement.getGeneratedKeys();,获取装有主键值的容器
{
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.101.130:3306/atguigu", "root", "root");
String ins_sql = "INSERT INTO t_user(account,password,nickname) values(?,?,?);";
PreparedStatement preparedStatement = connection.prepareStatement(ins_sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1, "www");
preparedStatement.setObject(2, "123456");
preparedStatement.setObject(3, "untifa");
int i = preparedStatement.executeUpdate();
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
if (i > 0) {
System.out.println("数据插入成功");
boolean next = generatedKeys.next();
if (next) {
System.out.println("数据的主键值为:" + generatedKeys.getInt(1));
}
}
generatedKeys.close();
preparedStatement.close();
connection.close();
}
4.9 批量插入
在进行连接时,URL一定要加上参数rewriteBatchedStatements=true,注意在写插入的SQL语句时,values
不要写成value
,语句结束不要加“;”
,使用preparedStatement.addBatch();来添加插入的数据,然后调用 preparedStatement.executeBatch();方法插入数据。
{
long start = System.currentTimeMillis();
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://192.168.101.130:3306/atguigu?rewriteBatchedStatements=true", "root", "root");
String ins_sql = "INSERT INTO t_user(account,password,nickname) values(?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(ins_sql);
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1, "sss" + i);
preparedStatement.setObject(2, "sss" + i);
preparedStatement.setObject(3, "untifa");
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
System.out.println(end - start);
preparedStatement.close();
connection.close();
}
4.10 事务
一个事务的最近本要求,必须是同一个连接对象 connection
事务的开启要在业务层实现
将连接从业务层传入到Dao层中
准备数据,我们来演示一下事务中最经典的案例:转账
主要关注的为业务层和Dao层的代码结构
-- 继续在atguigu的库中创建银行表
CREATE TABLE t_bank(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '账号主键',
account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
money INT UNSIGNED COMMENT '金额,不能为负值') ;
INSERT INTO t_bank(account,money) VALUES
('ergouzi',1000),('lvdandan',1000);
代码实现
BankService.java
package com.untifa.api.transaction;
import org.junit.Test;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* @Title: BankService
* @Package: com.untifa.api.transaction
* @Description:
* @Author: untifa
* @Date: 2023/3/23 - 23:32
*/
public class BankService {
public void transfer(String addAcount, String subAccount, BigDecimal money) throws ClassNotFoundException, SQLException {
Connection connection;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
try {
connection = DriverManager.getConnection("jdbc:mysql://192.168.101.130:3306/atguigu", "root", "root");
} catch (SQLException e) {
System.out.println("获取连接失败");
e.printStackTrace();
throw e;
}
} catch (ClassNotFoundException e) {
System.out.println("注册驱动失败");
e.printStackTrace();
throw e;
}
boolean flag = false; // 转账成功标志
try {
connection.setAutoCommit(false);
BankDao bankDao = new BankDao();
bankDao.addmoney(addAcount, money, connection);
bankDao.submoney(subAccount, money, connection);
flag = true;
connection.commit();
} catch (SQLException e) {
flag = false;
connection.rollback();
System.out.println(":#####" + e.getMessage());
e.printStackTrace();
throw e;
} finally {
connection.close();
if (flag) {
System.out.println("转账成功");
} else {
System.out.println("转账失败");
}
}
}
@Test
public void testTransfer() throws SQLException, ClassNotFoundException {
String addAcount = "ergouzi";
String subAccount = "lvdandan";
BigDecimal money = new BigDecimal("500");
transfer(addAcount, subAccount, money);
}
}
BankDao.java
package com.untifa.api.transaction;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @Title: BankDao
* @Package: com.untifa.api.statement
* @Description:
* @Author: untifa
* @Date: 2023/3/23 - 23:27
*/
public class BankDao {
public void addmoney(String account, BigDecimal money, Connection connection) throws SQLException {
String upd_sql = "update t_bank t set t.money = t.money + ? where t.account = ?";
PreparedStatement preparedStatement = connection.prepareStatement(upd_sql);
preparedStatement.setObject(1, money);
preparedStatement.setObject(2, account);
int i = preparedStatement.executeUpdate();
if (i > 0) {
System.out.println("加钱成功");
}
preparedStatement.close();
}
public void submoney(String account, BigDecimal money, Connection connection) throws SQLException {
String upd_sql = "update t_bank t set t.money = t.money - ? where t.account = ?";
PreparedStatement preparedStatement = connection.prepareStatement(upd_sql);
preparedStatement.setObject(1, money);
preparedStatement.setObject(2, account);
int i = preparedStatement.executeUpdate();
if (i > 0) {
System.out.println("减钱成功");
}
preparedStatement.close();
}
}