十.JDBC(重点)
小部分内容非原创
参考博客
原文链接:https://blog.csdn.net/a951273629/article/details/107094285/
目录
10.1数据库驱动
我们的程序会通过数据库驱动 和数据库打交道
10.2JDBC
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。
对于开发人员只需要使用jdbc即可
默认就有的两个包:
java.sql
javax.sql
还需要到导入一个数据库驱动包
第一个JDBC程序
-- jdbc测试数据 (提前准备的测试数据)
CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;
USE jdbcStudy;
CREATE TABLE `users`(
id INT PRIMARY KEY,
NAME VARCHAR(40),
PASSWORD VARCHAR(40),
email VARCHAR(60),
birthday DATE
);
INSERT INTO `users`(id,NAME,PASSWORD,email,birthday)
VALUES(1,'zhansan','123456','zs@sina.com','1980-12-04'),
(2,'lisi','123456','lisi@sina.com','1981-12-04'),
(3,'wangwu','123456','wangwu@sina.com','1979-12-04')
2.导入jar包添加到项目中 mysql-connector-java-5.1.49.jar
package com.dong.lesson01;
import java.sql.*;
public class JdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动
Class.forName("com.mysql.cj.jdbc.Driver"); //固定写法 //反射调用默认进行类的初始化
//2.连接 用户 密码 和url
String url ="jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=ture&characterEncoding=utf8&useSSl=true&serverTimezone=GMT%2B8";
String username = "root";
String password ="123456";
//3.连接成功,数据库对象 //connection 代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
//4.执行sql的对象// statement 执行sql的
Statement statement = connection.createStatement();
//5.执行sql的对象 去执行sql 查看
String sql="SELECT * FROM users";
ResultSet resultSet = statement.executeQuery(sql); //返回一个结果集,结果集封装了所有查询结果
while (resultSet.next()){
System.out.println("id="+resultSet.getObject("id"));
System.out.println("name="+resultSet.getObject("NAME"));
System.out.println("password="+resultSet.getObject("PASSWORD"));
System.out.println("email="+resultSet.getObject("email"));
System.out.println("birthday="+resultSet.getObject("birthday"));
System.out.println("===================");
}
//6.释放连接
resultSet.close();
statement.close();
connection.close();
}
}
-
加载驱动
-
连接数据库DriverManager
-
获取执行sql的对象statement
-
获得返回的结果集
-
释放连接
以后可能会写成一个工具类 直接传进去sql语句就可以了
JDBC 对象解释
-
DriverManager
// 加载驱动
Class.forName(“com.mysql.jdbc.Driver”); //固定写法Connection connection = DriverManager.getConnection(url, username, password); //connection 代表数据库 数据库写的代码 这个对象都可以 connection.commit(); connection.setAutoCommit(); connection.rollback();
-
URL
//协议://主机名:端口号/数据库名?参数1&参数2
String url="jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8";
//mysql 默认3306
//oralce 默认1521 "jdbc:oralce:think:@localhost:1521:sid
- statement 执行sql的对象;prepareStatement 执行sql的对象
Statement statement = connection.createStatement();
PreparedStatement preparedStatement = connection.prepareStatement();(暂时没讲)
statement.executeQuery(); // 查询
statement.execute(); //执行任何sql //有判断的过程 相对来说效率会低一点
statement.executeUpdate(); //更新 插入 删除都用这个,返回一个受影响行数
statement.executeBatch()--多个sql执行
-
ResultSet 查询结果集,封装了所有的查询结果
//和数据库中的类型一一匹配
resultSet.getObject(); // 不知道列类型的情况下使用
// 如果知道列的类型就使用指定的类型
resultSet.getString();
resultSet.getDate();
resultSet.getInt();
resultSet.getDouble(); -
遍历指针
resultSet.beforeFirst(); //移动到最前面
resultSet.afterLast(); //移动到最后面
resultSet.next() // 移动到下一个 《-------最常用
resultSet.previous(); // 移动到上一个
resultSet.absolute(row); // 移动到指定行 -
释放资源
// 释放连接 十分耗费资源
resultSet.close();
statement.close();
connection.close();
10.3 statement对象
提取工具类
package com.dong.lesson02.utils;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
//提升作用域
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try {
//这里是通过类获取反射对象,然后获取反射对象的类加载器,调用类加载器的获取资源的方法。一步一步来的
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//加载只需一次 所有放在static下
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
// Connection connection = DriverManager.getConnection(url, username, password);
return DriverManager.getConnection(url, username, password);
}
//释放连接资源
public static void release(Connection connection, Statement statement, ResultSet resultSet){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement !=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
增删改 测试
package com.dong.lesson02.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestDelete {
public static void main(String[] args) {
Connection conn = null; //try 里面的外面拿不到 提升作用域,try里面的作用域在{}里
Statement st = null;
ResultSet re = null;
try {
conn= JdbcUtils.getConnection();
st=conn.createStatement();
String sql="DELETE FROM `users` WHERE id=4 ";
int i = st.executeUpdate(sql);
if (i>0){
System.out.println("删除成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,re);
}
}
}
package com.dong.lesson02.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestInsert {
public static void main(String[] args) {
Connection conn = null; //try 里面的外面拿不到 提升作用域,try里面的作用域在{}里
Statement st = null;
ResultSet re = null;
try {
conn= JdbcUtils.getConnection();
st=conn.createStatement();
String sql="INSERT INTO `users`(`id`,`NAME`,`PASSWORD`,`email`,`birthday`)" +
"VALUES(5,'dong','123465','264598@qq.com','2002-01-01')";
int i = st.executeUpdate(sql);
if (i>0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,re);
}
}
}
--改没有写 类似
查询 测试
package com.dong.lesson02.utils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestSelect {
public static void main(String[] args) {
Connection conn=null;
Statement st=null;
ResultSet re=null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = new String();
sql="select * from `users` where id=1";
re = st.executeQuery(sql);//结果集 ResultSet对象维护指向其当前数据行的游标。 最初,光标位于第一行之前
while (re.next()){
System.out.println(re.getString("NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,re);
}
}
}
10.4 SQL注入问题
比如登入业务中 用户名和密码匹配的登入成功
正常传进去正确的 用户名和密码
不正常 eg: 用户名输入“ ‘or’ 1=1 ” 密码输入 ‘or’ 1=1 所有用户被查出
sql存在漏洞 本质:sql会被拼接
public class SQLinject {
public static void main(String[] args) throws SQLException {
login("'' or 1=1","'' or 1=1");
}
public static void login(String username,String password) throws SQLException {
Connection connection=null;
Statement statement=null;
ResultSet rs=null;
try {
//SELECT * FROM `users` WHERE `NAME`='' or 1=1 AND `PASSWORD`='' or 1=1 ;
//获取连接
connection = jdbcutils.getConnection();
//获取sql对象
statement = connection.createStatement();
//sql
String sql="SELECT * FROM `users` WHERE `NAME`= "+"'"+username+"'"+" AND `PASSWORD`="+"'"+password+"'";
//查询获取返回集合
ResultSet query = statement.executeQuery(sql);
//遍历
while (query.next()){
System.out.println(query.getObject("NAME"));
System.out.println(query.getObject("PASSWORD"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
jdbcutils.release(connection,statement,rs);
}
}
}
10.5 PreparedStatement对象
它可以防止sql注入,并且效率高
增删改查
package com.dong.lesson03;
import com.dong.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TestInsert2 {
public static void main(String[] args) {
Connection conn =null;
PreparedStatement st =null;
try {
conn = JdbcUtils.getConnection();
//?是占位符 代替参数
String sql ="INSERT INTO `users`(`id`,`NAME`,`PASSWORD`,`email`,`birthday`) VALUES(?,?,?,?,?)";
st =conn.prepareStatement(sql);//预编译sql 先写sql 但是不执行
//手动给参数赋值
st.setInt(1,4);
st.setString(2,"abc");
st.setString(3,"123456");
st.setString(4,"264598@qq.com");
//sql.Date
//java.util.Date //获得时间戳 然后传给sql.Date
st.setDate(5,new java.sql.Date(new Date().getTime()));
//以上填充完毕 然后执行
int i = st.executeUpdate();
if (i>0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,null);
}
}
}
package com.dong.lesson03;
import com.dong.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestDelete2 {
public static void main(String[] args) {
Connection conn =null;
PreparedStatement st =null;
try {
conn = JdbcUtils.getConnection();
//?是占位符 代替参数
String sql ="delete from users where id=?";
st =conn.prepareStatement(sql);//预编译sql 先写sql 但是不执行
//手动给参数赋值
st.setInt(1,4);
//以上填充完毕 然后执行
int i = st.executeUpdate();
if (i>0){
System.out.println("删除成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,null);
}
}
}
package com.dong.lesson03;
import com.dong.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
public class TextUpdata {
public static void main(String[] args) {
Connection conn =null;
PreparedStatement st =null;
try {
conn = JdbcUtils.getConnection();
//?是占位符 代替参数
String sql ="update users set `name` = ? where id=? ;";
st =conn.prepareStatement(sql);//预编译sql 先写sql 但是不执行
//手动给参数赋值
st.setString(1,"东");
st.setInt(2,1);
//以上填充完毕 然后执行
int i = st.executeUpdate();
if (i>0){
System.out.println("更新成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,null);
}
}
}
package com.dong.lesson03;
import com.dong.lesson02.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class testSelect {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet re =null;
try {
conn = JdbcUtils.getConnection();
String sql="Select * from users where id =?";
st = conn.prepareStatement(sql);
st.setInt(1,1);
re = st.executeQuery();
while (re.next()){
System.out.println(re.getString("NAME"));
System.out.println(re.getString("email"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,re);
}
}
}
import com.dong.lesson02.utils.JdbcUtils;
import java.sql.*;
public class LoginTest {
public static void main(String[] args) throws SQLException {
login("张三","123456");
}
public static void login(String username,String password) throws SQLException {
Connection connection=null;
PreparedStatement statement=null;
ResultSet rs=null;
try {
//SELECT * FROM `users` WHERE `NAME`='' or 1=1 AND `PASSWORD`='' or 1=1 ;
//获取连接
connection = jdbcutils.getConnection();
//获取sql对象
//PreparedStatement 防止sql注入的本质,把传递进来的参数当做字符
// 假设其中存在转义字符,比如说' 会被忽略掉, '会被转义
String sql="SELECT * FROM `users` WHERE `NAME`=? AND `PASSWORD`= ?";
statement= connection.prepareStatement(sql);
statement.setObject(1,username);
statement.setObject(2,password);
rs=statement.executeQuery();
//sql
//查询获取返回集合
//遍历
while (rs.next()){
System.out.println("登陆成功");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
jdbcutils.release(connection,statement,rs);
}
}
}
10.6 IDEA 连接数据库
10.7 事务
事务
ACID原则
原子性: 要么全都成功,要么全都失败
一致性: 结果不变,总数一直
隔离性: 多个进程互不干扰
持久性: 一旦提交不可逆,持久化到数据库了
隔离性的问题:
脏读 :一个事务读取了另一个没有提交的事务
不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变
虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来的结果不一致(数据变多了)
代码实现
-
开启事务
-
一组业务完毕,提交事务
-
可以在catch 中显示回滚语句 默认失败回滚可以不写
public class TestTransaction{
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet re = null;
try {
conn = JdbcUtils.getConnection();
//关闭数据库的自动提交且开启事务 开启事务是自动的
conn.setAutoCommit(false);
String sql1 = " update account set money=money-100 where name ='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate(sql1);
// int x=1/0 测试报错
String sql2 = " update account set money=money+100 where name ='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate(sql2);
//业务完毕,提交事务
conn.commit();
System.out.println("操作成功");
} catch (SQLException e) {
try {
conn.rollback(); //如果失败会回滚
//这句话是默认就有的 可以不写
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,null);
}
}
}
10.9 数据库连接池
- 数据库连接–执行完毕–释放
连接 ,释放 十分浪费系统资源
池化技术:准备一些预先的资源,过来就连接预先准备好的
(服务员准备好了 过来就进行服务 直接扔sql)
假设 常用连接数是10
假设 有15个员工
**最小连接数: **10(常用连接数 可以是100,10等等)
**最大连接数:**15(业务最高重载上限)
超过15个的排队等待
等待超时:100ms
- 编写连接池 需要实现DataSource接口
开源数据源实现(DataSource接口的实现类)
DBCP
C3P0
Druid:阿里巴巴
1. DBCP
DBCP连接池配置
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&uesSSL=true
username=root
password=123456
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=utf8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_COMMITTED
从数据源中获取连接
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class jdbcutils_dbcp {
private static DataSource source=null;
static {
try {
InputStream in =jdbcutils_dbcp.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in);
//创建数据源 工厂模式--> 创建对象
source = BasicDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
//从数据源中获取连接
return source.getConnection();
}
//释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {
if(conn!=null) conn.close();
if(st!=null) st.close();
if(rs!=null) rs.close();
}
}
测试连接和查询
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class testdbcp {
public static void main(String[] args) throws SQLException {
Connection connection=null;
PreparedStatement statement=null;
ResultSet rs=null;
try {
//获取连接
connection = jdbcutils_dbcp.getConnection();
//sql
String sql="SELECT * from users WHERE id>?";
//预编译sql
statement= connection.prepareStatement(sql);
//设置参数
statement.setObject(1,1);
//执行sql
rs=statement.executeQuery();
//遍历结果
while (rs.next()){
System.out.println(rs.getObject("NAME"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
jdbcutils_dbcp.release(connection,statement,rs);
}
}
}
2. C3p0
DBCP连接池配置
c3p0-config.xml
xml 不用读取,加载会自动匹配
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!--
c3p0的缺省(默认)配置
如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource();"这样写就表示使用的是c3p0的缺省(默认)-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&uesSSL=true&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquiredIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
<!--
c3p0的命名配置
如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource("MySQL");"这样写就表示使用的是mysql的缺省(默认)-->
<named-config name="MySQL">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&characterEncoding=utf8&uesSSL=true&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="acquiredIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
c3p0连接数据库
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class jdbcutils_c3p0 {
private static ComboPooledDataSource source=null;
static {
try {
source= new ComboPooledDataSource("MySQL");
//创建数据源 工厂模式--> 创建对象
}catch (Exception e){
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
//从数据源中获取连接
return source.getConnection();
}
//释放连接资源
public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {
if(conn!=null) conn.close();
if(st!=null) st.close();
if(rs!=null) rs.close();
}
}
无论使用什么数据源 本质还是一样 ,DataSource 接口不会变