JDBC基础部分
问题一:JDBC是什么,为什么会存在JDBC?作用是什么?
JDBC(Java DataBase Connection) 是用于跟数据库进行交互的,由JDK统一提供,可以为多种关系型数据库提供统一的标准,但是是各大厂商进行实例化的。否则多个每个数据库都有不同的实现类进行连接,那么明显不利于开发人员提高效率
JDBC本质是一种使用接口的规范,用于为开发人员快速实现不同关系型数据库的连接!
问题二:类加载器的作用?是如何被触发的,什么情况下会触发?跟JDBC有什么关系?
类加载器的作用是将使用到的每一个类加载到内存方法区当中常驻。有三个类型,AppClassLoader,ExtraClassLoader,BoostrapClassLoader,每当有类被调用的时候,会优先实行双亲委派机制。如果ext,boostrap均不加载,才由AppClassLoader自行进行加载,这个时候则该类为自定义类或者三方jar包。
JDBC的实质是一个三方jar包,所以最终是由AppClassLoader进行加载进内存的
类加载器被触发一共有五种情况:
1.在new任何一个对象的时候,则会进入内存(new出来的东西均去堆内存)
2.使用静态成员和方法(静态的东西都是伴随着类一起加载的)
3.存在继承关系的父子,在加载时,均被加载进内存
4.在通过反射实例化一个类的时候
5.在某一个类中使用main方法会将这个类一同载入到内存中去
问题三:JBDC是如何执行的?
根据此图,可以大致的知道JAVA和数据库是如何产生联系的,首先要明确这是两台电脑之间在进行交互。故必须建立连接,如何建立连接?那么就需要提供自己的ip地址,访问mySQL的账户和密码,还有端口号。(此处不需要端口号)
首先是需要将jar包导入到idea中,并且激活进内存才能够使用的
其次在连接创建成功之后,方可用连接对象去创建执行对象,需要注意的是:执行对象有create和prepared两种方法,前者是有SQL注入问题的,所以不使用。忽略即可
再使用prepared方法来执行语句的增删改查操作,再对返回的结果集进行进一步的操作。
最后是关闭所有资源。
1. 导入JAR(JDBC的具体实现)
2. 注册驱动(查看字节码:jclasslib)
3. 获取链接
4. 创建语句对象
5. 通过语句对象获取结果(List<Map<String, Object>>)
6. 解析结果集
7. 释放资源
JDBC的组成
JDBC是由DriverManager类,Connection接口,Statement接口,ResultSet接口组成的。
DriverManager类
在MySQL5版本之前,不提供注册驱动的方法,所以需要自己手动注册驱动
Class.forName("com.mysql.jdbc.Driver");
之后的版本已经提供过,所以不需要在写一次。
值得注意的是:在这个”com.mysql.jdbc.Driver”驱动器的类当中包含了一段静态代码块。使用的是AppClassLoader进行加载进内存的。此静态代码块为前提,作用是进行初始化连接,如果没有则接下来的操作均没有意义
DriverManager提供方法获取连接对象
Connection connection = DriverManager.getConnection(url, root,password);
Connection接口
这个接口的作用有两个
一个是用来获取执行对象的,可以获取createStatement和prepareStatement
PreparedStatement ps = connection.prepareStatement("delete from student where sid=?");
二个是用来手动开启事务的,不开启的时候都是默认自动提交,如转账操作的时候需要开启手动提交
connection.setAutoCommit(false);
commit();
rollback();
同时
public int deleteInfoById(int num) {
Connection connection=null;
Statement statement=null;
//使用工具类获取连接对象
try {
connection = JDBCtest.getConnection();
//使用prepare方法,避免注入
PreparedStatement ps = connection.prepareStatement("delete from student where sid=?");
//设置占位符
ps.setInt(1,num);
int i = ps.executeUpdate();
} catch (Exception e) {
//执行方法
e.printStackTrace();
}
//返回影响行数
return num;
}
Statement接口
是一个父接口,子类是 PreparedStatement,自身是提供createStatement方法但是含有注入问题,不用。
PreparedStatement的作用是可以预编译SQL语句,使效率更高。但是需要在语句中使用占位符代替输入,之后再使用set数据类型(是第几个占位符,需要替换的值)方法来进行赋值执行。最后在使用executeQurey或者executeUpdate来进行执行语句。
如下是进行如何执行SQL语句和设置占位符的代码:
public int deleteInfoById(int num) {
Connection connection=null;
Statement statement=null;
//使用工具类获取连接对象
try {
connection = JDBCtest.getConnection();
//使用prepare方法,避免注入
PreparedStatement ps = connection.prepareStatement("delete from student where sid=?");
//设置占位符
ps.setInt(1,num);
int i = ps.executeUpdate();
} catch (Exception e) {
//执行方法
e.printStackTrace();
}
//返回影响行数
return num;
}
ResultSet接口
也提供获取结果集的方法 ,get数据类型(“表中的列名”/“表中的第几列”)
//在获取到结果集之后
PreparedStatement ps11 = connection.prepareStatement("select * from student where sid=?");
ResultSet resultSet = ps11.executeQuery();
while (resultSet.next()){
String name1 = resultSet.getString("name");
}
是一个查询之后的返回结果集。如果是查询出来的对象则需要进行封装,遍历后使用list集合进行存放(List<Map<String, Object>>)。如果是增删改操作就是返回的影响行数。
public ArrayList<Student> findAll() {
try {
Connection connection = JDBCtest.getConnection(); //调用方法获取连接
//获取申明
Statement statement = connection.createStatement();
//查询语句,获得结果集
ResultSet resultSet = statement.executeQuery("select * from student;");
//进行遍历,把每一个对象存入list集合中,最后进行返回
while (resultSet.next()){
//获取每一个结果集中的数据
int sid = resultSet.getInt("sid");
String name = resultSet.getString("name");
Date birthday = resultSet.getDate("birthday");
int age = resultSet.getInt("age");
Student student = new Student(sid, name, age, birthday);//此处预先已经定义好了一个student
list.add(student);
}
//关闭资源
connection.close();
statement.close();
resultSet.close();
} catch (Exception e) {
e.printStackTrace();
}
//返回结果集给service层
return list;
}
返回的结果集是需要进行遍历的,所以提供了一个next()方法,如下是该next方法的伪代码。
class ResultSet {
List<Map<String, Object>> data;
int p = 0;
Map<String, Object> curData;
public boolean next() {
if (p > data.size()) return false;
p++;
curData = data.get(p);
return true;
}
public get(String key) {
return curData.get(key);
}
}
其他零散知识点
windows本机电脑ip可以写为如下格式,但是在公司里的时候这个端口号要询问公司是多少
Connection c = DriverManager.getConnection("url=jdbc:mysql://localhost:端口号/数据库名?参数... ", "root", "root");
Connection c = DriverManager.getConnection("url=jdbc:mysql://127.0.0.1:3306/db14 ", "root", "root");
- 导包(idea中可以直接alt + enter)
- 写Test注解
在使用createStatement方法的时候就会有注入问题。
就是利用sql语句的漏洞来对系统进行攻击
让特殊的sql格式来实现错误密码登录, 最终能够正确查询数据。这种情况应尽量避免
在一条SQL语句中 如果是用了 or 1=1 – 的情况 则会出现注入
JDBC的工具类编写主要是为了去除冗余。将重复的部分进行提取
方法一:最原始的抽取。
方法二:将数据库的信息用配置文件的方式进行存储
public class JDBCtest {
//私有构造方法,以让其他人不可new该对象
private JDBCtest(){};
//定义配置文件中的key作为类成员
private static String classLoad;
private static String url;
private static String root;
private static String password;
//使用静态代码块优先加载进内存
static{
//使用类加载器读取配置文件
InputStream ras = JDBCtest.class.getClassLoader().getResourceAsStream("config.properties");
//创建properties对象
Properties properties = new Properties();
//调用load方法
try {
properties.load(ras);
classLoad = properties.getProperty("classLoad");
url = properties.getProperty("url");
root = properties.getProperty("username");
password = properties.getProperty("password");
} catch (IOException e) {
e.printStackTrace();
}
}
//提供方法获取连接对象
public static Connection getConnection() throws Exception {
Class.forName(classLoad);
Connection c = DriverManager.getConnection("url=jdbc:mysql://localhost/db14 ", "root", "root");
Connection connection = DriverManager.getConnection(url, root,password);
return connection;
}
//提供方法关闭资源
public static void close(Connection connection, Statement statement, ResultSet resultSet) throws SQLException {
if(connection!=null){
connection.close();
}
if(statement!=null){
statement.close();
}
if(resultSet!=null){
resultSet.close();
}
}
//提供重载方法关闭资源
public static void close(Connection connection, Statement statement) throws SQLException {
close(connection,statement,null); // 调用重载方法
}
}
//配置文件的信息
classLoad=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/db14
username=root
password=root
在使用断点的时候,可以使用打断点,当这个断点从红色(还未执行时)变为蓝色的时候,说明这段代码是已经执行了的。
基础阶段总结
1.这个执行流程为:DriverManager -> Connetction -> Statement -> ResultSet
2.在每一个创建过程中,需要提取重复部分当作工具类实现的,另外JDBC是作为底层而存在的,后期会由框架帮我们完成与数据集的交互,只需要给定必要参数即可。
3.每一个资源都需要在使用完成后进行关闭,为确保一定被关闭掉,所以要写在try…catch中的finally里面
4.事务的管理需要在业务层实现,因为dao层的功能要给很多模块提供功能的支撑,而有些模块是不需要事务的。
JDBC进阶部分
连接池
-
连接池是什么?为什么会有连接池?作用是什么?
连接池用于存放多个连接对象的地方,每次在创建、关闭连接对象的时候,都会极大的消耗内存资源,所以迫切的需要一个工具可以在程序一启动的时候就一次性创建多个连接对象,在反复的使用。
连接池的主要作用是可以在内存中创建好一批连接对象,供程序使用,以提高效率。
每个连接池均实现DataSource接口,以拥有getConnection方法从池中获取连接
C3P0连接池
-
配置文件
可以有两种格式的配置文件.properties和.xml,但是
名字必须是 C3P0-config
如:C3P0-config.xml这也是用得最多的一种。配置文件中包括的参数:
<c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> //驱动器 <property name="jdbcUrl">jdbc:mysql://localhost:3306/db14</property> //连接信息 <property name="user">root</property> //账号 <property name="password">root</property> // 密码 <!-- 连接池参数 --> <property name="initialPoolSize">5</property> //初始承载 <property name="maxPoolSize">10</property> //最大承载 <property name="checkoutTimeout">3000</property> //连接超时控制时间 </default-config> <named-config name="otherc3p0"> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db15</property> <property name="user">root</property> <property name="password">itheima</property> <!-- 连接池参数 --> <property name="initialPoolSize">5</property> <property name="maxPoolSize">8</property> <property name="checkoutTimeout">1000</property> </named-config> </c3p0-config>
有两种标签: 默认的default和命名的named-config,均需要进行填写
-
使用步骤
-
导包,一共有三个(mysql驱动,C3P0,数据库)
-
配置文件,要放在根目录下
-
-
如何获取连接池对象?
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); //C3P0提供的获取连接池方法 Connection connection = comboPooledDataSource.getConnection(); // 获取连接对象
Druid连接池
是阿里旗下的一款产品
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db14
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
-
使用步骤
-
导包,一共有两个(mysql驱动,Druid,数据库)
-
配置文件,要放在根目录下
-
使用时要将配置文件读入到内存中,代码如下
package utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; public class DruidUtils { //私有化构造器 private DruidUtils(){}; //定义德鲁伊的存放对象 private static DataSource dataSource ; //设置静态代码块,将获取的值放在德鲁伊对象中 static{ //读取配置文件 Properties properties = new Properties(); InputStream resourceAsStream = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"); try { properties.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } try { dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } //提供方法获取德鲁伊对象 public static DataSource getDataSource(){ return dataSource; } }
数据库连接池的工具类
package com.heima.test1.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class DruidUtils { public static DataSource ds = null; static { try { //1. 加载 druid.properties 配置文件 InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"); Properties prop = new Properties(); prop.load(is); //2. 创建 Druid 连接池对象 ds = DruidDataSourceFactory.createDataSource(prop); } catch (Exception e) { e.printStackTrace(); } } /* 3. 提供 获得 连接池对象的方法 */ public static DataSource getDataSource(){ return ds; } /* 4. 提供 从连接池中 获取连接对象Connection的 方法 */ public static Connection getConnetion() throws SQLException { Connection conn = ds.getConnection(); return conn; } /* 5. 提供 释放资源的方法 可以再写一个重载方法只关闭Statement ,Connection */ public static void close(ResultSet rs, Statement stat, Connection conn) throws SQLException { if (rs != null) { rs.close(); } if (stat != null) { stat.close(); } if (conn != null) { conn.close(); } } }
小结:1.操作连接对象之前,都要先获连接池对象,再getConnection()获取连接对象
2.DruidDataSourceFactory是一个静态方法,同时需要读取一个properties类
C3P0需要new ComboPooledDataSource();
-
元数据
什么是元数据?
元数据指的是每一张表的列名称为元数据,用于底层,与反射结合使用可以作为框架基础
- 用户—>数据库 ParameterMetaData
- 数据库—>用户 ResultSetMetaData
各自提供不同的方法
preparedStatement中的方法
// 获取执行对象
PreparedStatement ps = connection.prepareStatement("select * from student where id=? and name=?");
ParameterMetaData pmd = ps.getParameterMetaData(); // 获得预编译的元数据
int parameterCount = pmd.getParameterCount(int); // 获取参数个数(占位符的个数)
//获取表中的数据类型,只能获取varchar类型,1表示第一列
//使用此方法要在注册驱动时追加----> ?generateSimpleParameterMetaData=true
String parameterTypeName = pmd.getParameterTypeName(int);
resultSet中的方法
ResultSetMetaData metaData = resultSet.getMetaData(); //获得结果集中的元数据
int columnCount = metaData.getColumnCount();//获得这个结果集中有几列
String catalogName = metaData.getCatalogName(int);//获得指定列的列名
String columnTypeName = metaData.getColumnTypeName(int);//获得指定列的数据类型
其他零散知识点
-
数据库的DCL操作
DCL一般是数据库管理员才可以拥有的操作 可以对数据集进行:创建用户,授权,撤销权限,删除用户等 语法: create user '用户名'@'主机名' identified by '密码' (创建用户) grant 权限 on 数据库.表名 to '用户名'@'主机名' (授权) revoke 权限 on 数据库.表名 from '用户名'@'主机名' (授权) drop user '用户名'@'主机名' (删除用户)
-
CLOB和BLOB的区别
- CLOB 在数据库中存储文本 - BLOB在数据库中存储图像、音频 - 最小256B,64KB,16MB,最大4GB 一共四个等级 BLOB基本不用,太过于占据数据库的内存
JDBC对于Mybatis底层框架的实现
主要使用的一种设计模式是:策略模式
作用是:所有的方法都是我们自己写的,框架只提供一个接口,再通过元数据和反射技术对其进行操作,使代码最终返回的结果实际上是接口的实现类返回的结果,再层层返回,最终返回给测试类的调用处。
该框架的查询语句的大致流程为:
test (测试类)【传递一个自己写的实现类Impl到中间类中去】—-> JDBCTemplate(中间类) 【其中有一个方法,需要传递一个接口进来,还再代码中调用了接口中的方法】—-> 接口【这个方法在被人调用的时候会自动去找到这个方法的实现类,去进行实现类中的代码】
——>实现类Impl —–>层层返回结果—-> 回到测试类的调用出
具体代码如下
测试类
package com.itheima05;
import com.itheima.utils.DataSourceUtils;
import com.itheima05.domain.Student;
import com.itheima05.handler.BeanHandler;
import com.itheima05.handler.BeanListHandler;
import com.itheima05.handler.ScalarHandler;
import org.junit.Test;
import java.util.List;
/*
模拟dao层
*/
public class JDBCTemplateTest {
private JDBCTemplate template = new JDBCTemplate(DataSourceUtils.getDataSource());
@Test
public void queryForScalar() {
//查询聚合函数的测试
String sql = "SELECT COUNT(*) FROM student";
Long value = template.queryForScalar(sql,new ScalarHandler<Long>());
System.out.println(value);
}
@Test
public void queryForList() {
//查询所有学生信息的测试
String sql = "SELECT * FROM student";
List<Student> list = template.queryForList(sql, new BeanListHandler<>(Student.class));
for(Student stu : list) {
System.out.println(stu);
}
}
@Test
public void queryForObject() {
//查询一条记录并封装自定义对象的测试
String sql = "SELECT * FROM student WHERE sid=?";
Student stu = template.queryForObject(sql,new BeanHandler<>(Student.class),1);
System.out.println(stu);
}
@Test
public void delete() {
//删除数据的测试
String sql = "DELETE FROM student WHERE name=?";
int result = template.update(sql, "周七");
System.out.println(result);
}
@Test
public void update() {
//修改数据的测试
String sql = "UPDATE student SET age=? WHERE name=?";
Object[] params = {37,"周七"};
int result = template.update(sql, params);
System.out.println(result);
}
@Test
public void insert() {
//新增数据的测试
String sql = "INSERT INTO student VALUES (?,?,?,?)";
Object[] params = {5,"周七",27,"1997-07-07"};
int result = template.update(sql, params);
if(result != 0) {
System.out.println("添加成功");
}else {
System.out.println("添加失败");
}
}
}
实现类,为三种查询时会出现的情况,分别为单个对象,集合对象(多对象),聚合函数(基本类型)
单个对象
package com.itheima05.handler;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
/*
实现类1:用于将查询到的一条记录,封装为Student对象并返回
*/
//1.定义一个类,实现ResultSetHandler接口
public class BeanHandler<T> implements ResultSetHandler<T>{
//2.定义Class对象类型变量
private Class<T> beanClass;
//3.通过有参构造为变量赋值
public BeanHandler(Class<T> beanClass) {
this.beanClass = beanClass;
}
//4.重写handler方法。用于将一条记录封装到自定义对象中
@Override
public T handler(ResultSet rs) {
//5.声明自定义对象类型
T bean = null;
try {
//6.创建传递参数的对象,为自定义对象赋值
bean = beanClass.newInstance();
//7.判断结果集中是否有数据
if(rs.next()) {
//8.通过结果集对象获取结果集源信息的对象
ResultSetMetaData metaData = rs.getMetaData();
//9.通过结果集源信息对象获取列数
int count = metaData.getColumnCount();
//10.通过循环遍历列数
for(int i = 1; i <= count; i++) {
//11.通过结果集源信息对象获取列名
String columnName = metaData.getColumnName(i);
//12.通过列名获取该列的数据
Object value = rs.getObject(columnName);
//13.创建属性描述器对象,将获取到的值通过该对象的set方法进行赋值
PropertyDescriptor pd = new PropertyDescriptor(columnName.toLowerCase(),beanClass);
//获取set方法
Method writeMethod = pd.getWriteMethod();
//执行set方法,给成员变量赋值
writeMethod.invoke(bean,value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
//14.返回封装好的对象
return bean;
}
}
集合对象(多对象)
package com.itheima05.handler;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
/*
实现类2:用于将查询到的多条记录,封装为Student对象并添加到集合返回
*/
//1.定义一个类,实现ResultSetHandler接口
public class BeanListHandler<T> implements ResultSetHandler<T>{
//2.定义Class对象类型变量
private Class<T> beanClass;
//3.通过有参构造为变量赋值
public BeanListHandler(Class<T> beanClass) {
this.beanClass = beanClass;
}
//4.重写handler方法。用于将多条记录封装到自定义对象中并添加到集合返回
@Override
public List<T> handler(ResultSet rs) {
//5.声明集合对象类型
List<T> list = new ArrayList<>();
try {
//6.判断结果集中是否有数据
while(rs.next()) {
//7.创建传递参数的对象,为自定义对象赋值
T bean = beanClass.newInstance();
//8.通过结果集对象获取结果集源信息的对象
ResultSetMetaData metaData = rs.getMetaData();
//9.通过结果集源信息对象获取列数
int count = metaData.getColumnCount();
//10.通过循环遍历列数
for(int i = 1; i <= count; i++) {
//11.通过结果集源信息对象获取列名
String columnName = metaData.getColumnName(i);
//12.通过列名获取该列的数据
Object value = rs.getObject(columnName);
//13.创建属性描述器对象,将获取到的值通过该对象的set方法进行赋值
PropertyDescriptor pd = new PropertyDescriptor(columnName.toLowerCase(),beanClass);
//获取set方法
Method writeMethod = pd.getWriteMethod();
//执行set方法,给成员变量赋值
writeMethod.invoke(bean,value);
}
//将对象保存到集合中
list.add(bean);
}
} catch (Exception e) {
e.printStackTrace();
}
//14.返回封装好的对象
return list;
}
}
聚合函数(基本类型)
package com.itheima05.handler;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
/*
1.定义一个类,实现ResultSetHandler接口
2.重写handler方法
3.定义一个Long类型变量
4.判断结果集对象中是否还有数据
5.获取结果集源信息的对象
6.获取第一列的列名
7.根据列名获取该列的值
8.返回结果
*/
//1.定义一个类,实现ResultSetHandler接口
public class ScalarHandler<T> implements ResultSetHandler<T> {
//2.重写handler方法
@Override
public Long handler(ResultSet rs) {
//3.定义一个Long类型变量
Long value = null;
try{
//4.判断结果集对象中是否还有数据
if(rs.next()) {
//5.获取结果集源信息的对象
ResultSetMetaData metaData = rs.getMetaData();
//6.获取第一列的列名
String columnName = metaData.getColumnName(1);
//7.根据列名获取该列的值
value = rs.getLong(columnName);
}
}catch (Exception e) {
e.printStackTrace();
}
//8.返回结果
return value;
}
}
中间类(该框架最主要的核心类)
其中的update方法是给增删改使用的
package com.itheima05;
import com.itheima.utils.DataSourceUtils;
import com.itheima05.handler.ResultSetHandler;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
/*
JDBC框架类
*/
public class JDBCTemplate {
//1.定义参数变量(数据源、连接对象、执行者对象、结果集对象)
private DataSource dataSource;
private Connection con;
private PreparedStatement pst;
private ResultSet rs;
//2.通过有参构造为数据源赋值
public JDBCTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
/*
查询方法:用于将聚合函数的查询结果进行返回
*/
public Long queryForScalar(String sql, ResultSetHandler<Long> rsh, Object...objs){
Long value = null;
try{
//通过数据源获取一个数据库连接
con = dataSource.getConnection();
//通过数据库连接对象获取执行者对象,并对sql语句进行预编译
pst = con.prepareStatement(sql);
//通过执行者对象获取参数的源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//通过参数源信息对象获取参数的个数
int count = parameterMetaData.getParameterCount();
//判断参数数量是否一致
if(count != objs.length) {
throw new RuntimeException("参数个数不匹配");
}
//为sql语句占位符赋值
for(int i = 0; i < objs.length; i++) {
pst.setObject(i+1,objs[i]);
}
//执行sql语句并接收结果
rs = pst.executeQuery();
//通过ScalarHandler方式对结果进行处理
value = rsh.handler(rs);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
DataSourceUtils.close(con,pst,rs);
}
//返回结果
return value;
}
/*
查询方法:用于将多条记录封装成自定义对象并添加到集合返回
*/
public <T> List<T> queryForList(String sql, ResultSetHandler<T> rsh, Object...objs){
List<T> list = new ArrayList<>();
try{
//通过数据源获取一个数据库连接
con = dataSource.getConnection();
//通过数据库连接对象获取执行者对象,并对sql语句进行预编译
pst = con.prepareStatement(sql);
//通过执行者对象获取参数的源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//通过参数源信息对象获取参数的个数
int count = parameterMetaData.getParameterCount();
//判断参数数量是否一致
if(count != objs.length) {
throw new RuntimeException("参数个数不匹配");
}
//为sql语句占位符赋值
for(int i = 0; i < objs.length; i++) {
pst.setObject(i+1,objs[i]);
}
//执行sql语句并接收结果
rs = pst.executeQuery();
//通过BeanListHandler方式对结果进行处理
list = rsh.handler(rs);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
DataSourceUtils.close(con,pst,rs);
}
//返回结果
return list;
}
/*
查询方法:用于将一条记录封装成自定义对象并返回
*/
public <T> T queryForObject(String sql, ResultSetHandler<T> rsh,Object...objs){
T obj = null;
try{
//通过数据源获取一个数据库连接
con = dataSource.getConnection();
//通过数据库连接对象获取执行者对象,并对sql语句进行预编译
pst = con.prepareStatement(sql);
//通过执行者对象获取参数的源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//通过参数源信息对象获取参数的个数
int count = parameterMetaData.getParameterCount();
//判断参数数量是否一致
if(count != objs.length) {
throw new RuntimeException("参数个数不匹配");
}
//为sql语句占位符赋值
for(int i = 0; i < objs.length; i++) {
pst.setObject(i+1,objs[i]);
}
//执行sql语句并接收结果
rs = pst.executeQuery();
//通过BeanHandler方式对结果进行处理
obj = rsh.handler(rs);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
DataSourceUtils.close(con,pst,rs);
}
//返回结果
return obj;
}
/*
用于执行增删改功能的方法
*/
//3.定义update方法。参数:sql语句、sql语句中的参数
public int update(String sql,Object...objs) {
//4.定义int类型变量,用于接收增删改后影响的行数
int result = 0;
try{
//5.通过数据源获取一个数据库连接
con = dataSource.getConnection();
//6.通过数据库连接对象获取执行者对象,并对sql语句进行预编译
pst = con.prepareStatement(sql);
//7.通过执行者对象获取参数的源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//8.通过参数源信息对象获取参数的个数
int count = parameterMetaData.getParameterCount();
//9.判断参数数量是否一致
if(count != objs.length) {
throw new RuntimeException("参数个数不匹配");
}
//10.为sql语句占位符赋值
for(int i = 0; i < objs.length; i++) {
pst.setObject(i+1,objs[i]);
}
//11.执行sql语句并接收结果
result = pst.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//12.释放资源
DataSourceUtils.close(con,pst);
}
//13.返回结果
return result;
}
}
接口(当调用其方法的时候会自动找到调用的实现类)
package com.itheima05.handler;
import java.sql.ResultSet;
/*
用于处理结果集方式的接口
*/
public interface ResultSetHandler<T> {
<T> T handler(ResultSet rs);
}
连接池底层实现可以使用的设计模式
该章节有助于更好的理解对JDBC连接对象和连接池的池化底层的具体操作
首先需要明确的是:
-
接口DataSource,Connection和DriveManage的关系?
所有的连接池对象都必须实现DataSource接口以规范操作,统一都提供getConnection()方法来获取由厂商的注册器创建出来的连接对象,再在方法中书写逻辑以实现对池子连接个数的控制。
DriveManager主要是用于提供一个连接对象给用户,一共提供了两个注册器,在mysql-connector-java的jar包 META-INFO->services->java.sql.Driver中可查看
com.mysql.jdbc.Driver com.mysql.fabric.jdbc.FabricMySQLDriver
Connection也是一个接口,其中提供close()方法,作用是直接关闭连接对象。通过DriveManager创建出来的这一个连接对象是基准对象,执行该对象的close()方法则直接关闭连接,但如果在关闭的时候,通过装饰者,适配器,动态代理等方式重写接口中的close(),对原有逻辑进行修改,则可以达到连接池中的close()的效果,仅是归还连接对象到池中,并未关闭。 Connection的close()方法可以以网络交互的形式切断数据库和java的远程连接。只要遇到基准连接对象的close()方法则通过网络下线的方式断开连接。
-
使用常规的获取连接对象,返回的这个对象是返回的哪一个包的对象,与Connection接口有什么关联?
通过DriveManager.getConnection()的方式获取的连接对象,实际上返回的是一个com.mysql.jdbc.JDBC4Connection@5ecddf8f对象,所以最终这个Connection接口是由JDBC4Connection这个类进行实现的。
其次要明白连接池和连接对象的close()方法是怎样的关系:
C3P0,Druid等连接池的底层都会有一个连接池的类,在该类被创建的同时,则往集合成员变量中存储指定个数的连接对象,
public class MyDataSource implements DataSource {
//1.准备一个容器。用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
//2.定义静态代码块,获取多个连接对象保存到容器中
static{
for(int i = 1; i <= 10; i++) {
Connection con = JDBCUtils.getConnection();
pool.add(con);
}
}
//2.也可以提供构造器来存入到集合中
public MyDataSource(){
for(int i = 1; i <= 10; i++) {
Connection con = JDBCUtils.getConnection();
pool.add(con);
}
}
}
又由于实现了DataSource接口,就一定会重写接口中的getConnetion()方法来提供获取连接的途径,此时则可以在该方法中将close方法重写为不关闭资源,重新还回池中的逻辑重写。
但是此处案例中的MySource类不能通过继承JDBC4Connection类的close方法进行替换close,由于JDBC4Connection提供的基础连接对象是不可以强转为子类的,子类不能接受父类引用,所以最终即使重写了close实现了逻辑,但是也会出现运行时错误。
代码如下:
public class MyConnection1 extends JDBC4Connection{//1.定义一个类,继承JDBC4Connection
//2.定义Connection连接对象和容器对象的成员变量
private Connection con;
private List<Connection> pool;
//3.通过有参构造方法为成员变量赋值
public MyConnection1(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToConnectTo, String url,Connection con,List<Connection> pool) throws SQLException {
super(hostToConnectTo, portToConnectTo, info, databaseToConnectTo, url);
this.con = con;
this.pool = pool;
}
//4.重写close方法,完成归还连接
@Override
public void close() throws SQLException {
pool.add(con);
}
}
所以要使用设计模式对close方法进行重写。下面有三种模式可以实现这个功能,但是都有弊端,直到
使用动态代理才是最理想的状态
装饰者模式
定义:在原有功能的基础上对其进行增强或者更换。前提是必须至少有一个基础类实现父接口,在这一个或者多个实现类的基础上再进行功能的增强或修改。提供案例供理解
这是一个父接口,提供一个重写方法。
public interface KTV {
//提供一个接口 以规范唱歌
public void sing();
}
这是一个父接口的基础实现类,重写方法,实现某一基础功能,第一代的功能。
public class baseKTV implements KTV {
@Override
public void sing() {
System.out.println("基础KTV,仅唱歌");
}
}
这是一个对功能进行升级的包装类,在传入的这个类的基础上再次进行增强。
public class lightKTV implements KTV {
//定义成员变量
private KTV ktv;
//提供构造方法
public lightKTV(KTV ktv){
this.ktv=ktv;
};
@Override
public void sing() {
ktv.sing();
System.out.println("我不管你传递的是什么,或者多少层,我只增强我自己这一趴的,我要灯光");
}
}
这是另外一个对功能进行升级的包装类,此类的方法如果被调用,则不进行升级而是直接修改内容。(类比修改close)
public class SpecialService implements KTV {
//定义变量
private KTV ktv;
//提供构造方法
public SpecialService(KTV ktv){
this.ktv=ktv;
};
@Override
public void sing() {
//ktv.sing(); 原有传入的包装类或者基础类不再生效,因为没有调用其方法
System.out.println("我不管你传递的是什么,或者多少层,我只增强我自己这一趴的,我要特殊服务");
}
}
-
装饰者模式总结
:所有的包装类和基础类都是兄弟关系,只是在包装类的成员变量处定义了一个父类接口,所有的实现类均可以传入从而实现上一层的包装。这只是该设计模式的其中一种包装做法,并不是所有的装饰者模式都这个做法。
使用这个设计模式需要定义一个类,实现Connection接口,但是这个接口中有几十个待重写的方法,而我只需要修改其中一个。又因为,修改这个close的时候需传入connection这个连接对象,为了所有的方法都使用这个接口进行操作,则每一个都必须进行修改
导致所有代码过于冗余和没有必要性。故不采用。
代码如下(小部分):
/*
1.定义一个类,实现Connection接口
2.定义连接对象和连接池容器对象的成员变量
3.通过有参构造方法为成员变量赋值
4.重写close方法,完成归还连接
5.剩余方法,还是调用原有的连接对象中的功能即可
*/
//1.定义一个类,实现Connection接口
public class MyConnection2 implements Connection{
//2.定义连接对象和连接池容器对象的成员变量
private Connection con;
private List<Connection> pool;
//3.通过有参构造方法为成员变量赋值
public MyConnection2(Connection con,List<Connection> pool) {
this.con = con;
this.pool = pool;
}
//4.重写close方法,完成归还连接
@Override
public void close() throws SQLException {
pool.add(con);
}
//5.剩余方法,还是调用原有的连接对象中的功能即可
@Override
public Statement createStatement() throws SQLException {
return con.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return con.prepareStatement(sql);
}
适配器模式
代码如下:
/*
1.定义一个类,继承适配器类
2.定义连接对象和连接池容器对象的成员变量
3.通过有参构造为变量赋值
4.重写close方法,完成归还连接
*/
//1.定义一个类,继承适配器类
public class MyConnection3 extends MyAdapter {
//2.定义连接对象和连接池容器对象的成员变量
private Connection con;
private List<Connection> pool;
//3.通过有参构造为变量赋值
public MyConnection3(Connection con,List<Connection> pool) {
super(con);
this.con = con;
this.pool = pool;
}
//4.重写close方法,完成归还连接
@Override
public void close() {
pool.add(con);
}
}
/*
1.定义一个适配器类。实现Connection接口
2.定义连接对象的成员变量
3.通过有参构造为变量赋值
4.重写所有的抽象方法(除了close)
*/
public abstract class MyAdapter implements Connection {
//2.定义连接对象的成员变量
private Connection con;
//3.通过有参构造为变量赋值
public MyAdapter(Connection con) {
this.con = con;
}
//4.重写所有的抽象方法(除了close)
//小部分
@Override
public Statement createStatement() throws SQLException {
return con.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return con.prepareStatement(sql);
}
-
在这个类当中就只是重写了close这一个方法而已,相比装饰者模式把接口所有的方法集中到一个类中要好一点,但是依然需要在父类中把每一个类的方法都替换成我们传入的这个连接对象去执行,相当于还是我们自己手写的。代码过于繁重,故不采用。
动态代理(最终类型)
动态代理的底层逻辑(此处为核心逻辑):当调用Proxy这个代理类的时候,底层会在内存中创建一个代理类对象,通过传入的接口进行反射,在代理类对象中生成接口中的所有方法。在每一个方法中都有这两句话
1.获取本方法的方法名。
2.执行在InvocationoHandler中对应的invoke方法。
需要注意的是:
1. 代理对象和被代理对象必须实现同一个接口,故这两个对象是属于兄弟关系,所以在返回代理对象的时候必须已这个接口来接收,而不是被代理对象来接收。
2. 由于是用接口反射出来的方法,所以当被代理对象在执行自己的特有方法时,生效,而代理对象根本不能使用特有方法,因为没有。
3. 因为是需要对这个接口中的某些方法进行增强或者修改,所以要对方法进行判断名字,需要的留下修改,不需要的照常执行原有逻辑
综上,对close方法进行重写和实现DataSource接口进行整合
代码如下(完整):
public class MyDataSource implements DataSource {
//1.准备一个容器。用于保存多个数据库连接对象
private static List<Connection> pool = Collections.synchronizedList(new ArrayList<>());
//2.定义静态代码块,获取多个连接对象保存到容器中
static{
for(int i = 1; i <= 10; i++) {
Connection con = JDBCUtils.getConnection();
pool.add(con);
}
}
//4.提供一个获取连接池大小的方法
public int getSize() {
return pool.size();
}
/*
动态代理方式
*/
@Override
public Connection getConnection() throws SQLException {
if(pool.size() > 0) {
Connection con = pool.remove(0);
Connection proxyCon = (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")) {
//归还连接
pool.add(con);
return null;
}else {
//以下是原有的Connection方法,执行原有逻辑
return method.invoke(con,args);
}
}
});
return proxyCon;
}else {
throw new RuntimeException("连接数量已用尽");
}
}
//3.重写getConnection方法,用于返回一个连接对象
// @Override
// public Connection getConnection() throws SQLException {
// if(pool.size() > 0) {
// Connection con = pool.remove(0);
// //通过自定义的连接对象 对原有的连接对象进行包装
// //MyConnection2 myCon = new MyConnection2(con,pool);
// MyConnection3 myCon = new MyConnection3(con,pool);
// return myCon;
// }else {
// throw new RuntimeException("连接数量已用尽");
// }
// }
//以下的方法均为DataSource的重写方法,不做任何变动
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
进阶总结
-
至此已经将JDBC的全部内容,包括mybatis底层框架结构,连接池底层结构进行了统一的梳理。
-
JDBC的作用是连接数据库与Java之间在持久层的操作,后期会有三大组件之一的mybatis进行统一管理。
-
连接池的作用是为了简化JDBC频繁的创建和关闭资源的操作,减轻系统内存压力。
nection方法,用于返回一个连接对象
// @Override
// public Connection getConnection() throws SQLException {
// if(pool.size() > 0) {
// Connection con = pool.remove(0);
// //通过自定义的连接对象 对原有的连接对象进行包装
// //MyConnection2 myCon = new MyConnection2(con,pool);
// MyConnection3 myCon = new MyConnection3(con,pool);
// return myCon;
// }else {
// throw new RuntimeException(“连接数量已用尽”);
// }
// }//以下的方法均为DataSource的重写方法,不做任何变动
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}@Override
public T unwrap(Class iface) throws SQLException {
return null;
}@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
# 进阶总结
1. 至此已经将JDBC的全部内容,包括mybatis底层框架结构,连接池底层结构进行了统一的梳理。
2. JDBC的作用是连接数据库与Java之间在持久层的操作,后期会有三大组件之一的mybatis进行统一管理。
3. 连接池的作用是为了简化JDBC频繁的创建和关闭资源的操作,减轻系统内存压力。
4. 元数据主要用于在底层与反射等技术一起使用来组成框架体系,日常编码基本不用。