目录
一、JDBC入门
1. jdbc的概念
- JDBC(Java DataBase Connectivity:java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的。
- JDBC的作用:可以通过java代码操作数据库
2. jdbc的本质
- 其实就是java官方提供的一套规范(接口)。用于帮助开发人员快速实现不同关系型数据库的连接
3. jdbc的快速入门程序
-
导入jar包
-
注册驱动
Class.forName("com.mysql.jdbc.Driver");
-
获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db2", "root", "root");
-
获取执行者对象
Statement stat = conn.createStatement();
-
执行sql语句,并接收返回结果
String sql = "SELECT * FROM user"; ResultSet rs = stat.executeQuery(sql);
-
处理结果
while(rs.next()) { System.out.println(rs.getInt("id") + "\t" + rs.getString("name")); }
-
释放资源
rs.close(); stat.close(); conn.close();
-
创建一个java项目:JDBC基础
-
将jar包导入,并添加到引用类库
-
import java.sql.*; public class Main { public static void main(String[] args) { Connection conn = null; Statement stat = null; ResultSet rs=null; try { Class.forName("com.mysql.cj.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123456"); stat = conn.createStatement(); String sql = "SELECT * FROM user"; rs = stat.executeQuery(sql); while(rs.next()) { System.out.println(rs.getInt("id") + "\t" + rs.getString("name")+ "\t" + "\t" + rs.getInt("age") + "\t" + rs.getString("status")+ "\t" + rs.getString("gender")); } } catch (SQLException | ClassNotFoundException e) { throw new RuntimeException(e); } finally { try { if(rs!=null){ rs.close(); } } catch (SQLException e) { throw new RuntimeException(e); } try { if(stat!=null) { stat.close(); } } catch (SQLException e) { throw new RuntimeException(e); } try { if(conn!=null){ conn.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } } }
1.1、使用配置文件读取连接信息
二、JDBC各个功能类详解
1. DriverManager
-
DriverManager:驱动管理对象
- 注册驱动(告诉程序该使用哪一个数据库驱动)
-
注册给定的驱动程序:static void registerDriver(Driver driver) (DriverManager的方法)
-
使用了Class.forName:Class.forName(“com.mysql.jdbc.Driver”)
- 通过了给forName指定了是mysql的驱动
- 它会帮助我们注册驱动,如下:
-
在com.mysql.jdbc.Driver类中存在静态代码块(通过查看源码发现)
//这是com.mysql.jdbc.Driver的静态代码块,只要使用这个类,就会执行这段代码 //而Class.forName("com.mysql.jdbc.Driver")就正好使用到了这个类 static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
-
- 注册驱动(告诉程序该使用哪一个数据库驱动)
-
获取数据库连接(获取到数据库的连接并返回连接对象)
- static Connection getConnection(String url, String user, String password);
- 返回值:Connection数据库连接对象
- 参数
- url:指定连接的路径。语法:jdbc:mysql://ip地址(域名):端口号/数据库名称
- user:用户名
- password:密码
- static Connection getConnection(String url, String user, String password);
2.Connection
- Connection:数据库连接对象
- 获取执行者对象
- 获取普通执行者对象:Statement createStatement();
- 获取预编译执行者对象:PreparedStatement prepareStatement(String sql);
- 管理事务
- 开启事务:setAutoCommit(boolean autoCommit); 参数为false,则开启事务。
- 提交事务:commit();
- 回滚事务:rollback();
- 释放资源
- 立即将数据库连接对象释放:void close();
- 获取执行者对象
3.Statement
- Statement:执行sql语句的对象
- 执行DML语句:
int executeUpdate(String sql)
;- 返回值int:返回
影响的行数
。 - 参数sql:可以
执行insert、update、delete
语句。 int i = statement.executeUpdate(sql);
- 返回值int:返回
- 执行DQL语句:
ResultSet executeQuery(String sql)
;- 返回值ResultSet:封装
查询的结果
。 - 参数sql:可以
执行select
语句。 ResultSet resultSet = statement.executeQuery(sql);
- 返回值ResultSet:封装
- 释放资源
- 立即将执行者对象释放:void close();
- 执行DML语句:
4.ResultSet
- ResultSet:结果集对象
- 判断结果集中是否还有数据:boolean next();
- 有数据返回true,并将索引向下移动一行
- 没有数据返回false
- 获取结果集中的数据:XXX getXxx(“列名”);
- XXX代表数据类型(要获取某列数据,这一列的数据类型)
- 例如:String getString(“name”); int getInt(“age”);
- 释放资源
- 立即将结果集对象释放:void close();
- 判断结果集中是否还有数据:boolean next();
获取列的数据问题(获取游标指定的那一行里列的数据)
- resultSet.get类型(String columnLabel 或 int columnIndex);
- columnLable: 列名 如果起别名了就 写别名 select * …
select id as aid, account as ac …- columnIndex: 列的下角标 从
左向右从1开始
总结
//1.注册驱动
方案1: 调用静态方法,但是会注册两次
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
方案2: 反射触发 - 注册一次
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection();
3 (String url,String user,String password)
2 (String url,Properties info(user password))
1 (String url?user=账号&password=密码 )
//3.创建statement
//静态
Statement statement = connection.createStatement();
//预编译
PreparedStatement preparedstatement = connection.preparedStatement(sql语句结构);
//4.占位符赋值
preparedstatement.setObject(?的位置从左向右从1开始,?的值)
//5.发送sql语句获取结果
int rows = executeUpdate(); //非DQL
Resultset = executeQuery(); //DQL
//6.查询结果集解析
//移动光标指向行数据 next(); if(next()) while(next())
//获取列的数据即可 get类型(列的下标 从1开始 | 别名或列名)
//获取列的信息 getMetadata(); ResultsetMetaData对象 包含的就是列的信息
getColumnCount(); //列的数量
getCloumnLebal(index); //列的别名,没有别名直接取列名
//7.关闭资源
close();
三、代码优化(preparedStatement)
1. 灵活变通SQL
-
这是我们写的SQL语句,但你是否觉得这样去写太死了,通常我们在操作数据库的时候都会写很多SQL语句,是非常灵活的,但是就下面这样的话确实是不太好
String sql = "insert into student values (1, 'zhansan')";
-
所以我们可以将代码改为下面这样,记录中的【学号】和【姓名】字段我们可以通过自己输入来进行控制
// 3.输入学号和姓名 System.out.println("请输入学号:"); Scanner sc1 = new Scanner(System.in); int id = sc1.nextInt(); System.out.println("请输入姓名:"); Scanner sc2 = new Scanner(System.in); String name = sc2.next(); // 4.构造一个 SQL 语句,来完成插入操作 //String sql = "insert into student values (1, 'zhansan')"; String sql = "insert into student values (" + id + ", '" + name + "')";
2.JDBC实现模糊查询
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 这个程序两个任务
* 第一:测试DBUtil是否好用
* 第二:模糊查询怎么写?
*/
public class JDBCTest12 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//获取连接
connection = DBUtil.getConnection();
//获取预编译的数据库操作对象
//错误写法
/*String sql = "select ename from emp where ename like '_?%'";
ps = connection.prepareStatement(sql);
ps.setString(1,"A");*/
String sql = "select ename from emp where ename like ?";
ps = connection.prepareStatement(sql);
ps.setString(1,"_A%");
resultSet = ps.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString("ename"));
}
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
//释放资源
DBUtil.close(connection,ps,resultSet);
}
}
}
3. 防止SQL注入攻击
黑客可以通过代码直接拼接构造sql语句导致sql语句结构可被恶意篡改
对于上面这种危害,我们进行预防最靠谱的方案,就是使用PreparedStatement
中占位符替换的方式,来实现SQL的构造~
-
我们可以把SQL语句构造成下面这样,两个【?】就相当于是占位符
String sql = "insert into student values (?, ?)"; //通过占位符进行替换 String sql = "update student set name = ? where id = ?"; String sql = "delete from student where id = ?";
-
还记得我们上面使用到的那个
statement
对象吗,它可以用来描述SQL的情况,我们可以通过里面的【setInt()】和【setString()】方法来设置记录中的两个字段,从而将这个SQL语句构造完整
// jdbc中还需要搭配一个特定的对象,来搭配描述这里的sql情况 PreparedStatement ps = connection.prepareStatement(sql); ps.setInt(1, id); ps.setString(2, name);
Statement和PreparedStatement对比
- Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题。
- Statement是编译一次执行一次,PreparedStatement是编译一次,可执行N次,PreparedStatement效率较高一些。
- PreparedStatement会在编译阶段做类型的安全检查。
【综上所述】:PreparedStatement使用较多,只有较少数的情况下需要使用Statement。
Statement
PrepareStatement
什么情况下必须使用Statement呢???
- 业务方面要求必须支持SQL注入的时候
- Statement支持SQL注入,凡是以业务方面要求是需要进行SQL语句拼接的,必须使用Statement。
四、Druid连接池技术
(一)连接性能消耗问题的分析
(二)数据库连接池的作用
(三)国货之光Druid连接池的使用
- 记得导入Druid工具类的jar包
- 记得创建一个学习Druid的测试类
/**
* ClassName: DruidUsePar
* Package: com.atguigu.api.druid
* Description:
* druid连接池使用类
*/
public class DruidUsePart {
}
1.硬编码方式(了解,不推荐)
/**
* 硬编码实现
* <p>
* 1.创建一个druid连接池对象
* 2.设置连接池参数[必须 | 非必须]
* 3.获取连接[通用方法,所有连接池都一样]
* 4.回收连接[通用方法,所有连接池都一样] TODO:这里不是 “关闭(释放)连接” 而是 “回收连接”
*/
@Test
public void testHard() throws SQLException {
//连接池对象
//DruidDataSource实现了Java规定标准的的DataSource接口
DruidDataSource druidDataSource = new DruidDataSource();
//设置参数
//必须: 连接数据库驱动类的全限定符[注册驱动] | url | user | password
druidDataSource.setUrl("jdbc:myql://127.0.0.1:3306/atguigu");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");//帮助我们进行驱动注册和获取连接
//非必须:初始化连接数量 , 最大的连接数量 , ......
druidDataSource.setInitialSize(5);//初始化连接数量
druidDataSource.setMaxActive(10);//最大的连接数量
//获取连接
//DruidPooledConnection实现了Java规定标准的Connection接口
//因此现在 用Connection类 来 实例化druidPooledConnection对象后,
//TODO:当调用.close()方法时,已经 不是关闭(释放)连接 ,而是 回收连接 了
Connection druidPooledConnection = druidDataSource.getConnection();
//数据库CURD
//回收连接
druidPooledConnection.close();//连接池提供的连接的.close()就是回收连接
}
2.软编码方式
(1)外部配置
- 存放位置:src/druid.properties
# 注:外部配置文件后缀必须是properties!
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
(2)Druid声明代码
/**
* 软编码实现
* 通过读取外部配置文件的方法,实例化druid连接池对象
*/
@Test
public void testSoft() throws Exception {
//1.读取外部配置文件 Properties
Properties properties = new Properties();
//src下的文件,可以使用类加载器提供的方法实现装载(properties.load(param))
InputStream inputPropertiesStream = DruidUsePart.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(inputPropertiesStream);
//2.使用连接池的工具类的工厂模式(DruidDataSourceFactory)来创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
//数据库CURD
//......
//回收连接
connection.close();
}
3.Druid配置(了解)
4.笔记
-
连接池帮我们进行注册驱动、创建链接
-
关于外部配置文件
封装
(一)JDBC工具类封装version1.0
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///atguigu
package com.atguigu.api.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* ClassName: JDBC_Utils
* Package: com.atguigu.api.utils
* Description:
* v1.0版本工具类
* 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
* <p>
* 小建议:
* 工具类的方法推荐写成静态方法,这样的话外部调用会更加方便
* <p>
* 实现:
* 属性 连接池对象[实例化一次]
* <p>
* 实例化一次的方法:
* 1.单例模式
* 2.static{全局调用一次} (静态代码块)
* <p>
* 方法:
* 1.对外提供连接的方法
* 2.回收外部传入的连接的方法
*/
public class JDBC_Utils {
private static DataSource dataSource = null;//连接池对象
//初始化连接池对象
static {
Properties properties = new Properties();
InputStream ips = JDBC_Utils.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(ips);
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 对外提供连接的方法
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/**
* 回收连接
*/
public static void freeConnection(Connection connection) throws SQLException {
connection.close();//连接池的连接,调用close()就是回收
}
}
二)JDBC工具类封装version2.0
对工具类v1.0版本的进一步优化
- 在考虑事务的情况下,如何从一个线程里的不同方法中获取同一个连接
1. 工具类v2.0
package com.atguigu.api.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* ClassName: JDBC_Utils
* Package: com.atguigu.api.utils
* Description:
* v2.0版本工具类
* 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法
* TODO:
* 利用线程本地变量(ThreadLocal)来存储连接信息!确保一个线程的多个方法可以获取同一个connection!
* 优势: 事务操作的时候 service 和 dao 属于同一个线程,不用再传递connection的参数了
* 大家都可以调用getConnection()自动获取相同的连接池
*/
public class JDBC_Utils_V2 {
private static DataSource dataSource = null;//连接池对象
//TODO:线程本地变量
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//初始化连接池对象
static {
//方法一 v1.0
// Properties properties = new Properties();
// InputStream ips = JDBC_Utils_V2.class.getClassLoader().getResourceAsStream("druid.properties");
// try {
// properties.load(ips);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
//
// try {
// dataSource = DruidDataSourceFactory.createDataSource(properties);
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
//方法二 v2.0
try {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 对外提供连接的方法
*/
public static Connection getConnection() throws SQLException {
//线程本地变量是否存在连接
Connection connection = threadLocal.get();
//第一次没有
if (connection == null) {
//线程本地变量没有,从连接池中获取连接
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
}
/**
* 回收连接
*/
public static void RecycleConnection() throws SQLException {
Connection connection = threadLocal.get();
if (connection != null) {
threadLocal.remove();//清空线程本地变量的数据
connection.setAutoCommit(true);//把BankService里面,修改过的事务提交状态给恢复一下(false -> true)
connection.close();//将拿出来的连接回收到连接池即可
}
}
}
高级应用封装BaseDao
针对DQL查询和非DQL查询,分成两类(即,定义两个方法)
public abstract class BaseDao {
/*
通用的增、删、改的方法
String sql:sql
Object... args:给sql中的?设置的值列表,可以是0~n
*/
protected int update(String sql,Object... args) throws SQLException {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
//执行sql
int len = ps.executeUpdate();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//connection.getAutoCommit()为false,不要在这里回收connection,由开启事务的地方回收
//connection.getAutoCommit()为true,正常回收连接
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return len;
}
/*
通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
这里的clazz接收的是T类型的Class对象,
如果查询员工信息,clazz代表Employee.class,
如果查询部门信息,clazz代表Department.class,
返回List<T> list
*/
protected <T> ArrayList<T> query(Class<T> clazz,String sql, Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
//设置?的值
if(args != null && args.length>0){
for(int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
ArrayList<T> list = new ArrayList<>();
ResultSet res = ps.executeQuery();
/*
获取结果集的元数据对象。
元数据对象中有该结果集一共有几列、列名称是什么等信息
*/
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();//获取结果集列数
//遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
while(res.next()){
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for(int i=1; i<=columnCount; i++){
//for循环一次,代表取某一行的1个单元格的值
Object value = res.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
//String columnName = metaData.getColumnName(i);//获取第i列的字段名
//这里再取别名可能没办法对应上
String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t, value);
}
list.add(t);
}
res.close();
ps.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return list;
}
//目的主要是安全保障,提高代码的健壮性
protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
ArrayList<T> list = query(clazz, sql, args);
if(list == null || list.size() == 0){
return null;
}
return list.get(0);
}
}
五、JDBC工具类的封装
driverClass = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/test
username = root
password = 123456
package utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* @version 1.0
* @Author pangyu
* @Date 2024/5/16 16:52
* @注释
*/
/*
JDBC工具类
*/
public class JDBCUtils {
private JDBCUtils(){
}
// 声明所需要的配置变量
private static String driverClass;
private static String url;
private static String username;
private static String password;
private static Connection con;
//提供静态代码块,读取配置文件信息为变量赋值,注册驱动
static {
try {
InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties prop = new Properties();
prop.load(is);
driverClass = prop.getProperty("driverClass");
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取数据库连接方法
public static Connection getConecction(){
try {
Class.forName(driverClass);
con = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return con;
}
//释放资源方法
public static void close(Connection con, Statement stat, ResultSet rs){
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection con, Statement stat){
close(con,stat,null);
}
}
扩展
(一)自增长主键回显的实现
1.功能需求
- 主键回显
- 定义:在数据库里的表中插入一条记录时,如果该表设置了适当的参数,那么数据库会在插入操作完成后返回生成的主键值。
- 这个主键可以是有数据库自动生成的,也可以是由开发人员明确指定的。
- 作用:主键回显能够允许开发者在执行数据库中表的插入操作后,立即在Java后端中获取到新插入记录的主键(值),方便后续的操作和数据管理
- 定义:在数据库里的表中插入一条记录时,如果该表设置了适当的参数,那么数据库会在插入操作完成后返回生成的主键值。
- 这里要解决的问题:
- **主表默认增长,但是从表不知道值,所以要主键回显,**让从表知道主表的主键增长了
- 解决思路:
- 在多表关联插入数据时,一般主表的主键都是自动生成的,因此在插入数据之前我们无法知道这条数据的主键,但是从表需要在插入数据之前就绑定主表的主键,这时可以使用主键回显技术
2.功能实现
- 继续沿用之前的数据库里的表的数据
/**
* TODO:
* t_user插入一条数据,并且 -- 获取数据库自增长的主键 --
* <p>
* 使用总结:
* 1.创建prepareStatement的时候,在.prepareStatement()的参数列表中加入一个参数,
* 来告知数据库,返回数据的时候记得携带数据库自增长的主键。
* 加入的参数:Statement.RETURN_GENERATED_KEYS
* <p>
* 2.获取 插入数据之后,其主键值也已经更新完后的 结果集对象,
* 获取对应的数据即可 - ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
*/
@Test
//主键回显 和 主键值获取
public void returnPrimaryKey() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu", "root", "root");
String sql = "insert into t_user(account,password,nickname) value(?,?,?)";
//创建preparedStatement
//注意:"PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的:要数据库返回数据时把KEYS的内容也带回来
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1, "test1");
preparedStatement.setObject(2, 123456);
preparedStatement.setObject(3, "驴蛋蛋");
int i = preparedStatement.executeUpdate();
//结果分析
if (i > 0) {
System.out.println("插入成功!");
//可以获取回显的主键
//获取搞完主键后的结果集对象,一行 一列 ,id = 值
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();
int id = generatedKeys.getInt(1);
System.out.println("id = " + id);
} else {
System.out.println("插入失败!");
}
preparedStatement.close();
connection.close();
}
(二)批量数据插入的性能提升
1.功能需求
- 批量数据插入优化
- 提升大量数据插入效率
2.功能实现
(1)使用普通的方式插入10000条数据所需的时间
@Test
//使用普通的方式插入10000条数据所需的时间
public void testInsert() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu", "root", "root");
String sql = "insert into t_user(account,password,nickname) value(?,?,?)";
//创建preparedStatement
//注意:"PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的:要数据库返回数据时把KEYS的内容也带回来
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//占位符赋值
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1, "dd" + i);
preparedStatement.setObject(2, "dd" + i);
preparedStatement.setObject(3, "驴蛋蛋" + i);
//TODO: 占位符赋值之后,发送SQL语句,并返回结果
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
//结果分析
System.out.println("执行10000次数据插入消耗的时间: " + (end - start));//26816毫秒
preparedStatement.close();
connection.close();
}
(2)优化:使用 批量插入 的方式插入10000条数
总结
批量输入
- 路径后面添加 ?rewriteBatchedStatements=true 允许批量输入
- insert into
values
[必须要写被插入数据的列] 且SQL语句的最后不能添加';'
- 不是执行每条语句,而是利用addBatch()来批量添加数据到SQL语句中的values后面
- 遍历添加数据完以后,统一执行executeBatch()来执行SQL语句
@Test
//批量插入数据优化
//使用 批量插入 的方式插入10000条数据所需的时间
public void testBatchInsert() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
//TODO:这里添加了一个url的路径属性里的可选信息 - "rewriteBatchedStatements=true"
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1/atguigu?rewriteBatchedStatements=true", "root", "root");
//TODO:这里不能写value,要写values,一直写values就行,记住
//TODO: 这里SQL语句最后不能加上 ';' 否则会执行失败。
// 原因:不加';'是因为 - 批量插入的原理是在原来的sql语句后面继续追加相应的sql语句
String sql = "insert into t_user(account,password,nickname) values(?,?,?)";
//创建preparedStatement,TODO:注意"com.atguigu.api.PreparedStatement.Statement.RETURN_GENERATED_KEYS"的目的是要数据库返回数据时把KEYS的内容也带回来
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//测试发送的时间
long start = System.currentTimeMillis();
//占位符赋值
for (int i = 0; i < 10000; i++) {
preparedStatement.setObject(1, "ddd" + i);
preparedStatement.setObject(2, "ddd" + i);
preparedStatement.setObject(3, "驴蛋蛋d" + i);
//TODO: 占位符赋值之后,发送SQL语句,并返回结果
//TODO:删去preparedStatement.executeUpdate();
//TODO:赋值一次就传给数据库一次的想法就不用了
//TODO:而是用 addBatch() 把赋值后的数据直接追加到SQL语句中的values后面
preparedStatement.addBatch();
}
//TODO:最后执行批量操作
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
//结果分析
System.out.println("执行10000次数据插入消耗的时间: " + (end - start));//310毫秒
preparedStatement.close();
connection.close();
}
(三)、JDBC的事物自动提交机制
JDBC事务机制
- JDBC中的事务是自动提交的,什么是自动提交?
- 只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为。
- 但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。
手动提交机制
重点三行代码:
- connection.setAutoCommit(false);//开启事务
- connection.commit();//提交事务
- connection.rollback();//回滚事务
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* sql脚本:
* drop table if exists t_act;
* create table t_act(
* actno int,
* balance double(7,2) //注意:7表示有效数据的个数,2表示小数位的个数。
* );
* insert into t_act(actno,balance) values(111,20000);
* insert into t_act(actno,balance) values(222,0);
* commit;
* select * from t_act;
*
* 批量编辑快捷键:Alt+shift+insert
*/
public class JDBCTest11 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement ps = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode?serverTimezone=UTC","root","user");
//将自动提交机制改为手动提交
connection.setAutoCommit(false);//开启事务
//3.获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = connection.prepareStatement(sql);
//给?传值
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
//制造一个异常,这里坑定会出现空指针异常,
// 出现异常下边的代码将会不执行,直接进入catch语句块...
String s =null;
s.toString();
//给?传值
ps.setDouble(1,10000);
ps.setInt(2,222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
//程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据
connection.commit();//提交事务
} catch (Exception e) {
//回滚事务
if (connection != null){
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
e.printStackTrace();
}finally {
//6.释放资源
if (ps == null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection == null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}