一、什么是jdbc?
数据库驱动:
将应用程序与数据库之间建立连接
jdbc:
为了简化开发人员的同意操作,提出了java操作数据库的规范,规范由具体的厂商去完成,开发人员只需要掌握jdbc的接口。
需要的包
javax.sql
com.mysql.jdbc.Driver(数据库驱动包)
二、连接数据库步骤:
1.加载驱动
class.forName(com.mysql.jdbc.Driver);
2.用户信息和url
//useUnicode=true 识别中文
//characterEncoding=utf8 字符集
//useSSL=true 安全连接
String url = "jdbc:mysql://local:3306/数据库名?+"
+"useUnicode=true&characterEncoding=utf8&useSSL=true";
String username = "root";
String passwd = "";
3.连接成功,数据库对象
//con 代表数据库;
Connection con = DriverManager.getConnection(url,root.passwd);
4.执行SQL的对象
//statement :执行SQL的对象
Statement statement = con.createStatement();
preperStatement statement = con.preperStatement(); //预处理
5.执行SQL的对象 去执行SQL,可能有返回值
String sql = "";
ResultSet rs = statement.executeQuery(sql);//查询
int rs = statement.executeUpdata(sql); //增删改
statement.execute(sql); //增删改查
//获取信息
whiel(rs.next()){
String name = rs.getString("字段名");
}
6.关闭连接
rs.close();
statement.close();
con.close();
JdbcUtil工具类源码:
package sdpei.jsj.comment;
import java.sql.*;
/**
* @Description 这个类是作为对数据库操作(增删改查)操作的标准工具类
* @author HuXuehao
*/
public class JdbcUtil {
private static final String URL = "jdbc:mysql://localhost:3306/gym?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "123123";
protected static Statement s = null; // 发送语句用的
protected static ResultSet rs = null; // 接收查询的结果集用的
/*
* 这里我还是解释一下ThreadLocal的是什么, 怎使用吧
* 算了,我还是给链接吧:https://www.cnblogs.com/dreamroute/p/5034726.html
*
*/
protected static ThreadLocal<Connection> tl = new ThreadLocal<>();
/**
* @Description 建立与数据 库的链接
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月16日下午6:03:54
* @return Connection 对象
*/
private static synchronized Connection getConnection() {
Connection conn = tl.get();
if (conn != null) // 这里的目的是为了让事务中的数据操作是使用的conn和开启事务的conn是同一个
return conn;
try {
Class.forName("com.mysql.cj.jdbc.Driver"); // 加载com.mysql.jdbc.Driver这个驱动类
conn = DriverManager.getConnection(URL, USER, PASSWORD); // 连接数据库
} catch (Exception e) {
e.printStackTrace();
}
return conn; // 连接成功后的对象
}
/**
* @Description 对数据库进行插入、更新、删除操作(只适合相对静态SQL语句,
* 其实我们也可以换一种方式实现动态语句,比如在java中我们可以这样做:
* int userId = 10;
* String sql = "select * from table where id="+userId;
* 上述的这种sql传到executeUpdate(String sql)是不可以改变的,但是但是在传之前userId是可以
* 变化的。)
*
* 注意:需要手动调用该类中的close()方法
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月16日下午6:05:08
* @param sql
* @return 操作成功就返回影响的行数,操作不成功就返回0
*/
public static int executeUpdate(String sql) {
int result = 0;
try {
s = getConnection().createStatement(); // 通过conn这个对象获取Statement对象s,s用来发送SQL语句
result = s.executeUpdate(sql); // 执行增删改语句,使用Statement是有参数的
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/**
* @Description 对数据库进行查询操作(同上)
* 注意:需要手动调用该类中的close()方法
*
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月16日下午6:05:38
* @param sql
* @return Result结果集
*/
public static ResultSet executeQuery(String sql) {
try {
s = getConnection().createStatement();
rs = s.executeQuery(sql); // 执行查询语句
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
/**
* @Description 执行动态的SQL语句 之所以可以实现动态语句,是因为它的参数中的SQL语句是要带“?”的
* 例如:
* String sql="insert into information(userName,userType)"+ "values(?,?)";
* PreparedStatement ps=null;
* ps.executePreparedStatement(String sql) //这里面是有参数的 ps.setString(1,"huxuehao"); ps.setString(2,18);
* int result = ps.executeUpdate(); //这里面是没有参数的
*
* 注意:需要手动调用该类中的close()方法
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月16日下午6:12:07
* @param sql
* @return preparedStatement对象
*/
public static PreparedStatement executePreparedStatement(String sql) {
PreparedStatement ps = null;
try {
ps = getConnection().prepareStatement(sql); // 只是获取到了发送动态语句的对象ps
} catch (Exception e) {
e.printStackTrace();
}
return ps;
}
/**
* @Description 关闭数据库连接对象
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月16日下午6:06:21
*/
public static void close() {
Connection conn = tl.get();
try {
if (rs != null)
rs.close();
if (s != null)
s.close();
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//--------------------------------------事务的手动提交以及事务的回滚----------------------------------------------
/**
* 下面是开启事务,事务提交,事务回滚的三个方法
* 用法:
* try{
* JdbcUtil.beginTransaction() //开启事务
* 对数据库的操作.....
* JdbcUtil.commitTransaction() //提交事务
* }catch{
* JdbcUtil.rollbackTransaction() //回滚事务
* }
* 上面的 getConnection()方法中有一判断conn是否不为空,当conn不为空时,直接返回已经创建的链接conn,保证对数据库的操作是使用
* 的是同一个conn,即保证在同一个事务中
*
* 注意:存在一个致命的问题,就是我在开启事务调用增删改查方法后,他就把conn给关了,那么我后面的提交事务和回滚事务就是无效操作
* 所以我在上面的方法中并没有调用上面定义的close()方法,这就意味着当你没有创建事务但是调用了操作数据库的方法时,我们需要在手动调用该工具类中的close()方法
* @Description 开启事务
* @author HuXuehao Email:1938667362@qq.com
* @version
* @return
* @date 2020年10月29日下午1:43:13
*/
public static void beginTransaction() {
Connection conn = tl.get(); //获取自己的链接,接下来判断其是否为空
if (conn == null) throw new RuntimeException("你说你是不是手欠,已经开启了事务,咱就不要重复操作了");
conn = getConnection(); //创建连接
try {
conn.setAutoCommit(false); // 事务设置为手动提交
tl.set(conn); //把当前线程的链接保存起来,为了给下面的使用
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* @Description 事务的提交
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月29日下午1:43:33
*/
public static void commitTransaction() {
Connection conn = tl.get();
if (conn == null) throw new RuntimeException("还没开启事务呢,你找啥急去提交事务");
try {
conn.commit(); //提交事务
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
tl.remove(); //移除当前连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* @Description 事务的回滚
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月29日下午1:43:56
*/
public static void rollbackTransaction() {
Connection conn = tl.get();
if (conn == null) throw new RuntimeException("还没开启事务呢,你找啥急去回滚事务");
try {
conn.rollback(); //回滚事务
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
conn.close();
tl.remove(); //移除当前连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* @Description 事务回滚(弃用)
* @author HuXuehao Email:1938667362@qq.com
* @version
* @date 2020年10月16日下午6:06:08
*/
/*
* public static void rollback() { try { getConnection().rollback(); //回滚 }
* catch (SQLException e) { e.printStackTrace(); } }
*/
}
三、sql注入问题:
SQL存在漏洞,导致会被攻击 (sql存在拼接 [or])
通过SQL语句,实现无账号登录,甚至篡改数据库。
举例:
加入后台的SQL语句如下:
String sql = “select * from user where username=’ “+userName+” ’ and password=’”+password+" '";
用户输入内容如下:
userName输入: or 1=1 –
password输入:00000 (随便输入)
那么原本的SQL语句就变成了:
select * from user where username=’’ or 1=1 - - and password =‘00000’
该语句很显然永远成立
username=’’ or 1=1为真,- - and password ='00000’很明显是一条注释
prepareStatement对象(预处理)可以防止SQL注入并且效率更高
//本质:将传递进来的参数当做字符,其中存在引号,则会转译
PrepareStatement pst = con.prepareStatement("....?,?,?");
pst.setInt(1,1); //第一1代表上面的第一个“?”,第二个1代表当前“?"的具体内容
pst.setString(2,""); //同上
pst.setData(3, java.sql.Data(new Data().getTime())); //同上
pst.executeQuery(); //执行查询操作
pst.executeUpdate(); //执行增、删、改操作
//释放资源.
con.close();
pst.close();
四、事务
作用:
在开启事务和提交事务期间程序发生异常,那么该期间的所有SQL执行都会被回滚
ACID原则:
原子性:要么都完成,要么都不完成
一致性:总数不变
隔离性:多进行互不干预
持久性:一单提交,不可回滚
隔离性的问题:
脏读:一个事务读取了另一个没有提交的数据
不可重复读:在一个事务内,重复读取表中的数据,表数据发生了改变
虚读(玄读):在一个事务内,读取到别人插入的数据,导致前后读取出来的数据不一致
例子
------------------【转账】------------------
前提:假设已经连接好了获取了con对象
try{
//1. 关闭自动提交,并开启事务
con.setAutoCommit(false)
2.其他SQL操作.....
//3.提交事务
con.commit()
}catch(Exception e){
try{
//4.回滚
con.rollback();
}catch(Exception e){
e.printStackTrance();
}
}finally{
关闭资源
}
五、数据库连接池
池化技术:
预先准备一些资源,需要就直接用。
存在【最大连接数】
存在【最小连接数】
存在【等待超时】
编写连接池:
本质上就是实现接口
常用的开源数据源
1、DBCP
2、C3P0
3、Druid(阿里巴巴)
【DBCP的使用】(需要commons-dacp.jar 和 commons-pool.jar)
dbcp.properties文件
########DBCP配置文件##########
#驱动名
driverClassName=com.mysql.jdbc.Driver
#url
url=jdbc:mysql://local:3306/数据库名?useUnicode=true&characterEncoding=utf8&useSSL=true
#用户名
username=user
#密码
password=123456
#初试连接数
initialSize=50
#最大活跃数
maxTotal=35
#最大idle数
maxIdle=20
#最小idle数
minIdle=10
#最长等待时间(毫秒)
maxWaitMillis=1000
#程序中的连接不使用后是否被连接池回收(该版本要使用removeAbandonedOnMaintenance和removeAbandonedOnBorrow)
#removeAbandoned=true
removeAbandonedOnMaintenance=true
removeAbandonedOnBorrow=true
#连接在所指定的秒数内未使用才会被删除(秒)(为配合测试程序才配置为1秒)
removeAbandonedTimeout=1
java代码:
//1.通过类加载器进行获取properties文件流
InputStream in = 共有类名.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
//2.创建Properties类
Properties properties = new Properties();
//3.加载流文件
properties.load(in);
//4.创建数据源(工程模式———>创建)
DataSource datasource = BasicDataSourceFactory.createDataSource(properties);
//5.创建连接
Connection = con datasource.getConnection()
//连接已经穿件好了,剩下的就是增删改查操作