JDBC相关介绍
JDBC是Java提供操作数据库的规范,也就是提供一系列接口,具体的实现类由各个数据库厂商提供,所以我们要导入相关Jar包,这些jar包提供对应的实现类,如果是mysql数据库,那么我们就用导入mysql的连接驱动的jar包,
一、JDBC的概念
JDBC指java database connectivity(java数据库连接),也就Java提供的执行SQL操作数据库的API,可为多种关系型数据库提供统一的操作,它是由一组java 语言编写的类或者接口组成(本质:就是Java提供一套操作数据库的规范,所有关系型数据库都实现了这一规范,这样程序员就可以通过统一的API操作不同的数据库)
二、JDBC操作
- 导入Jar包,因为JDBC只是Java提供的一套规范,具体实现需要不同数据库厂商去实现
注意:jdbc是java提供的操作数据库的规范,不同的数据库厂商需要实现规范,也就是提供实现类,而我们操作什么数据库就要导入对应的实现类jar包
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
- 注册驱动
- 获取连接
- 获取执行者对象(用来执行SQL)
- 执行SQL
- 处理结果
- 关闭连接 ```java package com.study;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class TestJDBC { public static void main(String[] args) throws Exception{ //第一步:导入Jar //第二步:注册驱动 Class.forName(“com.mysql.jdbc.Driver”); //第三步:获取连接 Connection connection = DriverManager.getConnection(“jdbc:mysql://182.92.71.236:3306/study”, “root”, “zhubowenmysql”); //第四步:获取执行者对象 Statement statement = connection.createStatement(); //第五步:执行SQL ResultSet resultSet = statement.executeQuery(“select * from jdbctest”); //遍历结果 while (resultSet.next()){ int id = resultSet.getInt(“id”); String username = resultSet.getString(“username”); String password = resultSet.getString(“password”); System.out.println(“id:”+id+”,username:”+username+”,password:”+password); } //释放资源 statement.close(); connection.close(); } }
<a name="vrnVT"></a>
### 三、详细解释
1.注册驱动,我们注册驱动使用的是DriverManager这个对象,但是在com.mysql.jdbc包下有一个Driver类,这个Driver类的静态代码块就完成了注册,为了防止重复注册,我们都是调用这个类来完成注册,只要类加载那么静态代码块就会被加载,所以我们注册驱动使用的是加载类,注意MySQL5版本,jar包中有一个配置文件写了注册驱动,我们可以省略,但是不建议
```java
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
2.获取数据库连接
通过DriverManager提供的静态方法getConnection方法
Connection connection = DriverManager.getConnection("jdbc:mysql://182.92.71.236:3306/study", "root", "zhubowenmysql");
//参数一:url 写法jdbc:mysql://IP:端口/要连接的数据库名&连接参数
//参数二:用户名
//参数三:密码
3.获取执行者对象
//可以获取普通的执行者对象,
Statement statement = connection.createStatement();
//这个是获取预编译执行者对象
// connection.prepareStatement();
//开启事务,即是否自动提交,false表示手动提交(开启事务),默认是自动提交
connection.setAutoCommit(boolean autoCommit);
//提交事务
connection.commit();
//回滚事务
connection.rollback();
//释放连接资源
connection.close():
3.执行者对象:用来执行SQL语句(DML语句:增删改语句,DQL语句查询的语句)
//如果执行DML语句即增删改的语句,需要调用
statement.executeUpdate(String sql);//返回值是int表示修改的行数
//执行DQL语句,调用的是executeQuery,返回的是查询的结果集
ResultSet resultSet = statement.executeQuery("select * from jdbctest");
//执行者对象释放资源调用close()方法
4.结果集对象ResultSet:它是执行查询返回的结果,封装到结果集
//方法一:next()如果有结果返回true,并将索引向下移一位
//XXX getXXX()/获取结果集中的结果,获取什么类型就调用对应的get方法,例如获取int就调用getInt(String column);
while (resultSet.next()){
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
System.out.println("id:"+id+",username:"+username+",password:"+password);
}
四、SQL注入攻击
我们在组装SQL参数的时候,采用的是拼接的形式,导致页面如果传入参数含有SQL那么就被认为是SQL语句,也会被拼接上去,那么就会存在SQL的注入攻击,
解决方案获取预编译执行者对象,这样SQL会提前编译好,使用?作为参数的占位符,然后调用set方法来给占位符赋值,索引从1开始,后来加上的都会本人为是参数部分,即使传递了参数含有SQL也不会被识别还是认为是参数
//SQL注入攻击演示
@Test
public void testSQLDI() throws Exception{
Connection connection = GetConnectionUtil.getConnection();
Statement statement = connection.createStatement();
//这样写存在SQL的注入攻击
// String username="test";
// String password="1111";
// ResultSet resultSet = statement.executeQuery("select * from jdbctest where username='test' and password=1111 ");
// //使用预编译执行者对象来解决SQL注入攻击
PreparedStatement preparedStatement = connection.prepareStatement("select * from jdbctest where username=? and password=?");
preparedStatement.setString(1,"test");
preparedStatement.setString(2,"1111");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println("id:"+resultSet.getInt("id"));
}
//关闭资源
statement.close();
connection.close();
}
五、JDBC事务的注意:
JDBC管理事务的对象是Connection对象
我们在进行增删改的时候一定是要提交事务,否者结果不会被修改,JDBC默认是自动提交事务
数据源介绍使用
一、自定义数据库连接池
1.数据库连接池介绍
对于程序来操作数据库,如果我们每操作一次就建立一次连接,然后使用完就释放连接这样性能低下,所以在此基础上提出了池的概念
数据库连接池,它是一个负责分配,管理,释放数据库连接的,他允许数据库重复使用一个现有的数据库连接,而不是新建立一个,这样可以提高性能
2.自定义数据库连接池
DataSource接口:javax.sql.DataSource它是数据源(java官方提供的数据库连接池规范,我们要定义数据连接池要实现这个接口提供抽象方法,即实现相关规范),这个接口提供getConnection()方法用来获取连接
package com.study.source;
import com.study.util.GetConnectionUtil;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
//步骤:第一步实现javax.sql.DataSource接口
//第二步:创建容器池:即创建一个线程安全的集合容器(这里一定要是线程安全的因为连接池程序线程共同操作的)
//第三步:在静态代码块中创建多个连接对象放入池中
//第四步:重写getConnection();来获取连接
public class SelfDataSource implements DataSource {
private static List<Connection> pool= Collections.synchronizedList(new ArrayList<Connection>());
static {
for (int i = 0; i < 10; i++) {
try {
Connection connection = GetConnectionUtil.getConnection();
pool.add(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//重写获取连接对象,获取对象要判断池中是否还有连接
public Connection getConnection() throws SQLException {
if(pool.size()>0){
//获取连接并将连接移除池子
Connection connection = pool.remove(0);
return connection;
}else {
throw new RuntimeException("连接以用尽");
}
}
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
这里我们就实现了通过连接池来获取连接对象,这个连接对象时DriverManager调用getConnection()获取的,他的类型是JDBC4Connection类型(我们引入的驱动包中的连接对象),通过调用这个类来执行SQL的,但是我们执行完SQL,调用这个类的Close()会关闭资源不能将资源归还给池子,所以需要优化,因为这个方法是源码里的方法,所以我们最好的方式是使用动态代理
优化归还连接方式
方式一:装饰者设计模式,我们自定义一个类实现我们要增强类的接口,这样这个类就具备了原有来相同的功能,我们可以在这个类上继续添加或者修改原有功能,对于不需要修改的功能我们还是调用原来类的对象调用其方法,相当于对原有类进行一个包装,这种设计模式称为装饰者设计模式
实现步骤:
装饰者设计模式来归还连接:对返回的连接对象进行装饰,即对需要的功能进行装饰(增强),其他功能可以调用原油对象进行保留
步骤:定义一个装饰者类用来装饰原有类即继承同一个接口
将原有类对象作为装饰者类的属性,在构造器为其赋值(不需要增强的类,需要原有对象调用)
重写要增强的方法
对于不需要增强的方法调用原有对象执行
装饰者模式实现原理:是定义一个类实现要装饰类同样的接口(这个接口要包含需要装饰的方法),重写需要装饰的方法,对于不需要重写的方法,我们需要调用原有的装饰对象去执行,所以需要原有对象作为成员变量,在构造器或其他地方为其值
缺点:需要重写很多不需要的方法
方式二:适配器模式
由于我们使用装饰者设计模式需要重写很多方法,再此基础上我们可以进行优化,提供一个中间类来实现接口,这样我们只需要中间类重写方法(需要增强的方法不进行重写,所以适配器类是一个抽象类),我们只需要写一个类来继承中间类即可
方式三:动态代理(在不该源码的情况,对需要代理的源码中的方法进行增强)
这里最值得注意的是我们在重写方法,执行代理规则的时候,一定要注意,如果需要执行原有方法那么,一定要将原有方法的对象进行返回保持调用类型相同
@Override
public Connection getConnection() throws SQLException {
if(pool.size()>0){
Connection con = pool.remove(0);
//使用动态代理的方式
//动态代理的时候指定增强规则时,这个方法的返回值一定要注意,
//如果我们是直接执行原有方法,不进行增强修改,那么return是method.invoke()这个方法的返回值,
//因为这样得到的对象类型才对,你要知道动态代理对象类型一定要对
//如果是我们需要增强的方法,那么返回值更具我们实际调用来决定
Connection connection= (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), new Class[]{Connection.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method.getName().equals("close")){
System.out.println("增强方法");
pool.add(con);
return null;
}else {
return method.invoke(con, args);
}
}
});
return connection;
}else {
throw new RuntimeException("连接用尽");
}
}
二、开源的数据库连接池
2.1.C3P0数据源
使用步骤
- 导入坐标
- 配置配置文件
- 创建连接池对象
- 获取连接使用
注意:c3p0数据源的配置文件,可以自动加载,但是前提条件是必须放在src目录下,且文件名叫c3p9-config.xml或者叫c3p0-config.properites
c3p0使用实例:下面是硬编码的形式
package com.study.c3p0;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
public class C3p0DataSourceNum {
public static void main(String[] args) throws Exception{
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://182.92.71.236:3306/study?characterEncoding=utf-8");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("zhubowenmysql");
//设置初始化连接池数量
comboPooledDataSource.setInitialPoolSize(10);
//设置最大连接池数量:表示初始化连接用完,它会继续申请连接,一直申请到最大连接数量
comboPooledDataSource.setMaxPoolSize(12);
//设置连接超时时长:如果连接用尽,会等待3秒,3秒没有获取连接会报错
comboPooledDataSource.setCheckoutTimeout(3000);
for (int i = 0; i < 13; i++) {
Connection connection = comboPooledDataSource.getConnection();
System.out.println(i+":"+connection);
}
}
}
2.2德鲁伊连接池技术(Druid)阿里巴巴提供连接池技术
使用步骤:
- 导入坐标
- 编写配置文件
- 通过properties集合加载配置文件(也可以采用其他构造器,传入map集合进行硬编码的形式读取)
- 获取数据库连接进行使用
注意:Druid连接池不会自动加载配置文件,需要我们手动加载
#连接参数
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://182.92.71.236:3306/study
username=root
password=zhubowenmysql
#初始连接数量
initialSize=5
#最大连接数量
maxActive=10
#超时时长
maxWait=3000
package com.study.druid;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Properties;
public class DruidDataSource {
@Test
public void testProperties() throws Exception{
//通过读取配置文件的格式获取连接
InputStream inputStream = new FileInputStream("E:\\selfstudy\\studyjdbc\\src\\main\\resources\\druid.properties");
Properties properties = new Properties();
properties.load(inputStream);
System.out.println(properties);
//创建Druid连接池工厂对象
DruidDataSourceFactory druidDataSourceFactory = new DruidDataSourceFactory();
//获取连接池对象
DataSource dataSource = druidDataSourceFactory.createDataSource(properties);
//获取连接
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from jdbctest");
while (resultSet.next()){
System.out.println(resultSet.getInt("id"));
}
System.out.println(connection);
connection.close();
}
@Test
public void testMap() throws Exception{
HashMap<String, String> map = new HashMap<>();
map.put("driverClassName","com.mysql.jdbc.Driver");
map.put("url","jdbc:mysql://182.92.71.236:3306/study");
map.put("username","root");
map.put("password","zhubowenmysql");
map.put("initialSize","5");
map.put("maxActive","10");
map.put("maxWait","3000");
DruidDataSourceFactory druidDataSourceFactory = new DruidDataSourceFactory();
DataSource dataSource = druidDataSourceFactory.createDataSource(map);
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from jdbctest");
while (resultSet.next()){
System.out.println(resultSet.getInt("id"));
}
System.out.println(connection);
connection.close();
}
}
**