目录
- JDBC简介
- 数据库驱动
- JDBC
- 第一个JDBC程序
- JDBC中各对象详解
- statement对象
- 包装成工具类
- SQL注入问题
- PreparedStatement对象
- 使用IDEA连接数据库
- JDBC操作事务
- 数据库连接池
- DBCP
- 需要用到的JAR包
- DBCP配置文件
- 工具类
- 测试代码
- C3P0
- 需要用到的JAR包
- C3P0配置文件
- 工具类
- 测试代码
- 结论
JDBC简介
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
数据库驱动
什么是数据库驱动?类似声卡驱动、显卡驱动
我们的程序会通过数据库驱动,和数据库打交道。
JDBC
SUN 公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库的)规范,JDBC。
这些规范的实现由具体的厂商去做。
对于开发人员来说,我们只需要掌握JDBC的接口操作即可。
java.sql
javax.sql
还需要导入数据库驱动包
第一个JDBC程序
1.创建测试数据库
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.创建一个普通项目
3.导入数据库驱动
pom.xml
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency>
4.编写测试代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;public class JdbcDemo {public static void main(String[] args) throws Exception {//1. 加载驱动Class.forName("com.mysql.cj.jdbc.Driver");//固定写法 jdbc驱动版本8.0之前为com.mysql.jdbc.Driver//2. 用户信息和url//useUnicode=true&characterEncoding=utf8&&useSSL=true//useUnicode=true支持中文编码;characterEncoding=utf8中文字符集设置为utf-8;useSSL=true使用安全链接//jdbc驱动版本8.0之后要添加serverTimezone=UTCString url ="jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true";String name = "root";String password = "123456";//3. 连接成功,返回数据库对象 connection代表数据库Connection connection= DriverManager.getConnection(url,name,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("姓名="+resultSet.getObject("NAME"));System.out.println("密码="+resultSet.getObject("PASSWORD"));System.out.println("邮箱="+resultSet.getObject("email"));System.out.println("生日="+resultSet.getObject("birthday"));System.out.println("==================================");}//6. 释放连接(从后往前释放)resultSet.close();statement.close();connection.close();}
}
JDBC中各对象详解
步骤总结:
1.加载驱动
2.连接数据库 DriverManager
3.获取执行SQL的对象 Statement
4.获得返回的结果集
5.释放连接
DriverManager
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");//固定写法
Connection connection= DriverManager.getConnection(url,name,password);//connection代表数据库
//数据库设置自动提交
//事务提交
//事务回滚
connection.rollback();
connection.commit();
connection.setAutoCommit();
URL
String url ="jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true";//mysql 默认端口3306
//协议://主机地址:端口号/数据库名?参数1&参数2&参数3...//Oracle 1521
//jdbc:oralce:thin:@localhost:1521:sid
statement执行SQL的对象 PrepareStatement 执行SQL的对象
String sql="SELECT * FROM users";//编写Sqlstatement.executeQuery();//查询操作,返回ResultSet
statement.execute();//执行任何SQL
statement.executeUpdate();//更新,插入,删除,返回一个受影响的行数
ResultSet 查询的结果集,封装了所有的查询结果
获得指定的数据类型
ResultSet resultSet = statement.executeQuery(sql);//返回的结果集,结果集中封装了我们全部查询的结果resultSet.getObject();//在不知道列类型下使用//如果知道则使用指定类型
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();
...
遍历,指针
resultSet.next(); //移动到下一个resultSet.afterLast();//移动到最后resultSet.beforeFirst();//移动到最前面resultSet.previous();//移动到前一个resultSet.absolute(row);//移动到指定行
释放内存
//6. 释放连接(从后往前释放) resultSet.close();statement.close();connection.close();//connection连接最占用资源
statement对象
JDBC中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。
Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sq|语句, executeUpdate执行完后, 将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。
Statement.executeQuery方法用于向数据库发生查询语句,executeQuery方法返回代表查询结果的ResultSet对象。
CRUD操作-create
使用executeUpdate(String sql)方法完成数据添加操作,示例操作:
Statement statement = connection.createStatement();String sql = "insert into user(...) values(...)";int num = statement.executeUpdate(sql);if(num>0){System.out.println("插入成功");}
CRUD操作-delete
使用executeUpdate(String sql)方法完成数据删除操作,示例操作:
Statement statement = connection.createStatement();String sql = "delete from user where id =1";int num = statement.executeUpdate(sql);if(num>0){System.out.println("删除成功");}
CURD操作-update
使用executeUpdate(String sql)方法完成数据修改操作,示例操作:
Statement statement = connection.createStatement();String sql = "update user set name ='' where name = ''";int num = statement.executeUpdate(sql);if(num>0){System.out.println("修改成功");}
CURD操作-read
使用executeUpdate(String sql)方法完成数据查询操作,示例操作:
Statement statement = connection.createStatement();String sql = "select * from user where id =1";ResultSet rs= statement.executeQuery(sql);if(rs.next()){System.out.println("");}
包装成工具类
首先配置文件db.properties写好信息
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true
username=root
password=123456
工具类
import java.io.FileInputStream;
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{FileInputStream in = new FileInputStream("src/main/resources/db.properties");//InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");// 通过类加载器读取对应的资源,注意此时db.properties要放在resources文件夹里面Properties properties = new Properties();properties.load(in);driver=properties.getProperty("driver");url=properties.getProperty("url");username=properties.getProperty("username");password=properties.getProperty("password");//1.驱动只用加载一次Class.forName(driver);} catch (Exception e) {e.printStackTrace();}}//2.获取连接public static Connection getConnection() throws Exception{return DriverManager.getConnection(url, username, password);}//3.释放资源public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {if (rs != null) {rs.close();}if (st != null){st.close();}if(conn != null){conn.close();}}
}
使用工具类(插入数据为例)(exectueUpdate
可以进行增删改三个操作)
import com.cheng.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;import static com.cheng.utils.JdbcUtils.getConnection;public class TestInsert {public static void main(String[] args){Connection conn =null;Statement st = null;ResultSet rs =null;try {conn = getConnection();//获取连接st = conn.createStatement();//获取SQL执行对象String sql = "INSERT INTO users(id,`NAME`,`PASSWORD`,`email`,`birthday`)" +"VALUES(5,'你好','123456','233223@qq.com','2020-01-01')";int i = st.executeUpdate(sql);if(i>0){System.out.println("插入成功");}JdbcUtils.release(conn,st,rs);} catch (Exception e) {e.printStackTrace();}}}
使用工具类(查询数据为例)
import com.cheng.utils.JdbcUtils;import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;import static com.cheng.utils.JdbcUtils.getConnection;public class TestQuery {public static void main(String[] args) throws SQLException {Connection conn =null;Statement st = null;ResultSet rs =null;try {conn = getConnection();//获取连接st = conn.createStatement();//获取SQL执行对象String sql = "select * from users";rs=st.executeQuery(sql);//查询完毕返回结果集while (rs.next()){System.out.println(rs.getString("NAME"));}JdbcUtils.release(conn,st,rs);} catch (Exception e) {e.printStackTrace();}finally {JdbcUtils.release(conn,st,rs);}}
}
SQL注入问题
sql存在漏洞,会被攻击导致数据泄露 SQL会被拼接 or
原理,例如密码查询正常是要匹配密码,但是加个or 1=1后就会认为密码查询通过,因为or是左右两边有一个成立都认为是成立,又因为1=1恒成立,所以服务器被欺骗,认为密码查询通过
示例
import com.cheng.utils.JdbcUtils;import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;import static com.cheng.utils.JdbcUtils.getConnection;public class SqlInjection {public static void main(String[] args) {// 正常查询login("你好","123456");System.out.println("=================");// SQL注入login("' OR '1=1","' OR '1=1");}public static void login(String name,String password){Connection conn =null;Statement st = null;ResultSet rs =null;try {conn = getConnection();//获取连接st = conn.createStatement();//获取SQL执行对象// 正常查询:SELECT * FROM users WHERE `NAME`='你好' AND `PASSWORD`='123456'// SQL注入:SELECT * FROM users WHERE `NAME`='' OR '1=1' AND `PASSWORD`='' OR '1=1'// 即为SELECT * FROM users 这样就能获取整个表所有信息String sql = "SELECT * FROM users WHERE `NAME`='"+ name +"' AND `PASSWORD`='"+ password +"'" ;rs=st.executeQuery(sql);//查询完毕返回结果集while (rs.next()){System.out.println(rs.getString("NAME"));}JdbcUtils.release(conn,st,rs);} catch (Exception e) {e.printStackTrace();}finally {try {JdbcUtils.release(conn,st,rs);} catch (SQLException throwables) {throwables.printStackTrace();}}}
}
PreparedStatement对象
PreparedStatement 可以防止SQL注入 ,效率更高。
public class Test {public static void main(String[] args) {Connection connection= null;PreparedStatement pstm=null;try {connection = JdbcUtils.getConnection();//PreparedStatement防止SQL注入本质:把传递进来的参数当作字符//假设其中存在转义字符,比如说引号,会被直接转义// 区别:// 1.使用问号占位符代替参数String sql = "insert into users(id, `NAME`, `PASSWORD`, `email`,`birthday`) values(?, ?, ?, ?, ?)";// 2.预编译sql,先写sql然后不执行pstm = connection.prepareStatement(sql);// 手动赋值pstm.setInt(1,4);// 1代表第一个问号pstm.setString(2,"张三");pstm.setString(3,"123123");pstm.setString(4,"123333@qq.com");pstm.setDate(5,new java.sql.Date(new Date().getTime()));// 注意要转换成sql的Date//执行int i = pstm.executeUpdate();if (i>0){System.out.println("插入成功");}} catch (Exception e) {e.printStackTrace();}finally {try {JdbcUtils.release(connection,pstm,null);} catch (SQLException throwables) {throwables.printStackTrace();}}}
}
输出:
你好
=================
zhansan
lisi
wangwu
张三
你好
防止SQL注入本质,传递字符 带有“ ”,转义字符会被转义
改进示例:
import com.cheng.utils.JdbcUtils;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class Test {public static void main(String[] args) {// 正常查询login("你好","123456");System.out.println("=================");// SQL注入login("'' OR 1=1","'' OR 1=1");}public static void login(String username,String password) {Connection connection = null;PreparedStatement pstm = null;ResultSet rs = null;try {connection = JdbcUtils.getConnection();// 区别:// 1.使用问号占位符代替参数String sql = "SELECT * FROM users WHERE `NAME`=? AND `PASSWORD`=?";// 2.预编译sql,先写sql然后不执行pstm = connection.prepareStatement(sql);// 手动赋值pstm.setString(1, username);// 1代表第一个问号pstm.setString(2, password);rs = pstm.executeQuery();//注意!!!!这里不像st.executeQuery(sql);那样,括号里不要写sql,否则报错。因为PreparedStatement是预编译的,PreparedStatement对象中已包含SQL查询//查询完毕返回结果集while (rs.next()) {System.out.println(rs.getString("NAME"));}JdbcUtils.release(connection, pstm, rs);} catch (Exception e) {e.printStackTrace();} finally {try {JdbcUtils.release(connection, pstm, rs);} catch (SQLException throwables) {throwables.printStackTrace();}}}
}
注意!!!!rs = pstm.executeQuery();
不像rs = st.executeQuery(sql);
那样,括号里不要写sql,否则报错。因为PreparedStatement是预编译的,PreparedStatement对象中已包含SQL查询
输出:(SQL注入失效)
你好
=================
使用IDEA连接数据库
连接数据库
添加表
修改数据表:
双击数据表,修改,提交
编写查询
JDBC操作事务
要么都成功,要么都失败
ACID原则
- 原子性:要么全部完成,要么都不完成
- 一致性:结果总数不变
- 隔离性:多个进程互不干扰
- 持久性:一旦提交不可逆,持久化到数据库了
隔离性的问题:
- 脏读: 一个事务读取了另一个没有提交的事务
- 不可重复读:在同一个事务内,重复读取表中的数据,表发生了改变
- 虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来的结果不一致
代码实现
- 开启事务conn.setAutoCommit(false);
- 一组业务执行完毕,提交事务
- 可以在catch语句中显示的定义回滚,但是默认失败会回滚
创建示例数据库:
public class JdbcTransaction{public static void main(String[] args) {Connection conn =null;PreparedStatement ps = null;ResultSet rs = null;try {conn = JdbcUtils.getConnection();//关闭数据库的自动提交功能, 开启事务conn.setAutoCommit(false);//自动开启事务String sql = "update account set money = money-100 where id = 1";ps =conn.prepareStatement(sql);ps.executeUpdate();String sql2 = "update account set money = money-100 where id = 2";ps=conn.prepareStatement(sql2);ps.executeUpdate();//业务完毕,提交事务conn.commit();System.out.println("操作成功");} catch (Exception e) {try {//如果失败,则默认回滚conn.rollback();//如果失败,回滚System.out.println("失败");} catch (SQLException throwables) {throwables.printStackTrace();}e.printStackTrace();}finally {try {JdbcUtils.release(conn,ps,rs);} catch (SQLException throwables) {throwables.printStackTrace();}}}
}
数据库连接池
传统连接方式:数据库连接–执行完毕–释放
服务器频繁重复连接–释放会十分浪费资源
池化技术:准备一些预先的资源,过来就连接预先准备好的
- 常用连接数:10
- 最小连接数:10(一般跟常用连接数相等)
- 最大连接数 : 100(业务最高承载上限)
- 若大于最大连接数,排队等待,
- 等待超时:100ms(100ms后等待的链接断开连接)
编写连接池:实现一个接口DateSource
常见的开源数据源实现(拿来即用)
以下这些是市面上常见的DateSource接口的实现类
- DBCP
- C3P0
- Druid: 阿里巴巴
使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了
DBCP
需要用到的JAR包
(使用maven导入)
pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-dbcp2</artifactId><version>2.7.0</version></dependency>
DBCP配置文件
dbcp-config.properties
#连接设置
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=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_UNCOMMITTED
工具类
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;public class Dbcp {private static DataSource dataSource = null;static {try{FileInputStream in = new FileInputStream("src/main/resources/dbcpconfig.properties");Properties properties = new Properties();properties.load(in);// 1.创建数据源 工厂模式——>创建dataSource = BasicDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();}}// 2.获取连接public static Connection getConnection() throws Exception{return dataSource.getConnection(); // 从数据源中获取连接}// 3.释放资源public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {if (rs != null) {rs.close();}if (st != null){st.close();}if(conn != null){conn.close();}}
}
测试代码
import com.cheng.utils.Dbcp;
import java.sql.*;public class DbcpTest {public static void main(String[] args) throws SQLException {Connection conn =null;PreparedStatement ps = null;ResultSet rs =null;try {conn = Dbcp.getConnection();String sql = "select * from users";ps=conn.prepareStatement(sql);rs=ps.executeQuery();while (rs.next()){System.out.println(rs.getString("NAME"));}} catch (Exception e) {e.printStackTrace();}finally {Dbcp.release(conn,ps,rs);}}
}
C3P0
需要用到的JAR包
(使用maven导入)
pom.xml
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.4</version></dependency>
C3P0配置文件
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config><!-- 如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource();"这样写就表示使用的是c3p0的缺省(默认)--><!-- c3p0的缺省(默认)配置--><default-config><property name="driverClass">com.mysql.cj.jdbc.Driver</property><property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&&useSSL=true</property><property name="user">root</property><property name="password">123456</property><property name="acquireIncrement">5</property><property name="initialPoolSize">10</property><property name="minPoolSize">5</property><property name="maxPoolSize">20</property></default-config><!-- 如果在代码中"ComboPooledDataSource ds=new ComboPooledDataSource("s1");"这样写就表示使用的是s1配置参数)--><named-config name="s1"></named-config></c3p0-config>
另外,可以使用代码配置参数
// 可以使用代码配置参数dataSource = new ComboPooledDataSource();dataSource.setDriverClass("");dataSource.setJdbcUrl("");dataSource.setUser("");dataSource.setPassword("");dataSource.setMaxPoolSize(100);
工具类
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;public class C3p0 {private static ComboPooledDataSource dataSource = null;static {try{// 创建数据源dataSource = new ComboPooledDataSource();} catch (Exception e) {e.printStackTrace();}}// 2.获取连接 因为getConnection()是接口的方法 所以下面跟DBCP一样都不用变public static Connection getConnection() throws Exception{return dataSource.getConnection(); // 从数据源中获取连接}// 3.释放资源public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {if (rs != null) {rs.close();}if (st != null){st.close();}if(conn != null){conn.close();}}
}
测试代码
import com.cheng.utils.C3p0;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class C3p0Test {public static void main(String[] args) throws SQLException {Connection conn =null;PreparedStatement ps = null;ResultSet rs =null;try {conn = C3p0.getConnection();String sql = "select * from users";ps=conn.prepareStatement(sql);rs=ps.executeQuery();while (rs.next()){System.out.println(rs.getString("NAME"));}} catch (Exception e) {e.printStackTrace();}finally {C3p0.release(conn,ps,rs);}}
}
结论
无论使用什么数据源,本质是不变的,DateSource接口不会变,方法就不会变,因为这些开源数据源都是DateSource接口的实现。