青之峰网站建设,网站备案信息如何下载,我想学室内设计怎么学,百度怎样做网站本文全面的介绍了JDBC的相关知识#xff0c;包括其基础与高级应用、Service层的事务管理、ThreadLocal#xff08;本地线程变量#xff09;、 数据库连接池、Commons Dbutils的原理及其使用#xff01; 文章目录 一、JDBC基础1.1JDBC介绍1.2JDBC的核心类与接口1.java.sql.D… 本文全面的介绍了JDBC的相关知识包括其基础与高级应用、Service层的事务管理、ThreadLocal本地线程变量、 数据库连接池、Commons Dbutils的原理及其使用 文章目录 一、JDBC基础1.1JDBC介绍1.2JDBC的核心类与接口1.java.sql.DriverManager类 (驱动管理器)2.java.sql.Connection接⼝ (数据库连接)3.java.sql.Connection接⼝ (数据库连接)4.java.sql.ResultSet接⼝ 结果集 1.3SQL注⼊问题1.4 JDBC开发步骤 二、JDBC高级应用2.1 三层架构与面向结构编程2.1 封装2.2 Service层的事务管理2.3 ThreadLocal本地线程变量2.4 数据库连接池2.5 Commons DbutilsApache提供的dao层工具类1原理自定义DaoUtils实现通用2Apache的DbUtils 一、JDBC基础 1.1JDBC介绍 JDBC (全称Java DataBase Contectivity) Java与数据库的连接数据库编程。JDBC 是Java语⾔JDK为完成数据库的访问操作提供的⼀套统⼀的标准驱动包下载 下载驱动jar包下载地址https://mvnrepository.com/打开⽹址搜索 mysql 。开发者通过JDK提供的规范与数据库厂商提供的驱动将驱动类加载到程序中使用进而达到通过程序操作数据库 1.2JDBC的核心类与接口 1.java.sql.DriverManager类 (驱动管理器) 注册驱动创建数据库连接 1注册驱动 在Driver类中的静态初始化块中注册驱动DriverManager.registerDriver(new Driver()); 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(Cant register driver!);}}
}在我们的应⽤程序中⼿动注册驱动的代码也可以省略 【Class.forName(“com.mysql.cj.jdbc.Driver”);】 如果我们没有⼿动注册驱动驱动管理器在获取连接的时候发现没有注册驱动则读取 驱动jar/META-INF/servicesjava.sql.Driver⽂件中配置的驱动类路径进⾏注册 不推荐 2获取连接 // url 数据库服务器的地址
// username 数据库连接⽤⼾名
// password 数据库连接密码
Connection connection DriverManager.getConnection(url, root,123456);2.java.sql.Connection接⼝ (数据库连接) Connection对象表⽰Java应⽤程序与数据库之间的连接 通过Connection接⼝对象获取执⾏SQL语句的Statement对象完成数据的事务管理 1 获取Statement对象 Statement接⼝: 编译执⾏静态SQL指令 Statement statement connection.createStatement();PreparedStatement接⼝继承了Statement接⼝预编译动态SQL指令解决SQL注⼊问题 PreparedStatement preparedStatement connection.prepareStatement(sql);CallableStatement接⼝继承了PreparedStatement接⼝可以调⽤存储过程 CallableStatement callableStatement connection.prepareCall(sql);2事务管理 //开启事务关闭事务⾃动提交
connection.setAutoCommit(false);
//事务回滚
connection.rollback();
//提交事务
connection.commit();3.java.sql.Connection接⼝ (数据库连接) —用于编译、执行SQL指令 // 执⾏DML操作的SQL指令返回值是受影响行数
int row statement.executeUpdate(sql);
// 执⾏DQL操作的SQL指令返回结果集
ResultSet rs statement.executeQuery(sql);4.java.sql.ResultSet接⼝ 结果集 — ResultSet接⼝对象表⽰查询操作返回的结果集提供了便利的⽅法⽤于获取结果集中的数据 //res.next()用于判断下一个位置是否还有值初始时位于首元素之前while (res.next()){//res.getInt(tid)通过数据库字段名获取数据//res.getInt(2)通过字段列标获取数据列标从1开始teachernew Teacher(res.getInt(tid),res.getString(tname),res.getString(gender),res.getDate(workingdate),res.getString(workgrade));}1.3SQL注⼊问题 1什么是SQL注⼊问题 在JDBC操作SQL指令编写过程中如果SQL指令中需要数据我们可以通过字符串拼接的形式将参数拼接到SQL指令中如 String sql delete from books where book_ids; (s就是拼接到SQL中的变量)使⽤字符串拼接变量的形式来设置SQL语句中的数据可能会导致因变量值的改变引起SQL指令的原意发⽣改变 这就被称为SQL注⼊。SQL注⼊问题是需要避免的例如 如果s的值为1SQL指令 delete from books where book_id1 如果s的值为1 or 11SQL指令delete from books where book_id1 or 11, 那么SQL中的条件则是⼀个恒等式sql指令发生变化 2如何解决SQL注⼊问题 使⽤PreparedStatement进⾏SQL预编译解决SQL注⼊问题 在编写SQL指令时如果SQL指令中需要参数⼀律使⽤?参数占位符如果SQL指令中有?在JDBC操作步骤中不再使⽤Statement⽽是从Conection对象获取PreparedStatement对SQL指令进⾏预编译 PreparedStatement preparedStatement connection.prepareStatement(sql);预编译完成之后通过PreparedStatement对象给预编译后的SQL指令的?赋值 prepareadStatement.setInt(参数占位符序号,值);prepareadStatement.setString(参数占位符序号,值); SQL指令中的所有?完成赋值之后通过PreparedStatement执⾏SQL执⾏SQL时不再加载SQL语句 int row prepareadStatement.executeUpdate();ResultSet rs preparedStatement.executeQuery(); 1.4 JDBC开发步骤 加载驱动 // 1.加载驱动类
Class.forName(com.mysql.jdbc.Driver);创建连接
//2.建立连接
conn DriverManager.getConnection(jdbc:mysql://localhost:3306/javaTest, root, 123456);编写sql指令 //3.编写sql语句String sqlinsert into student values(2,李四);创建执行体
//4.创建执行体stmt conn.createStatement();执行sql指令
//5.执行sql语句int row stmt.executeUpdate(sql);处理执行结果集
//6.处理结果if (row-1){System.out.println(插入成功);}else{System.out.println(插入失败);}释放占用资源 //释放资源try {stmt.close();conn.close();} catch (SQLException throwables) {throwables.printStackTrace();}二、JDBC高级应用 2.1 三层架构与面向结构编程 1三层架构 三层架构是指视图层 View、服务层 Service与持久层 Dao。它们分别完成不同的功能。 View 层用于接收用户提交请求。Service 层用以实现系统的业务逻辑Dao 层用以实现直接对数据库进行操作。 2面向接口 (抽象) 编程 面向接口编程程序设计时考虑易修改、易扩展为Service层和DAO层设计接口便于未来更换实现类 在三层架构程序设计中采用面向接口(抽象)编程。 实现方式 上层对下层的调用是通过下层接口实现的而下层对上层的真正服务提供者是下层接口的实现类 特点 服务标准接口规范相同是相同的服务提供者实现类可以更换。这就实现了层间解耦合与编程的灵活性 2.1 封装 1DAO封装 — (DAO Data Access Object 数据访问对象) 将对数据库中同⼀张数据表的JDBC操作⽅法封装到同⼀个Java类中这个类就是访问此数据表的 数据访问对象 2DTO封装 — Data Transfer Object 数据传输对象实体类 在Java程序中创建⼀个属性与数据库表匹配的类通过此类的对象封装查询到的数据我们把⽤于传递JDBC增删查改操作的数据的对象称之为 数据传输对象 2.2 Service层的事务管理 1事务的概念 事务是指是程序中一系列严密的逻辑操作而且所有操作必须全部成功完成否则在每个操作中所作的所有更改都会被撤消。 2事务的四大特性 原子性Atomicity操作这些指令时要么全部执行成功要么全部不执行。只要其中一个指令执行失败所有的指令都执行失败数据进行回滚回到执行指令前的数据状态。一致性Consistency 事务的执行使数据从一个状态转换为另一个状态但是对于整个数据的完整性保持稳定。隔离性Isolation 隔离性是当多个用户并发访问数据库时比如操作同一张表时数据库为每一个用户开启的事务不能被其他事务的操作所干扰多个并发事务之间要相互隔离。持久性Durability 当事务正确完成后它对于数据的改变是永久性的。 3事务的隔离级别 第一种隔离级别Read uncommitted(读未提交) 如果一个事务已经开始写数据则另外一个事务不允许同时进行写操作但允许其他事务读此行数据该隔离级别可以通过“排他写锁”但是不排斥读线程实现。这样就避免了更新丢失却可能出现脏读也就是说事务B读取到了事务A未提交的数据 解决了更新丢失但还是可能会出现脏读 第二种隔离级别Read committed(读提交) 如果是一个读事务(线程)则允许其他事务读写如果是写事务将会禁止其他事务访问该行数据该隔离级别避免了脏读但是可能出现不可重复读。事务A事先读取了数据事务B紧接着更新了数据并提交了事务而事务A再次读取该数据时数据已经发生了改变。 解决了更新丢失和脏读问题 第三种隔离级别Repeatable read(可重复读取) 可重复读取是指在一个事务内多次读同一个数据在这个事务还没结束时其他事务不能访问该数据(包括了读写)这样就可以在同一个事务内两次读到的数据是一样的因此称为是可重复读隔离级别读取数据的事务将会禁止写事务(但允许读事务)写事务则禁止任何其他事务(包括了读写)这样避免了不可重复读和脏读但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。 解决了更新丢失、脏读、不可重复读、但是还会出现幻读 第四种隔离级别Serializable(序列化) 提供严格的事务隔离它要求事务序列化执行事务只能一个接着一个地执行但不能并发执行如果仅仅通过“行级锁”是无法实现序列化的必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别同时代价也是最高的性能很低一般很少使用在该级别下事务顺序执行不仅可以避免脏读、不可重复读还避免了幻读 解决了更新丢失、脏读、不可重复读、幻读(虚读) 隔离级别脏读不可重复度幻读Read uncommitted(读未提交)√√√Read committed(读提交)×√√Repeatable read(可重复读取)××√Serializable(序列化)××× 脏读 所谓脏读是指一个事务中访问到了另外一个事务未提交的数据 幻读 一个事务读取2次得到的记录条数不一致 不可重复读 一个事务读取同一条记录2次得到的结果不一致 MySQL事务管理 start transaction (开启事务)end transaction (结束事务)rollback (事务回滚)commit (提交事务) 4JDBC事务管理 ⼀个事务中的多个DML操作需要基于同⼀个数据库连接创建连接之后设置事务⼿动提交关闭⾃动提交connection.setAutoCommit(false);当当前事务中的所有DML操作完成之后⼿动提交 connection.commit();当事务中的任何⼀个步骤出现异常在catch代码块中执⾏事务回滚 connection.rollback(); 5Service层简介 DAO负责特定的数据库操作业务由service层进⾏管理 业务指的是完成某一功能软件提供的一个功能 例如A给B转帐其包含A账户减钱B账户加钱其整体为一个业务的操作 Servcie进⾏业务处理Service业务处理过程如果需要数据库操作则调⽤DAO完成Service层的一个业务可能需要调用一个或若干个DAO层对数据库进行处理 6Service层事务管理 事务管理要满⾜以下条件 多个DML操作需使⽤同⼀个数据库连接第⼀个DML操作之前设置事务⼿动提交所有DML操作执⾏完成之后提交事务出现异常则进⾏事务回滚 1.需要解决的问题 Servcie层事务可能涉及多个DAO层其中多个数据库的DML操作是相互独⽴的如何保证所有DML要么同时成功要么同时失败呢 2.解决办法让Service事务中的多个DML使⽤同⼀个数据库连接 方式一在Service获取连接对象将连接对象传递到DAO中
分析 DAO类中的Connection对象需要通过Service传递给进来这种对象传递本来也⽆可厚⾮但是当我们通过⾯向接⼝开发时⾯向接⼝是为了能够灵活的定义实现类容易造成接⼝的冗余接⼝污染接口污染典型示例 不同的数据库的数据库连接对象是不同的MySQL的连接对象是Connection 但Oracle数据库则不是
方式二使⽤ThreadLocal容器实现多个DML操作使⽤相同的连接
不使用自定义List集合的原因 存储Connection的容器可以使⽤List集合使⽤List集合做容器在多线程并发编程中会出现资源竞争问题多个并发的线程使⽤的是同⼀个数据库连接对象我们的要求是同⼀个事务中使⽤同⼀个连接⽽并⾮多个线程共享同一个连接为了解决并发编程的连接对象共享问题我们可以 使⽤ThreadLocal作为数据库连接对象的容器 2.3 ThreadLocal本地线程变量 1ThreadLocal简介 ThreadLocal 叫做本地线程变量意思是说ThreadLocal 中填充的的是当前线程的变量该变量对其他线程而言是封闭且隔离的ThreadLocal 为变量在每个线程中创建了一个副本这样每个线程都可以访问自己内部的副本变量。 2ThreadLocal的应用 在进行对象跨层传递的时候使用ThreadLocal可以避免多次传递打破层次间的约束。线程间数据隔离进行事务操作用于存储线程事务信息。数据库连接Session会话管理。 3ThreadLocal常用的方法 set()方法 ThreadLocal对象.set() 会为ThreadLocal对象调用set()方法所在的线程中的ThreadLocal.ThreadLocalMap threadLocals null;进行赋值,赋值类型为一个Map,Map的键为当前的ThreadLocal对象,值为所传入的值. set()的源码 public void set(T value) {
//获取当前ThreadLocal对象所在的线程
Thread t Thread.currentThread();
//获取所在线程中存储的threadLocals(ThreadLocalMap)
ThreadLocalMap map getMap(t);
//判断map是否为空
if (map ! null)//不为空,则替换值map.set(this, value);
else//为空则为当前线程创建ThreadLocalMap对象并为threadLocals赋值createMap(t, value);
}void createMap(Thread t, T firstValue) {
//为线程t中的threadLocals创建一个ThreadLocalMap对象进行赋值
t.threadLocals new ThreadLocalMap(this, firstValue);
}ThreadLocalMap 为 ThreadLocal 的一个静态内部类里面定义了Entry 来保存数据。而且是继承的弱引用。在Entry内部使用ThreadLocal作为key使用我们设置的value作为value。 对于每个线程内部有个ThreadLocal.ThreadLocalMap 变量存取值的时候也是从这个容器中来获取。 get()方法 public T get() {//获取当前ThreadLocal对象所在的线程Thread t Thread.currentThread();//获取所在线程中存储的threadLocals(ThreadLocalMap)ThreadLocalMap map getMap(t);//判断线程中存储的ThreadLocalMap是否为空if (map ! null) {//不为空则以当前ThreadLocal对象为键获取Entry对象ThreadLocalMap.Entry e map.getEntry(this);//判断Entry对象是个为空if (e ! null) {//不为空则返回Entry对象的值SuppressWarnings(unchecked)T result (T)e.value;return result;}}//线程中ThreadLocalMap为空或者未存储以当前ThreadLocal对象为键的Entry对象时设置初始值return setInitialValue();
}//设置为线程中ThreadLocalMap的初始值
private T setInitialValue() {//设置当前值为nullT value initialValue();//获取当前ThreadLocal对象所在的线程Thread t Thread.currentThread();//获取所在线程中存储的threadLocals(ThreadLocalMap)ThreadLocalMap map getMap(t);//判读map是否为空if (map ! null)//map不为空向map中添加一个以当前ThreadLocal对象为键。值为空的Entry对象map.set(this, value);else//map为空则为当前线程的threadLocals进行创建对象初始化createMap(t, value);return value;
}protected T initialValue() {return null;
}4ThreadLocal的内存泄流问题 内存泄漏原因 当ThreadLocal为null时也就是要被垃圾回收器回收了但是此时我们的ThreadLocalMapthread 的内部属性生命周期和Thread的一样它不会回收这时候就出现了一个现象。那就是ThreadLocalMap的key没了但是value还在这就造成了内存泄漏。 解决方法: 用完ThreadLocal后执行remove操作避免出现内存溢出情况。所以 如同 lock 的操作 最后要执行解锁操作一样ThreadLocal使用完毕一定记得执行remove 方法清除当前线程的数值。如果不remove 当前线程对应的VALUE ,就会一直存在这个值。 5强引用,弱引用,软引用 强引用普通的引用强引用指向的对象不会被回收软引用仅有软引用指向的对象只有发生gc且内存不足才会被回收弱引用仅有弱引用指向的对象只要发生gc就会被回收。 6多线程环境下ThreadLocal在事务中操作可能出现的问题 在阐述此问题时需要简要介绍一下mysql数据库的事务默认隔离级别Repeatable read(可重复读取) 第三种隔离级别Repeatable read(可重复读取) 可重复读取是指在一个事务内多次读同一个数据在这个事务还没结束时其他事务不能访问该数据(包括了读写)这样就可以在同一个事务内两次读到的数据是一样的因此称为是可重复读隔离级别读取数据的事务将会禁止写事务(但允许读事务)写事务则禁止任何其他事务(包括了读写)这样避免了不可重复读和脏读但是有时可能会出现幻读。 场景 存在两个线程因为ThreadLocal保存数据库连接变量可以保证两个线程各自拥有自己的数据库连接一般在操作各自线程任务的事务时不会出现冲突和干扰现在有如下场景两个线程同时操作同一个数据中的同一张表a线程使1号用户给3号用户转账b线程使1号用户给2号客户转账a、b线程同时运行抢夺cpu的执行权此时程序中只会有一个线程执行成功另一个线程执行失败 原因mysql默认为Repeatable read(可重复读取)隔离级别ab两线程同时操作写违反了mysql的事务隔离级别根本原因每个线程操作的数据库中表的副本在对数据库的表操作过程中会有以下验证当a线程拿到表的副本后会记录拿到副本当时表内容状态T当它修改完自己拿到的副本后准备提交给数据库时会将自己记录的表状态T 与提交前数据库中此表状态进行比对如果二者不一致则线程a不会提交自己的副本给数据库并会报错比对结果表明在a线程修改表内容的期间有其他线程对表进行了更改所以会报错 2.4 数据库连接池 1.引入数据库连接池的原因 如果每个JDBC操作需要数据库连接都重新创建使⽤完成之后都销毁我们的JVM会因为频繁的创建、销毁连接⽽占⽤额外的系统资源。数据库连接本质上是可被重⽤的资源当⼀个JDBC操作完成之后其创建的连接是可以被其他JDBC操作使⽤的基于这个特性 我们可以创建⼀个 存放数据库连接的容器 连接池连接池是有最⼤容量的当我们要进⾏JDBC操作时直接从这个容器中获取连接: 如果容器中没有空闲的连接且连接池中连接的个数没有达到最⼤值则创建新的数据库连接存⼊连接池并给这个操作使⽤使⽤完成之后⽆需关闭连接直接归还这个容器中即可如果容器中没有空闲的连接且连接池中连接的个数达到最⼤值当前操作就会进⾏等待等待连接池中的某个连接被归还归还之后再使⽤如果容器中有空闲连接则⽆需创建新的连接直接从容器中获取这个空闲连接进⾏使⽤ 2.概念 连接池存放数据库连接对象的容器连接池作⽤对数据库连接进⾏管理减少因重复创建、销毁连接导致的系统开销 3.常用的线程池 功能dbcpdruidc3p0tomcat-jdbcHikariCP是否支持PSCache是是是否否监控jmxjmx/log/httpjmx,logjmxjmx扩展性弱好弱弱弱sql拦截及解析无支持无无无代码简单中等复杂简单简单特点依赖于common-pool阿里开源功能全面历史久远代码逻辑复杂且不易维护优化力度大功能简单起源于boneCP连接池管理LinkedBlockingDeque数组FairBlockingQueuethreadlocalCopyOnWriteArrayList 由于boneCP被hikariCP替代并且已经不再更新boneCP没有进行调研。proxool网上有评测说在并发较高的情况下会出错proxool便没有进行调研。druid的功能比较全面且扩展性较好比较方便对jdbc接口进行监控跟踪等。c3p0历史悠久代码及其复杂不利于维护。并且存在deadlock的潜在风险。基于连接池的性能、使⽤的便捷性、连接监控等多⽅⾯综合情况druid是⽬前企业应⽤中使⽤最 ⼴泛的Hikari在SpringBoot中默认集成性能是诸多竞品中最好的 4.Druid线程池的使用 参考我的另一篇博客 Druid连接池简介及其使用 2.5 Commons DbutilsApache提供的dao层工具类 1原理自定义DaoUtils实现通用 核心思想使用泛型参考我的另一篇博客 DaoUtils实现通用增、删、改、查 2Apache的DbUtils 核心思想反射注意事项创建QueryRunner对象时不能使用无参构造方法需要传入一个连接池对象配合线程池使用参考我的另一篇博客 DaoUtils实现通用增、删、改、查