🌈 个人主页:十二月的猫-CSDN博客
🔥 系列专栏: 🏀系统学javaWeb开发_十二月的猫的博客-CSDN博客💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光
目录
1. 前言
进行JavaWeb的学习不可避免地需要和数据库打交道,而一个java程序想要和数据库连接就需要——JDBC技术
1.1 数据的存储
我们在开发Java程序时,数据都是存储在内存中,属于临时存储,当程序停止或重启时,内存中的数据就丢失了!我们为了解决数据的长期存储问题,有如下解决方案:
- 数据通过I/O流技术,存储在本地磁盘中,解决了持久化问题,但是没有结构和逻辑,不方便管理和维护。
- 通过关系型数据库,将数据按照特定的格式交由数据库管理系统维护。关系型数据库是通过库和表分隔不同的数据,表中数据存储的方式是行和列,区分相同格式不同值的数据。
因此,数据存储一定需要依赖数据库以及数据库管理系统
1.2 数据的操作
数据存储在数据库,仅仅解决了我们数据存储的问题,但当我们程序运行时,需要读取数据,以及对数据做增删改的操作,那么我们如何通过Java程序对数据库中的数据做增删改查呢?
答案是:通过Java程序控制数据库管理系统,从而完成对数据库中数据的操作
2. JDBC
2.1 JDBC是什么
-
JDBC:Java Database Connectivity,意为Java数据库连接。
-
JDBC是Java提供的一组独立于任何数据库管理系统的API。
-
Java提供接口规范,由各个数据库厂商提供接口的实现,厂商提供的实现类封装成jar文件,也就是我们俗称的数据库驱动jar包。
-
学习JDBC,充分体现了面向接口编程的好处,程序员只关心标准和规范,而无需关注实现过程。
2.2 JDBC的本质
- 一组规范的接口
- 面向接口编程的体现
- 通过JDBC+驱动控制不同的数据库管理系统,从而控制数据库
2.3 JDBC的核心
-
接口:
-
为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。
-
接口存储在java.sql和javax.sql包下。
-
-
实现:
-
因为各个数据库厂商的DBMS软件各有不同,那么各自的内部如何通过SQL实现增、删、改、查等操作管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。
-
厂商将实现内容和过程封装成jar文件,我们程序员只需要将jar文件引入到项目中集成即可,就可以开发调用实现过程操作数据库了。
-
本质就是:接口规范化+实现自由化
3. JDBC快速入门
3.1 环境搭建
-
准备数据库。(对于MySQL数据库安装不了解的友友可以看我的这篇文章:【一篇搞定】MySQL安装与配置_5.2 配置mysql-CSDN博客
-
官网下载数据库连接驱动jar包。https://downloads.mysql.com/archives/c-j/
-
创建Java项目,在项目下创建lib文件夹,将下载的驱动jar包复制到文件夹里。
-
选中lib文件夹右键->Add as Library,与项目集成。
-
编写代码
3.2 编写代码(架构)
3.2.1 创建数据库
create table t_fruit
(
f_id int auto_increment comment '水果编号' primary key,
f_name varchar(100) not null comment '水果名字',
price double(6,2) not null comment '水果价格',
f_count int not null comment '水果库存',
remark VARCHAR(100) comment '水果备注'
);
insert into t_fruit (f_name,price,f_count,remark)
values ('apple',100.00,500,'China'),
('banana',56.00,600,'China'),
('orange',121.00,300,'China'),
('watermelon',23.00,900,'China')
3.2.2 为表建立实体类
在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行数据就对应的是Java中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,这个载体就是实体类!
ORM(Object Relational Mapping)思想,对象到关系数据库的映射,作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数据对应一个对象,一个列对应一个属性!
当下JDBC中这种过程我们称其为手动ORM。后续我们也会学习ORM框架,比如MyBatis、JPA等。
1、首先在Idea中建立如下文件夹:
fruit:对应数据库中的表的名字叫做fruit
dao:dao层,建立在fruit下面,表示对fruit表格的增删改查操作
pojo:pojo层,建立在fruit下面,表示fruit表格在java程序中的实体对象
2、在pojo文件夹在建立fruit对象:
package com.javaWebEx.fruit.pojo;
public class fruit {
//数据库中列名用下划线,这里用驼峰
private Integer fId;//f_id
private String fName;//f_name
private Double price;//price
private Integer fCount;//f_count
private String remark;//remark
public fruit(Integer fId, String fName, Double price, Integer fCount, String remark) {
this.fId = fId;
this.fName = fName;
this.price = price;
this.fCount = fCount;
this.remark = remark;
}
public fruit() {}
public Integer getfId() {
return fId;
}
public void setfId(Integer fId) {
this.fId = fId;
}
public String getfName() {
return fName;
}
public void setfName(String fName) {
this.fName = fName;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getfCount() {
return fCount;
}
public void setfCount(Integer fCount) {
this.fCount = fCount;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return "Fruit{" +
"fId=" + fId +
", fName='" + fName + '\'' +
", price=" + price +
", fCount=" + fCount +
", remark=" + remark +
'}';
}
}
关键点:
1、这里的构造函数、set、get函数都可以利用idea自动生成
2、toString方法:当print(fruit)执行时会自动调用toString函数
3.2.3 构建API核心代码
java程序实现和数据库管理系统交互代码的核心步骤有六步,如下:
- 注册驱动【依赖的驱动类,进行安装】
- 获取连接【Connection建立连接】
- 创建发送SQL语句对象【Connection创建发送SQL语句的Statement】
- 发送SQL语句,并获取返回结果【Statement 发送sql语句到数据库并且取得返回结果】
- 结果集解析【结果集解析,将查询结果解析出来】
- 资源关闭【释放ResultSet、Statement 、Connection】
每次与DBMS打交道时我们都要遵守这六步操作 ,因此:
- 将所有对数据库表的操作都封装到DAO层中
- DAO层包括接口和实现
- 接口中包括对表的:增删改查各个操作
- 每个增删改查操作的实现都要包括上面的六步操作
- 增删改查可能包括:selectAll、selectById、insert、delete等操作。在这些操作中只要是select类型(查询类)的则有很大部分是相同的代码;同样,如果是insert、delete(更新类)也有很大部分相同代码
- 因此可以将查询类抽象出一个query函数(接口),将更新类抽象出一个update函数(接口)。并且将这两个函数放在baseDAO中,作为上面增删改查操作的基础
传统方法:一步步撰写代码(这里就不写六步的传统代码了)
我们现在来看传统六步法中有没有可以优化的地方:
- 现在的Idea也可以自动注册驱动——》不写注册驱动
- 获取连接存在:连接数量无法把控问题——》连接池处理
- Statement存在SQL注入问题——》preparedStatement代替Statement
- 每一个JDBC在1、2、6的操作都是相同的——》封装为工具类JDBCUtil
- 多线程来访问数据库时存在事务前后不一致问题——》ThreadLocal技术对连接限制
进一步分析这五个优化在代码中的关系:
- 五步优化中1、2、5体现在第四步的封装工具类JDBCUtil中
- 五步优化中3体现在BaseDAO中
- baseDAO要完成DAO操作,需要借助JDBCUtil的1、2、6步操作。自己完成3、4、5步
因此,代码编写顺序就很好确定了:
- 编写db.properties为druid连接池做配置
- 封装工具类JDBCUtil中
- BaseDAO
- FruitDAO
- FruitDAOImpl
一、编写db.properties为druid连接池做配置
- 建立resources文件夹
- 在resources中建立db.properties
- 将resources文件夹标记为资源根目录
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///atguigu
username=root
password=1557304
initialSize=10
maxActive=20
1、完整url设定如下:
//url:连接的数据库对象:jdbc:mysql://本机(阿里服务器ip号):端口号/数据库名字 String url="jdbc:mysql://localhost:3306/atguigu";
这里的username、password是所连接的数据库的账号密码(我这里连接的数据库名字是atguigu)
2、
initialSize:最小连接池中的连接数量 maxActive:最大连接池中的连接数量
二、封装工具类JDBCUtil中
package com.javaWebEx.util;
/*
* JDBC工具类
* 1、维护一个连接池对象,维护一个线程绑定变量的threadlocal对象
* 2、对外提供在TreadLocal中获取连接的方法
* 3、对外提供回收连接的方法,将要回收的连接从threadlocal中移除
* */
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 JDBCUtil {
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal=new ThreadLocal<>();//connection设定为TheadLocal使得线程中connection是唯一的
static {
Properties properties=new Properties();//配置对象
InputStream inputStream=JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");//加载db.properties到类中(以数据流形式)
try {
properties.load(inputStream);//设置配置对象
dataSource= DruidDataSourceFactory.createDataSource(properties);//利用配置对象创建连接池
} catch (IOException e) {
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection(){
try {
//从threadlocal中获取连接
Connection connection=threadLocal.get();//从TheadLocal中获取连接
//threadlocal中没有连接(第一次获取),则从连接池中加载连接到TheadLocal中
if(connection==null){
connection= dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void release(){
try {
//归还连接对象是以线程为单位的(因为一个线程只有一个连接对象)
Connection connection=threadLocal.get();
//线程中
if(connection!=null){
//从threadLocal中移除当前已经存储的Connection对象
threadLocal.remove();
//归还connection后要修改回自动提交
connection.setAutoCommit(true);
//将Connection对象归还连接池
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
这里的工具类不同于传统的工具类,用了两个技术:
- TheadLocal技术:保证每个线程连接的唯一性,使得事务原子性的实现存在可能
- Druid连接池:能够有效限制连接资源
三、BaseDAO
package com.javaWebEx.fruit.dao;
import com.javaWebEx.util.JDBCUtil;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class BaseDAO {
/**
* 通用的增删改方法
* @param sql 请求者要执行的SQL语句
* @param params SQL语句中的占位符要赋值的参数
* @return 受影响的行数
*/
public int executeUpdate(String sql,Object...params) throws SQLException {
//1、获取驱动+获取连接
Connection connection= JDBCUtil.getConnection();
//2、预编译SQL语句
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//3、确定SQL语句并处理
if(params!=null&¶ms.length>0){
for(int i=0;i<params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
//4、处理结果
int row=preparedStatement.executeUpdate();
//5、释放资源
preparedStatement.close();
//如果开启了自动提交,才自动释放连接;否则,手动提交情况下不释放连接
if(connection.getAutoCommit()){
JDBCUtil.release();
}
return row;
}
/**
* 通用的查询方法
* @param clazz 查询结果返回的对象
* @param sql 查询的预编译sql语句
* @param params 确定sql语句的占位符
* @return 单行单列、单行多列、多行多列都有可能
* @param <T> 与Class<T>合起来共同表示一个通用类
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
/*
通用查询结果:单行单列、单行多列、多行多列
单行单列:一个int结果
单行多列:一个对象(例如employee)
多行多列:list(object)
封装过程
1、返回的类型:泛型。类型不确定,使用时将结果类型提供给baseDAO
2、返回的结果:通用list。可以存储多个结果,也可以存储单个结果
3、结果的封装:反射!!要求调用者告诉baseDAO要封装对象的类对象
*/
//Class<T>:类的类型
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object...params) throws Exception{
//1:获取连接
Connection connection=JDBCUtil.getConnection();
//2、获取预编译sql
PreparedStatement preparedStatement=connection.prepareStatement(sql);
//3、确定sql
if(params!=null && params.length>0){
for(int i=0;i<params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
//4、获取并处理结果(只有得到对象的属性,才能对对象的属性赋值)
ResultSet resultSet=preparedStatement.executeQuery();
//处理结果:获取数据库表的元数据(元数据中有:列数、列名)
ResultSetMetaData resultSetMetaData=resultSet.getMetaData();
//处理结果:根据数据库表的元数据得到数据库表的列的数量
int columCount=resultSetMetaData.getColumnCount();
List<T> list=new ArrayList<>();
//处理结果(while循环一次处理完一个对象,也就是一条记录)
while(resultSet.next()){
//循环一次,代表有一个记录,创建一个对象
T t=clazz.newInstance();
for(int i=1;i<=columCount;i++){
//通过getObject操作获得列的值
Object value = resultSet.getObject(i);
//获得到的列的value值,就是t对象中的一个属性值
//获取t对象中的一个属性名
String fieldName=resultSetMetaData.getColumnLabel(i);
//通过对象的属性名来得到对象的属性
Field field=clazz.getDeclaredField(fieldName);
//为属性突破封装的private
field.setAccessible(true);
//为属性赋值
field.set(t,value);
}
list.add(t);
}
//5、释放资源
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()){
JDBCUtil.release();
}
return list;
}
/**
* 通用的查询方法————简化仅用于单行数据的查询
* @param clazz 查询结果返回的对象
* @param sql 查询的预编译sql语句
* @param params 确定sql语句的占位符
* @return
* @param <T>
* @throws Exception
*/
public <T> T executeQueryBean(Class<T> clazz,String sql,Object...params) throws Exception{
List<T> list=this.executeQuery(clazz,sql,params);
if(list==null || list.size()==0)
return null;
return list.get(0);
}
}
基本已经对每一行都做了注释,大家一定要仔细去琢磨!!!
最后一个方法仅仅是为了优化单行数据查询,不要也可以
四、FruitDAO
package com.javaWebEx.fruit.dao;
import com.javaWebEx.fruit.pojo.Fruit;
import java.sql.SQLException;
import java.util.List;
public interface FruitDAO {
/**
* 查询数据库中水果的所有数据
* @return 表中所有的数据
*/
List<Fruit> selectAll() throws Exception;
/**
* 根据水果id查询一个水果对象
* @param fId
* @return 一个水果对象
*/
Fruit selectByFId(Integer fId) throws Exception;
/**
* 新增数据库对应的新增一条水果数据
* @param fruit
* @return 受影响行数
*/
int insert(Fruit fruit) throws SQLException;
/**
* 数据库对应的修改一条水果数据
* @param fruit 数据库中想要修改的水果对象
* @return 受影响的行数
*/
int update(Fruit fruit) throws SQLException;
/**
* 数据库对应的删除一条员工数据
* @param fruit 数据库中想要删除的水果对象
* @return 受影响的行数
*/
int delete(Fruit fruit) throws SQLException;
}
FruitDAO:一个接口,里面定义了对fruit数据库表的操作。仅仅定义,并不实现
五、FruitDAOImpl
FruitDAOImpl:对FruitDAO接口的实现,实现了接口中的方法。使用baseDAO实现
package com.javaWebEx.fruit.dao;
import com.javaWebEx.fruit.pojo.Fruit;
import java.sql.SQLException;
import java.util.List;
public class FruitDaoImpl extends BaseDAO implements FruitDAO{
@Override
public List<Fruit> selectAll() throws Exception {
String sql="select f_Id as fId,f_name as fName,price as price,f_count as fCount,remark as remark from t_fruit";
return executeQuery(Fruit.class,sql,null);
}
@Override
public Fruit selectByFId(Integer fId) throws Exception {
String sql="select f_Id as fId,f_name as fName,price as price,f_count as fCount,remark as remark from t_fruit where f_id=?";
return executeQueryBean(Fruit.class,sql,1);
}
@Override
public int insert(Fruit fruit) throws SQLException {
String sql="insert into t_fruit(f_name,price,f_count,remark) values(?,?,?,?)";
return executeUpdate(sql,fruit.getfName(),fruit.getPrice(),fruit.getfCount(),fruit.getRemark());
}
@Override
public int update(Fruit fruit) throws SQLException {
String sql="UPDATE t_fruit set price=?,f_count=? ,remark=? WHERE f_name=?";
return executeUpdate(sql,fruit.getPrice(),fruit.getfCount(),fruit.getRemark(),fruit.getfName());
}
@Override
public int delete(Fruit fruit) throws SQLException {
String sql="delete from t_fruit where f_id=?";
return executeUpdate(sql,fruit.getfId());
}
}
六、构建API核心代码总结
JDBC代码实现中最复杂的部分是baseDAO的实现、JDBCUtil的实现
baseDAO实现:难点在于如何将DAO的实现抽象化,写出通用的函数
JDBCUtil的实现:难点在于如何使用TheadLocal技术、连接池技术
七、前端测试使用代码
add.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>fruit</title>
</head>
<body>
<form action="add" method="post">
名称:<input type="text" name="fname"/><br/>
价格:<input type="text" name="price"/><br/>
库存:<input type="text" name="fcount"/><br/>
备注:<input type="text" name="remark"/><br/>
<input type="submit" value="添加">
</form>
</body>
</html>
4. 总结
整体流程小总结:
- 向服务器请求add.html
- 服务器返回add.html的代码
- 浏览器解析html代码,并在前端显示出来
- 填写完内容,点击添加
- 由于action=‘add’,因此到web.xml中匹配到add对应的servlet——AddServlet
- 到AddServlet中找到doPost方法去执行,获取前端数据处理到后端数据库
如果觉得对你有帮助,辛苦友友点个赞,收个藏呀~~~
想要完整源码的可以私信我呀~~后续我也会上传到CSDN上的
猫猫未来一定会产出更多优质文章的!!