JDBC概述
基本介绍
1.JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
2.Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
3.JDBC的基本原理[ 重要 ]
4.模拟JDBC com.lmbc.myjdbc
- JDBC
1.如果Java直接访问数据库(示意图)
2.JDBC带来的好处(示意图)
3.说明:JDBC是Java提供一套用于数据库操作的APIzz,Java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同的实现。
JDBC快速入门
-
JDBC程序编写步骤
- 注册驱动 - 加载Driver类
- 获取连接 - 得到Connection
- 执行增删改查 - 发送SQL给mysql执行
- 释放资源 -关闭相关连接
package com.lmrxx.jdbc;
import com.mysql.cj.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
//在项目下创建一个文件夹比如libs
//将mysql.jar 拷贝到该目录下,点击
//1.注册驱动
Driver driver=new Driver();
//2.得到连接
//jdbc:mysql://规定好表示协议,通过jdbc的连接方式连接mysql
//localhost 主机,可以是IP地址
//3306 表示mysql监听的端口
// customer_manage连接到mysql dbms的哪个数据库
//mysql 连接本质就是前面学过的socket连接
String url="jdbc:mysql://localhost:3306/customer_manage?characterEncoding=UTF-8";
// 将用户名和密码放入Properties对象
Properties properties=new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
//网络连接
Connection conect =driver.connect(url,properties);
//3.执行sql
String sql="delete from actor where id=1";
//statement 用于执行静态sql语句并返回其生成的结果对象
Statement statement=conect.createStatement();
int rows=statement.executeUpdate(sql);//如果是dml语句,返回的就是影响行数
System.out.println(rows>0? "成功" : "失败");
//4.关闭连接资源
statement.close();
conect.close();
}
}
获取数据库连接的5种方式
方式一:
//方式一
@Test
public void connect01() throws SQLException{
Driver driver=new Driver(); //创建driver对象
String url="jdbc:mysql://localhost:3306/customer_manage";
Properties properties=new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
Connection connect=driver.connect(url,properties);
System.out.println(connect);
}
方式2
//方式二
@Test
public void connect02 () throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
//使用反射加载Driver类,动态加载,更加灵活,减少依赖
Class<?> aClass =Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver=(Driver) aClass.newInstance();
String url="jdbc:mysql://localhost:3306/customer_manage";
//将用户名和密码放入到properties对象
Properties properties=new Properties();
//说明user和password是规定好,后面的值根据实际情况写
properties.setProperty("user","root");
properties.setProperty("password","123456");
Connection connect=driver.connect(url,properties);
System.out.println("方式2="+connect);
}
方式三
//方式三 使用DriverManager 替代driver进行统一管理
@Test
public void connect03() throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
//使用反射加载Driver类
Class<?> aClass =Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver=(Driver) aClass.newInstance();
String url="jdbc:mysql://localhost:3306/customer_manage";
String user="root";
String password="123456";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式:"+connection);
}
方式四:
1.mysql驱动5.1.6 可以无需Class.forName(" com.mysql.Driver");
2.从jdk1.5 以后使用了jdbc4 ,不需要显式调用class.firName()注册驱动而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册
3.建议还是写上Class.forName("com.mysql.cj.jdbc.Driver")
//方式四:使用class.forName 自动完成注册驱动,简化代码
@Test
public void connect04() throws ClassNotFoundException, SQLException {
//使用反射加载了Driver
//在加载Driver类时,完成注册
/*
源码 1.静态代码块,在类加载时,会执行一次。
2.DirverManager.registerDriver(new Driver())
3.因此注册driver工作已经完成的
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
// Class.forName("com.mysql.cj.jdbc.Driver");
//创建url和 user 和 password
String url="jdbc:mysql://localhost:3306/customer_manage";
String user="root";
String password="123456";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第四种方式~:"+connection);
}
方式五:
mysql.properties文件
user=root password=123456 url=jdbc:mysql://localhost:3306/customer_manage driver=com.mysql.cj.jdbc.Driver
//方式5,在方式四的基础上改进,增加配置文件,让连接mysql更加灵活
@Test
public void connect05() throws IOException, ClassNotFoundException, SQLException {
//通过Properties对象获取配置文件信息
Properties properties=new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取值
String user=properties.getProperty("user");
String password=properties.getProperty("password");
String url=properties.getProperty("url");
String driver=properties.getProperty("driver");
Class.forName(driver);
Connection connection=DriverManager.getConnection(url,user,password);
System.out.println("方式五:"+connection);
}
ResultSet 结果集
- 基本介绍
1.表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
2.ResultSet对象保持一个光标指向当前的数据行。最初,光标位于第一行之前
3.next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集。
Interface Statement
用于执行静态SQL语句并返回生成的结果的对象
PreparedStatement(预处理)
- 预处理好处
1.不再使用+拼接sql语句,减少语法错误
2.有效的解决了sql注入问题
3.大大减少了编译次数,效率较高
不能有未知符
jdbc API
JDBCUtils类
package com.lmrxx.jdbc.utils;
import jdk.internal.dynalink.beans.StaticClass;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* 这是一个工具类,完成mysql连接和关闭资源
*/
public class JDBCUtils {
//定义相关的属性,因为只需要一 份,我们做出static
private static String user;
private static String password;
private static String url;
private static String driver;
//在static模块去初始化
static {
Properties properties=new Properties();
try {
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的数据值
user= properties.getProperty("user");
password= properties.getProperty("password");
url= properties.getProperty("url");
driver= properties.getProperty("driver");
} catch (IOException e) {
//在实际开发中,我们可以这样处理
//1.将编译异常转成运行异常
//2.调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便。
throw new RuntimeException(e);
}
}
//连接数据库,返回Connection
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
//1.将编译异常转成运行异常
//2.调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便。
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1.ResultSet 结果集
2.Statement 或者 PreparedStatement
3.Connection
*/
public static void close(ResultSet set,Statement statement,Connection connection){
//判断是否为null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e){
throw new RuntimeException(e);
}
}
}
使用该类
package com.lmrxx.jdbc.utils;
import com.lmrxx.jdbc.PreparedStatement_.PreparedStatement_;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
*该类演示如何使用JDBCUtils工具类,完成dml和selec
*/
public class JDBCUtils_Use {
public static void main(String[] args) {
}
@Test
public void tesSelect(){
//1.得到连接
Connection connection=null;
//2.组织一个sql
String sql="select * from actor where id=?";
ResultSet set=null;
PreparedStatement preparedStatement=null;
//3.创建PreparedStatement对象
try {
connection=JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,2);
//执行,得到结果集
set= preparedStatement.executeQuery();
//遍历该结果集
while (set.next()){
int id=set.getInt("id");
String name=set.getString("name");
String sex=set.getString("sex");
String phone=set.getString("phone");
System.out.println(id+"\t"+name+"\t"+sex+"\t"+phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null,preparedStatement,connection);
}
}
@Test
public void tesDML(){
//1.得到连接
Connection connection=null;
//2.组织一个sql
// String sql="update actor set name =? where id= ?";
//测试delete语句和insert语句
String sql1="delete from actor where id=?";
PreparedStatement preparedStatement=null;
//3.创建PreparedStatement对象
try {
connection=JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql1);
//给占位符赋值
// preparedStatement.setString(1,"周星驰");
preparedStatement.setInt(1,2);
//执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null,preparedStatement,connection);
}
}
}
事务
- 基本介绍
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务,每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务。
- 调用Connection的setAutoCommit(false)可以取消自动提交事务
- 在所有的SQL语句都 成功执行后,调用Connection的commit();方法提交事务
- 在其中某个操作失败或出现异常时,调用Connection的rollback();方法回滚事务
应用实例(有事务与没有事务的对比)
package com.lmrxx.jdbc.transaction_;
import com.lmrxx.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Transaction_ {
@Test
public void noTransaction(){
//操作转账的业务
//1.得到连接
Connection connection=null;
//2.组织一个sql
String sql="update account set balance=balance-100 where id=1";
String sql2="update account set balance=balance+100 where id=2";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement=null;
try {
connection= JDBCUtils.getConnection();//在默认情况下,connection是自动提交的
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();//执行第一条sql
// int i=1/0;//抛出异常
preparedStatement= connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
//给占位符赋值
// preparedStatement.setString(1,"周星驰");
//preparedStatement.setInt(1,2);
//执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(null,preparedStatement,connection);
}
}
//事务来解决
@Test
public void noTransaction2(){
//操作转账的业务
//1.得到连接
Connection connection=null;
//2.组织一个sql
String sql="update account set balance=balance-100 where id=1";
String sql2="update account set balance=balance+100 where id=2";
//3.创建PreparedStatement对象
PreparedStatement preparedStatement=null;
try {
connection= JDBCUtils.getConnection();//在默认情况下,connection是自动提交的
//将connection设置为不自动提交
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();//执行第一条sql
//int i=1/0;//抛出异常
preparedStatement= connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
//这里提交事务
connection.commit();
} catch (SQLException e) {
//这里我们可以进行回滚即撤销执行的sql,默认回滚到事务开始的状态
System.out.println("执行发生了异常,撤销执行的sql");
try {
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
e.printStackTrace();
} finally {
JDBCUtils.close(null,preparedStatement,connection);
}
}
}
批处理
基本介绍:
1. 当需要成批插入或更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
2. JDBC的批处理语句包括下面方法:
addBatch():添加需要批量处理的SQL语句或参数
executeBatch():执行批量处理语句;
clearBatch():清空批处理包的语句
3. JDBC连接MySQL时,如果要使用批处理功能,请在url中加参数?rewruteBatchedStatements=true
4. 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高。
package com.lmrxx.jdbc.batch;
import com.lmrxx.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 演示java的批处理
*/
public class Batch_ {
@Test
public void noBatch() throws SQLException {
Connection connection= JDBCUtils.getConnection();
String sql="insert into admin values (null,?,?)";
PreparedStatement preparedStatement=connection.prepareStatement(sql);
System.out.println("开始执行");
long start=System.currentTimeMillis();//开始时间
for (int i=0;i<5000;i++){
preparedStatement.setString(1,"jack"+i);
preparedStatement.setString(2,"666");
preparedStatement.executeUpdate();
}
long end=System.currentTimeMillis();//结束时间
System.out.println("传统的方式 耗时="+(end-start));//耗时=11741
JDBCUtils.close(null,preparedStatement,connection);
}
@Test
public void batch() throws SQLException {
Connection connection= JDBCUtils.getConnection();
String sql="insert into admin2 values (null,?,?)";
PreparedStatement preparedStatement=connection.prepareStatement(sql);
System.out.println("开始执行");
long start=System.currentTimeMillis();//开始时间
for (int i=0;i<5000;i++){
preparedStatement.setString(1,"jack"+i);
preparedStatement.setString(2,"666");
//将sql语句加入到批处理包中 -> 看源码
preparedStatement.addBatch();
//当有1000条记录时,在批量执行
if((i+1) % 1000==0){//满1000条sql
preparedStatement.executeBatch();
//清空一把
preparedStatement.clearBatch();
}
}
long end=System.currentTimeMillis();//结束时间
System.out.println("传统的方式 耗时="+(end-start));//耗时=145
JDBCUtils.close(null,preparedStatement,connection);
}
}
数据库连接池
-
传统获取Connection问题分析
- 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址、用户名和密码(0.05s~1s时间) 需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用后都要断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术。(connection pool)
-
数据连接池基本介绍
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
-
数据库连接池种类
- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
- C3P0 数据库连接池,速度相对较慢,稳定性不错(hibernate,spring)
- DBCP数据库连接池,速度相对c3p0较快,但不稳定
- Proxool数据库连接池,有监控连接池状态的功能,稳定性c3p0差一点
- BoneCP数据库连接池,速度快
- DRuid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点集一身的数据库连接池
- 将JDBCUtils工具类改成Druid(德鲁伊)实现
通过德鲁伊数据库连接池获取对象。
-
apache-DBUtils
数据库在返回resultSet时
问题:1.结果集和connection是关联的,即如果关闭连接,就不能使用结果集
2.结果集不利于数据管理(只能用一次)。
3.使用返回信息也不方便。
将结果集记录,封装到ArrayList<Actor>中
Java类Actor ==> JavaBean,PoJO,Domain
class Actor{
属性,id,name,sex,borndate,phone
},一个Actor对象对应actor记录。
一个Actor对象,对应一条actor表记录,actor对象放入到ArrayList集合
- 基本介绍
- common-dbutils是Apache组织提供给的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。
- DbUtils类
- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、查、改、批处理
- 使用QueryRunner类实现查询。
- ResultSetHandler接口,该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式 。
DAO和增删改查通用方法-BasicDao
apache-dbutils +Druid 简化了JDBC开发,但还有不足:
- SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行 增删改查
- 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个java类完成
- 引出=》 BasicDAO画出示意图
- 基本说明
- DAO:data access object 数据访问对象
- 这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
- 在BasicDao基础上,实现一张表 对应一个Dao,更好的完成功能,比如Customer表-Customer.java 类(javabean)-CustomerDao.java