一、JDBC入门
1、JDBC概念:
JDBC本质上是由java官方提供的一套接口,具体实现类由各大数据库厂商进行实现。
2、为什么要有JDBC?
每一套数据库都有自己的API,那么对于开发人员每用一套数据库都要学习一套新的API,那么开发成本就会加大。所以java官方出面定义了一套 数据库接口,具体实现类由厂商实现。那么我们使用时,只需要导入jar包,使用Java定义的接口调用厂商提供的实现类对数据库进行连接。所以我们只需要面向接口编程.
3、JDBC的使用步骤
- 导入jar包
- 注册驱动 mysql 5 版本之后可以自动注册
- 获取连接对象
- 获取执行对象
- 执行sql语句并获得返回结果
- 处理返回结果
- 释放资源
二、JDBC功能类详解
1、DriverManager类
作用:让自己的电脑和数据库服务器建立连接
格式:static Connection getConnection(String url, String user, String password);
2、Connection
作用:获取执行对象
获取普通执行对象:Statement creatStatement();
获取预编译执行对象:PreparedStatement prepareStatement(String sql)
管理事务:
开启事务:setAutoCommit (boolean autoCommit); 默认为true,为自动提交
提交事务:commit();
回滚事务:rollback();
释放资源:
释放数据库连接对象:void close();
3、Statement
作用:将sql语句发送到数据库并且执行
根据查询语句的不同分为两类方法:
执行DQL语句:ResultSet executeQuery(String sql)
执行DML语句:int executeUpdate(String sql)
释放资源:void close();
扩展:boolean execute(String sql) 作用:执行SQL语句,可以执行任意的SQL语句,通常用来执行 DDL语句 ,返回值:如果有结果集返回true,如果没有结果集返回false查询有结果集,增删改没有结果集
4、ResultSet
作用:用于接收查询返回来的结果集
方法:
判断结果集中是否还存在数据:boolean next(); 有数据返回true,并将索引向下移动一行,没有数据就返回false
获取结果集中的数据: XXX getXxx(“列名”);根据数据库中列的数据类型, int getInt(“id”)
释放资源:void close();
以上对象的关闭顺序:先开的后关闭
数据库连接代码案例:
//1、加载类驱动,mysql版本5之后可以完成自动加载,
Class.forName("com.mysql.jdbc.Driver")
//2、获取连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://数据库ip地址/数据库名", "用户名", "密码");
//3、获取发送语句的对象
Statement statement = connection.createStatement();
String sql = "select * from account";
ResultSet resultSet = statement.executeQuery(sql);
//4、处理返回的数据对象
while (resultSet.next()){ System.out.println(resultSet.getInt("id")+"\t"+resultSet.getString("NAME")+"\t"+resultSet.getString("money"));
}
//5、关闭资源
resultSet.close();
statement.close();
connection.close();
三、工具类的抽取
为什么要抽取工具类?
每一次连接数据库,创建连接对象的代码都是一样的。代码冗余度大,所以我们将创建连接对象的代码抽取出来成为一个工具类,提高代码的复用性。
package com.test.utils;
import java.sql.*;
/**
* 创建工具类
* 1. 直接获取连接对象
* 2. 释放资源
*/
public class JdbcUtils {
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql:///day22", "root", "root");
}
/**
* 关闭连接
* 查询调用这个方法
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 关闭连接
* 增删改没有结果集
*/
public static void close(Connection connection, Statement statement) {
//直接调用上面的方法
close(connection, statement, null);
}
}
四、SQL注入问题
使用字符串拼接的方式,将sql语句和要查询的条件进行字符串拼接,那么恶意的用户可以在输入框输入恶意字符串将sql语句拼接起来。这样哪怕就算数据库中没有数据也可以登录成功。
**SQL注入问题的解决 **:不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。
我们通过使用prepareStatement接口来解决SQL注入的问题:
PreparedStatement接口
PrepareStatement extends Statement
MySQL中的语句也是要编译以后才执行,编译是需要消耗时间的。Prepared 翻译为事先的,准备好的。
PrepareStatement比父接口更强大的地方:
- SQL语句会预先编译,执行效率会更高。
- 解决SQL注入的问题,更安全
- SQL语句代码的可读性更好,所有要替换的参数使用占位符,占位符是?问号
PreparedSatement的执行原理:如果执行相同的SQL语句,只需要编译一次,而且是预先编译。在创建语句对象的时候就提供了SQL语句,而不是等到执行的时候才提供SQL语句。
PreparedStatement中相关的方法
Connection创建PreparedStatement对象
Connection接口中的方法 | 描述 |
---|---|
PreparedStatement prepareStatement(String sql) | 通过连接对象创建预编译语句对象, 创建的时候要提供SQL语句,语句有可能有占位符 如果有占位符,后面需要替换占位符为真实的值 |
注意与Statement的不同,这里是将sql语句传入对象中,Statement是将sql语句传入方法中。在创建语句执行对象时就将sql语句发送到数据库,这里很好的体现了预编译的思想。
PreparedStatement接口中的方法 | 描述 |
---|---|
int executeUpdate() | 实现增删改的操作 返回影响的行数 |
ResultSet executeQuery() | 实现查询的操作,返回结果集 |
设置占位符的方法
PreparedStatement的方法 | 说明 |
---|---|
void set数据类型(int 参数1,数据类型 参数2) | 参数1:第几个占位符(从1开始) 参数2:替换占位符的真实值 |
示例代码:
package com.itheima;
import com.test.utils.JdbcUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 1. 向学生表中添加1条记录代码
* 2. 将id为2的用户,姓名更新为"蜘蛛精",性别换成女
* 3. 将id为4的学员删除
*/
public class Demo06Prepared {
@Test
public void testInsert() throws SQLException {
//1.创建连接对象
Connection connection = JdbcUtils.getConnection();
//2.创建预编译的语句对象,提供了SQL语句
PreparedStatement ps = connection.prepareStatement("insert into student (name, sex, birthday) values (?, ?, ?)");
//3.替换占位符为真实的值
ps.setString(1, "二郎神");
ps.setBoolean(2, true);
ps.setDate(3, Date.valueOf("2011-02-20")); //注:这是java.sql.Date类中静态方法
//4.执行SQL语句
int row = ps.executeUpdate(); //没有SQL语句
//5.释放资源
JdbcUtils.close(connection, ps);
System.out.println("插入了" + row + "条记录");
}
//将id为2的用户,姓名更新为"蜘蛛精",性别换成女
@Test
public void testUpdate() throws SQLException {
//1.创建连接对象
Connection connection = JdbcUtils.getConnection();
//2.创建预编译的语句对象,提供了SQL语句
PreparedStatement ps = connection.prepareStatement("update student set name=?, sex=? where id = ?");
//3.替换占位符为真实的值
ps.setString(1, "蜘蛛精");
ps.setBoolean(2, false);
ps.setInt(3, 2);
//4.执行SQL语句
int row = ps.executeUpdate(); //没有SQL语句
//5.释放资源
JdbcUtils.close(connection, ps);
System.out.println("更新了" + row + "条记录");
}
//将id为4的学员删除
@Test
public void testDelete() throws SQLException {
//1.创建连接对象
Connection connection = JdbcUtils.getConnection();
//2.创建预编译的语句对象,提供了SQL语句
PreparedStatement ps = connection.prepareStatement("delete from student where id=?");
//3.替换占位符为真实的值
ps.setInt(1, 4);
//4.执行SQL语句
int row = ps.executeUpdate(); //没有SQL语句
//5.释放资源
JdbcUtils.close(connection, ps);
System.out.println("删除" + row + "条记录");
}
}
五、JDBC的事务管理
在默认情况下,jdbc是自动提交的。如果我们要进行多条sql语句的执行操作,那么我们就需要对事务进行管理。
管理事务的功能类:Connection
开启事务:setAutoConmit(boolean autoCommit); 参数为false则开启事务
提交事务:commit();
回滚事务:rollback();
六、元数据
什么元数据?
用来描述数据的数据,比如创建表的代码,用来描述表的结构,描述这一列数据的信息
JDBC中元数据分类
三种元数据 | 说明 | 如何得到元数据 |
---|---|---|
数据库元数据 | 描述数据库一些信息,如:数据库驱动版本号,数据库版本号等 | 通过连接对象Connection |
参数元数据 | 描述占位符参数的一些信息,如:一共有几个参数 | 通过PreparedStatement |
结果集元数据 | 描述结果集中信息,如:一共有多少列,每列的数据类型是什么 | 通过ResultSet |
参数元数据:ParameterMetaData
获取ParameterMetaData
PreparedStatement接口中的方法 | 说明 |
---|---|
ParameterMetaData getParameterMetaData() | 通过预编译的语句获取参数元数据 |
接口中的方法
因为是接口,所以需要数据库厂商驱动的支持,mysql对参数元数据的支持不是太理想
ParameterMetaData接口中的方法 | 说明 |
---|---|
int getParameterCount() | 获取参数的个数 |
String getParameterTypeName(int param) | 获取某一列参数的数据类型,参数就是列号,从1开始 只能获取varchar类型的数据 |
示例代码:
package com.itheima;
import com.test.utils.JdbcUtils;
import java.sql.*;
/**
* 参数元数据
*/
public class Demo3 {
public static void main(String[] args) throws SQLException {
//1. 得到连接对象
Connection connection = JdbcUtils.getConnection();
//2. 得到语句对象,SQL语句是向学生表中插入一条记录
PreparedStatement preparedStatement = connection.prepareStatement("insert into student (name, sex, birthday) values (?,?,?)");
preparedStatement.setString(1,"猪八戒");
preparedStatement.setInt(2, 1); //使用整数
preparedStatement.setDate(3, Date.valueOf("2000-11-11"));
//3. 得到参数元数据
ParameterMetaData metaData = preparedStatement.getParameterMetaData();
//4. 输出参数的个数 getParameterCount()
System.out.println("一共有多少个参数:" + metaData.getParameterCount());
//5. 得到第1个参数的类型名字 getParameterTypeName()
System.out.println("第1个参数的类型:" + metaData.getParameterTypeName(1));
//6. 执行SQL语句,插入1条记录
int row = preparedStatement.executeUpdate();
System.out.println("插入了一条记录:" + row);
//关闭连接
JdbcUtils.close(connection, preparedStatement);
}
}
七、连接池介绍
JDBC访问数据库时操作Connection对象
JDBC访问数据库的每个增删改查的操作都需要先创建连接,而且这个连接对象是不能共享的。每个用户每次访问都必须创建一个连接对象,经过测试,每次访问数据库最耗时的部分就是创建连接对象。每次使用完连接对象以后,就马上关闭。
为了提高效率,需要解决两个问题
-
如何提高创建连接对象的速度
-
如何提高连接对象的使用率
使用连接池的目的就是为了解决以上2个问题
连接池的原理图
没有使用连接池的情况:每个用户访问数据库的时候,都是自己创建连接对象
使用连接池的情况:一开始系统启动的时候就创建了一定数量的连接对象,使用的时候直接从连接池中去获取就可以了。不需要自己来创建连接对象。
八、数据库连接池API
数据源接口
javax.sql.DataSource接口
实现类在哪?由第三方厂商来实现,只要实现这个接口都可以编写自己的连接池。
数据源接口中的方法
DataSource接口中的方法 | 描述 |
---|---|
Connection getConnection() | 从连接池中获取连接对象 |
常用连接池参数含义
每个连接池都有很多的参数,几乎所有的参数都是有默认的值的,我们也可以根据实际情况进行调整。参数名在不同的连接池中参数名是有区别的。
常用参数 | 描述 |
---|---|
初始连接数 | 服务器启动的时候创建的连接对象数量 |
最大连接数 | 连接池中最多可以允许放多少个连接对象 |
最长等待时间 | 如果连接池中没有连接对象,设置用户等待的最长时间是多久,单位是毫秒。 如果超过这个时间就抛出异常 |
最长空闲回收时间 | 如果一个连接对象长时间没有人使用,设置多久回收这个对象,默认是不回收。 |
九、c3p0连接池的介绍
常用连接池的介绍
DataSource本身只是Oracle公司提供的一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现。我们只需要学习这个工具如何使用即可。
常用的连接池实现组件有这些:
- 阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目
- DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。
- C3P0是一个开源的JDBC连接池,目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能。
C3P0常用的配置属性
就算不设置任何参数,每个连接池都有默认的配置参数。不同的数据源实现厂商,下面这些属性名会不同。
常用参数 | 描述 |
---|---|
initialPoolSize | 初始连接数,一开始创建多少个连接对象 |
maxPoolSize | 连接池中最大连接数 |
checkoutTimeout | 最长等待时间,等多久没有获取到连接对象,抛出异常 |
maxIdleTime | 最长空闲回收时间,并不是到了这个时间马上回收,看连接池使用情况 |
配置文件的要求
使用c3p0的配置文件来配置连接池,配置文件有如下几个要求:
- 文件名:必须叫c3p0-config.xml
- 路径:这个文件必须要放在src目录下,即类路径下
- 分成两种配置
- 默认配置:在不指定配置名的情况下,默认使用的配置
- 命名配置:可以使用多个配置,通过名字来选择使用哪个配置
- 多个配置的好处:
- 可以使用不同的连接池参数
- 可以指定不同的数据库名,如:day20, day21
- 可以指定不同厂商的数据库,如:Oracle,MySQL
API的介绍
C3P0中的类
构造方法 | 描述 |
---|---|
ComboPooledDataSource() | 默认的构造方法,使用默认的配置来创建连接池 |
ComboPooledDataSource(命名配置) | 指定使用哪个命名配置来创建连接池 |
配置文件示例:c3p0-config.xml
<?xml version="1.0" encoding="utf-8"?>
<c3p0-config>
<!-- 默认配置 -->
<default-config>
<!--1. 连接数据库有关的参数,键是固定的,值可以修改 -->
<!--mysql驱动的名字-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!--连接字符串URL-->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day24</property>
<!--用户名-->
<property name="user">root</property>
<!--密码-->
<property name="password">root</property>
<!--2. 连接池配置有关的参数-->
<!--初始连接数-->
<property name="initialPoolSize">5</property>
<!--最大连接数-->
<property name="maxPoolSize">10</property>
<!--过多久没有获取连接,超时抛出异常-->
<property name="checkoutTimeout">3000</property>
</default-config>
<!--命名配置,可以使用多个-->
<named-config name="otherc3p0">
<!--mysql驱动的名字-->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!--连接字符串URL,修改了库名 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day23</property>
<!--用户名-->
<property name="user">root</property>
<!--密码-->
<property name="password">root</property>
<!--初始连接数-->
<property name="initialPoolSize">3</property>
<!--最大连接数-->
<property name="maxPoolSize">15</property>
<!--过多久没有获取连接,超时抛出异常-->
<property name="checkoutTimeout">2000</property>
</named-config>
</c3p0-config>
java代码
package com.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 1. 从连接池中得到10个连接对象,输出每个对象
* 2. 分别设置默认配置和命名配置
*/
public class Demo1C3p0 {
public static void main(String[] args) throws SQLException {
//1.创建连接池,使用默认配置。ComboPooledDataSource实现了DataSource接口
//ComboPooledDataSource ds = new ComboPooledDataSource();
//使用命名配置
ComboPooledDataSource ds = new ComboPooledDataSource("otherc3p0");
//2.使用连接池,从连接池中获取10个连接对象
for (int i = 1; i <= 16; i++) {
Connection connection = ds.getConnection();
System.out.println("第" + i + "个连接对象:" + connection);
//第5个释放
if (i==5) {
//放回到连接池中
connection.close();
}
}
}
}
十、Druid的连接池
DRUID简介
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:https://github.com/alibaba/druid
DRUID连接池使用的jar包:druid-1.0.9.jar
常用的配置参数
参数 | 说明 |
---|---|
url | 连接字符串 |
username | 用户名 |
password | 密码 |
driverClassName | 驱动类名,会自动根据URL识别,这一项可以不配置 |
initialSize | 初始连接数 |
maxActive | 最大连接数 |
maxWait | 最长等待时间 |
DRUID连接池API介绍
得到配置文件的输入流
Class类中的方法 | 说明 |
---|---|
InputStream getResourceAsStream(String path) | 加载类路径下配置文件,转成一个输入流对象 |
Properties类的方法,读取属性文件中所有的键和值,加载到集合中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EmxnNnaw-1592189228218)(D:/%E9%BB%91%E9%A9%AC%E5%AD%A6%E4%B9%A0/JavaWeb/day24%E3%80%90MyBatis%E7%AC%AC1%E5%A4%A9%E3%80%91/%E8%AE%B2%E4%B9%89/assets/image-20200513103942896.png)]
通过druid工厂类的静态方法创建连接池,提供属性集合做为参数
DruidDataSourceFactory的方法 | 方法 |
---|---|
public static DataSource createDataSource(Properties properties) | 通过属性集合中属性,创建一个连接池 |
示例代码:
在src目录下新建一个DRUID配置文件,命名为:druid.properties
url=jdbc:mysql://localhost:3306/test
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10
maxWait=2000
java代码
package com.test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class Demo2Druid {
public static void main(String[] args) throws Exception {
//1.从类路径下加载配置文件,获取一个输入流。如果不指定路径,默认是读取同一个包下资源文件
InputStream inputStream = Demo2Druid.class.getResourceAsStream("/druid.properties");
//2.使用Properties对象的方法将配置文件中属性加载到Properties对象中
Properties properties = new Properties();
//加载了配置文件中所有的属性
properties.load(inputStream);
//3.通过druid的工厂类创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//获取10个连接对象
for (int i = 1; i <= 11; i++) {
Connection connection = dataSource.getConnection();
System.out.println("第" + i + "个连接对象:" + connection);
//第3个连接关闭
if (i==3) {
connection.close();
}
}
}
}