目录
一、数据库连接池
Druid 连接池的使用
二、Apache的DBUtils
实现增删改 — QueryRunner、update()
实现查询 — query、ResultSetHandler
ResultSetHandler结果集处理类
三、封装 DAO层代码 ***
通用方法再封装 — 反射封装BaseDAOImpl类
解决JDBC工具类事物问题 — ThreadLocal *
使用ThreadLocal类
总结
一、数据库连接池
为什么使用数据库连接池?
-
1.资源重用,避免每次创建和销毁连接都带来的较大系统开销
-
2.链接数有限,可以防止大量用户并发访问数据库服务器。
-
3.更快的响应速度,连接池提前进行初始化,减少了数据库连接初始化和释放过程的时间开销
参考共享单车,数量有限。
连接池的原理
连接池维护着两个容器空闲池和活动池
空闲池用于存放未使用的连接,活动池用于存放正在使用的连接,活动池中的连接使用完之后要归还回空闲池
当Java程序需要连接时,先判断空闲池中是否有连接,如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用
Java程序需要连接时,如果空闲池中没有连接了,则先判断活动池的连接数是否已经达到了最大连接数,如果未达到最大连接数,则会新创建一个连接放置到活动池,供Java程序使用
如果空闲池中没有连接了,活动池中的连接也已经达到了最大连接数,则不能新创建连接了,那么此时会判断是否等待超时,如果没有等待超时则需要等待活动池中的连接归还回空闲池
如果等待超时了,则可以采取多种处理方式,例如:直接抛出超时异常,或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用
连接池的实现 —— DataSource接口
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),所有的Java数据库连接池都需要实现该接口。 该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现
Druid 连接池的使用常见的数据库连接池
- DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- HikariCP 俗称光连接池,是目前速度最快的连接池
- Druid(德鲁伊) 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池
导入druid-1.1.10.jar
创建druid连接池的配置文件druid.properties文件,需要放置到项目src下,这是为了getResourceAsStream()方法能够方便读取 (暂未深究)。
druid.properties所需参数:
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/数据库名
username=root 用户名
password=123456 密码
initialSize=5 初始的连接数量
maxActive=10 最大连接数
maxWait=1000 等待时间
public class JDBCUtils {
//连接池 单例模式??
static DataSource dataSource;
//静态代码块
static {
//属性集对象
Properties pro = new Properties();
try {
//将配置文件信息 读取到属性集内 (将配置文件信息转为 字节流)
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
//通过DruidDataSourceFactory创建连接池
dataSource = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
//通过连接池获取链接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//测试连接池
public static void main(String[] args) throws SQLException {
for (int i = 0; i < 11; i++) {
//获取连接池链接
Connection connection = getConnection();
System.out.println("connection=" + connection);
//将链接还到池子
connection.close();
}
}
}
Class.getClassLoader.getResourceAsStream(String path)
:默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
附:Druid连接池的配置参数列表
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:使用ConfigFilter · alibaba/druid Wiki · GitHub | |
driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
其中QueryRunner类封装了SQL的执行,是线程安全的。
(1)可以实现增、删、改、查、
(2)考虑了事务处理需要共用Connection。
(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库 *** 作,能够大大减少编码量。
实现增删改 — QueryRunner、update()DBUtils相关API:
QueryRunner() ,创建QueryRunner对象,用于执行SQL语句
update(Connection conn, String sql, Object... params),执行增删改
query(String sql, ResultSetHandler
rsh, Object... params) ,执行查询 BeanListHandler<>(结果集合的泛型) 对象集合处理器
ResultSetHandler 结果集处理器,query的返回值
@Test//DBUtils 完成添加数据
public void test01() throws Exception {
//1.通过连接池工具类(刚才写的)获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建 QueryRunner 对象
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "INSERT INTO temp(name,age,gender) VALUES(?,?,?)";
//4.传入链接、sql、参数,执行链接
int i = queryRunner.update(connection, sql, "古力娜扎", 22, "女");
System.out.println(i+"行受到了影响。");
//省略了命令发送器,填装等步骤 setObject、executeUpdate...
//5.关闭资源
connection.close();
}
@Test//DBUtils 完成删除数据
public void test02() throws Exception {
//获取数据库链接 -- (类似socket)
Connection connection = JDBCUtils.getConnection();
//事物: 修改自动提交,不想删除
connection.setAutoCommit(false);
//创建queryRunner对象
QueryRunner queryRunner = new QueryRunner();
//准备Sql
String sql = "DELETE FROM temp WHERE id = ?";
//传入数据,执行链接
int i = queryRunner.update(connection, sql, 1);
System.out.println(i+"行受到了影响。");
//回滚,数据不想删
connection.rollback();
//关闭资源
connection.close();
}
@Test//DBUtils 完成修改数据
public void test03() throws Exception {
//获取链接
Connection connection = JDBCUtils.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "UPDATE temp SET salary = ? where name = ?";
//传入数据,执行链接
int i = queryRunner.update(connection, sql, 5000, "迪丽热巴");
System.out.println(i+"行受到了影响。");
//关流
connection.close();
}
实现查询 — query、ResultSetHandler
queryRunner 查询程序
query() 查询
BeanListHandler<>(结果集合的泛型) 对象集合处理器
ResultSetHandler() 结果集处理器
需要提前将查询的表封装为JavaBean对象
@Test//DBUtils 完成数据查找
public void test01() throws Exception {
//1.通过连接池工具类(刚才写的)获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建 QueryRunner 对象
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "SELECT * FROM temp";
//4.查询数据,并将数据封装到结果集内,BeanListHandler<>(结果集合的泛型) 需要将查询的应表封装为JavaBean对象
List list = queryRunner.query(connection, sql, new BeanListHandler<>(temp.class));
//5.遍历循环
list.forEach(System.out::println);
/*temp{id=1, name='张三', age=18, gender='男', salary=180.0, balance=10000.0}
temp{id=2, name='迪丽热巴', age=22, gender='女', salary=5000.0, balance=10000.0}
temp{id=3, name='古力娜扎', age=22, gender='女', salary=3000.0, balance=10000.0}*/
//5.关闭资源
connection.close();
}
ResultSetHandler结果集处理类
DBuils 查询时,结果返回什么数据,重点是结果集处理器
BeanListHandler 返回一个集合:所有结果
BeanHandler 返回一个对象:第一个结果
ScalarHandler 返回一个数据:结果数量
Handler类型 | 说明 |
---|---|
BeanHandler | 将结果集中第一条记录封装到一个指定的javaBean中。 |
BeanListHandler | 将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中 |
ScalarHandler | 它是用于单个数据。例如select count(*) from 表。 |
ArrayHandler | 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值 |
ArrayListHandler | 将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。 |
ColumnListHandler | 将结果集中指定的列的字段值,封装到一个List集合中 |
KeyedHandler | 将结果集中每一条记录封装到Map |
MapHandler | 将结果集中第一条记录封装到了Map |
MapListHandler | 将结果集中每一条记录封装到了Map |
(DAO) Data Access Object 数据访问对象,是一个面向对象的数据库接口,主要实现插入 更新 查询 删除等 *** 作。
JavaEE项目的三层架构:
//领导| 规定了对Temp表的 *** 作
public interface TempDao {
//查询所有
List selectAll() throws Exception;
//查询指定id
Temp getById(int id) throws Exception;
//更新
void updateTemp(Temp temp) throws Exception;
//添加
void insertTemp(Temp temp) throws Exception;
//删除
void deleteById(int id) throws Exception;
}
实现类
public class TempDaoImpl implements TempDao {
@Override//查询所有
public List selectAll() throws Exception {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "SELECT * FROM temp";
//4.执行 *** 作
List list = queryRunner.query(connection, sql, new BeanListHandler<>(Temp.class));
//关闭资源
connection.close();
return list;
}
@Override//查询指定id
public Temp getById(int id) throws Exception {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "SELECT * FROM temp WHERE id = ?";
//4.执行 *** 作
Temp temp = queryRunner.query(connection, sql, new BeanHandler<>(Temp.class),id);
//关闭资源
connection.close();
return temp;
}
@Override//更新
public void updateTemp(Temp temp) throws Exception {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "UPDATE temp SET name = ?,age = ?,gender = ?,salary = ? WHERE id = ?";
//4.执行 *** 作
queryRunner.update(connection,sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary(),temp.getId());
//关闭资源
connection.close();
}
@Override//添加
public void insertTemp(Temp temp) throws Exception {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "INSERT INTO temp(name,age,gender,salary) VALUES (?,?,?,?)";
//4.执行 *** 作
queryRunner.update(connection,sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary());
//关闭资源
connection.close();
}
@Override//删除
public void deleteById(int id) throws SQLException {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3.准备sql
String sql = "DELETE FROM temp WHERE id = ?";
//4.执行 *** 作
queryRunner.update(connection,sql,id);
//关闭资源
connection.close();
}
}
测试类
TempDao tempDao = new TempDaoImpl();
@Test //测试
public void testInster() throws Exception {
//Integer id, String name, Integer age, String gender, Double salary, Double balance
Temp temp = new Temp(6,"赵丽颖", 19, "女", 13000.0);
tempDao.insertTemp(temp);
tempDao.updateTemp(temp);
tempDao.deleteById(3);
List temps = tempDao.selectAll();
Temp byId = tempDao.getById(3);
}
通用方法再封装 — 反射封装BaseDAOImpl类
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的 *** 作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDAOImpl。
测试temp表:
通用方法封装
public class BaseDaolmpl {
//通用的BaseDao 通用的增 删 改 查方法
public void update(String sql,Object...params) {
try {
//1.通过线程池获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建queryRunner
QueryRunner queryRunner = new QueryRunner();
//3.执行 *** 作
queryRunner.update(connection,sql,params);
} catch (SQLException e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 通用的查询多条数据
* @param clazz 集合的泛型类型
* @param sql
* @param params 参数
* @param
* @return
*/
//不确定查询哪张表,加入泛型!!!!
public List findAll(Class clazz, String sql, Object... params) {
try {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建queryRunner
QueryRunner queryRunner = new QueryRunner();
List query = queryRunner.query(connection, sql, new BeanListHandler<>(clazz), params);
return query;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 通用的返回单个对象
* @param clazz bean的链接类型
* @param sql
* @param params 参数
* @param
* @return
*/
public T findOneBean(Class clazz, String sql, Object... params) {
try {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建queryRunner
QueryRunner queryRunner = new QueryRunner();
T bean = queryRunner.query(connection, sql, new BeanHandler<>(clazz), params);
return bean;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 通用的查询单个值,暂未用到
* @param sql
* @param params
* @return
*/
public Object getSingleValue(String sql,Object...params){
try {
//1.获取链接
Connection connection = JDBCUtils.getConnection();
//2.创建queryRunner
QueryRunner queryRunner = new QueryRunner();
Object query = queryRunner.query(connection, sql, new ScalarHandler<>(), params);
return query;
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
方法接口
public interface TemoDao_base {
//获取所有
List getAll();
//获取单个
Temp getById(int id);
//更新
void updateTemp(Temp temp);
//添加
void insertTemp(Temp temp);
}
实现类
public class TempDao_baseImpl extends BaseDaolmpl implements TemoDao_base {
//继承BaseDao,方法已经封装好,只需要传入Sql语句和?参数即可
@Override//获取所有
public List getAll() {
String sql = "SELECT * FROM temp";
List all = findAll(Temp.class, sql);
return all;
}
@Override//获取单个
public Temp getById(int id) {
String sql = "SELECT * FROM temp WHERE id = ?";
Temp oneBean = findOneBean(Temp.class, sql, id);
return oneBean;
}
@Override//修改数据
public void updateTemp(Temp temp) {
String sql ="UPDATE temp SET name = ?,age = ?,gender = ?,salary = ? WHERE id = ?";
update(sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary(),temp.getId());
}
@Override//添加数据
public void insertTemp(Temp temp) {
String sql ="INSERT INTO temp(name,age,gender,salary) VALUES(?,?,?,?)";
update(sql,temp.getName(),temp.getAge(),temp.getGender(),temp.getSalary());
}
}
测试
@Test
public void test01(){
TempDao_baseImpl tb = new TempDao_baseImpl();
//1.测试查询所有
List all = tb.getAll();
all.forEach(System.out::println);
//2.测试修改
//获取指定id对象
Temp byId = tb.getById(1);
//传入,修改
byId.setSalary(1500.0);
tb.updateTemp(byId);
//3.测试添加
Temp temp1 = new Temp("炎爆龙", 100, "未知", 0.0);
tb.insertTemp(temp1);
}
解决JDBC工具类事物问题 — ThreadLocal *
问题:线程池可以让同一个线程内获取到不同的链接,在不同的链接内对表进行修改,破坏事务处理原则(保证事物都作为一个工作单元来执行),无法做到同时成功或同时失败。
JDBC事物 *** 作前提:所有DML *** 作 在同一个事务内(连接 connection)内。
使用ThreadLocal类需求:
(1)修改did=1的部门简介为“部门1的简介”
(2)修改did=2的部门名称 = 部门1的名称(故意制造错误,因为t_department表的dname有唯一键元素,这条修改会失败)
要求(1)(2)同时成功或同时失败,即在一个事务中完成。调用线程池,(1)(2)使用了不同的线程。
解决事物问题,多线程程序的并发问题,保证同一个事物,同一个链接。
ThreadLocal可以保证单个线程中的数据库 *** 作使用的是同一个数据库连接,从而解决解决多线程程序的并发问题。(因为事物只在当前链接有效,如果两个链接不同无法生效)
需求:
(1)修改did=1的部门简介为“部门1的简介”
(2)修改did=2的部门名称 = 部门1的名称(故意制造错误,因为t_department表的dname有唯一键元素,这条修改会失败)
要求(1)(2)同时成功或同时失败,即在一个事务中完成。
关于
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap
/* Thread源码
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
修改线程池 — 代码实现
public class JDBCUtils_ThreadLocal {
static DataSource dataSource;
//ThreadLocal,每个线程独有,用于存储共享变量
static ThreadLocal local = new ThreadLocal<>();
//静态代码块
static {
//属性集对象
Properties pro = new Properties();
try {
//将配置文件信息 读取到属性集内 (将配置文件信息转为 字节流)
pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
//通过配置文件创建了链接对象
dataSource = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
//通过连接池获取链接
public static Connection getConnection() throws SQLException {
Connection connection = local.get();
//如果ThreadLocalMap内不存在,用连接池获取,存在则直接返回
if(connection==null){
//连接池获取
connection = dataSource.getConnection();
//放入ThreadLocal内
local.set(connection);
}
return connection;
}
//关闭链接方法
public static void closeConnectiopn() throws Exception{
Connection connection = JDBCUtils_ThreadLocal.getConnection();
//事务关闭,防止后面复用出现问题(因为连接使用完成后会归还,等待下次调用)
connection.setAutoCommit(true);
//删除线程内存储的链接
local.remove();
connection.close();
}
}
local.set()源码:获取当前线程,获取当前线程Map,不存在则创建
//Params:value – the value to be stored in the current thread's copy of this thread-local.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
总结
1、获取数据库的链接
Connection conn = JDBCUtils,getConnection(); //数据库连接池 | 手动获取链接
conn.setAutoCommit(false); //体现事物
2、如下的多个DML *** 作,作为一个事务出现:
*** 作1:需要使用通用的增删改查 *** 作 //通用的增删改查 *** 作如何实现?
*** 作2:需要使用通用的增删改查 *** 作 //方式1:手动使用PreparedStatement实现
*** 作3:需要使用通用的增删改查 *** 作 //方式2:使用dbutils.jar中QueryRunner类
conn.commit();
3、如果出现异常,则:
coon.rollback();
4、关闭资源
JDBCUtils.closeResource(..,...,...);
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)