DBCP是tomcat中的一个工具类。
DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放和断开。
DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。单独使用dbcp需要2个包:commons-dbcp.jar,commons-pool.jar由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。
DBCP数据库连接池操作如下:
1、导入驱动jar包(dbcp连接池jar包通常依赖于pool包一起使用)
原理:dbcp包会产生许多个连接对象,这些对象供pool包进行统一管理。
2、创建数据库工具类:DBUtils.java
工具类优点:
- 1、提高性能
告别传统方式每次连接数据库都要进行创建并关闭,严重影响性能,使用数据库连接池DBCP技术实现连接池中存放多个连接对象供使用。当不需要连接时,将连接对象存放到池中即可,提高性能!
- 2、避免多线程访问连接对象混乱问题
将连接保存在ThreadLocal类中,相当于map结构,它是将当前线程对象作为key,保存连接conn对象,避免多线程访问业务层和dao层导致使用的conn连接对象不一致问题。先从threadLocal中获取连接,如果连接为null,从连接池获取连接,设置到threadLocal中。
- 3、将提交和回滚事务统一处理
先从ThreadLocal类中获取连接,如果连接不为null,提交或者回滚事务,然后关闭连接,清理当前线程所绑定的连接。
- 4、方便关闭连接
关闭除过conn连接对象的其他对象statement、resultset等。
DBCP核心设置代码如下:
// 创建数据库连接池
ds = new BasicDataSource();
// 设置连接信息
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(URL);
ds.setUsername(USERNAME);
ds.setPassword(PASSWORD);
// 设置连接池信息
// 最大空闲连接数
ds.setMaxIdle(30);
// 最小空闲连接数
ds.setMinIdle(2);
// 设置初始连接数
ds.setInitialSize(2);
// 创建连接时最大等待时间
ds.setMaxWaitMillis(4000);// 毫秒
// 从数据源中拿到的连接,关闭其自动提交的事务
ds.setDefaultAutoCommit(false);
完整代码如下:
package com.yueqian.store.common;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
public class DBUtils {
private static final String URL = "jdbc:mysql://127.0.0.1:3306/store?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false";
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
private static final BasicDataSource ds;
static {
// 加载驱动
try {
// 方式一
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
// 方式二 根据传入字符串形式的类名加载该类 (加载类时调用静态代码块加载驱动)
// Class.forName("com.mysql.cj.jdbc.Driver()");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 创建数据库连接池
ds = new BasicDataSource();
// 设置连接信息
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(URL);
ds.setUsername(USERNAME);
ds.setPassword(PASSWORD);
// 设置连接池信息
// 最大空闲连接数
ds.setMaxIdle(30);
// 最小空闲连接数
ds.setMinIdle(2);
// 设置初始连接数
ds.setInitialSize(2);
// 创建连接时最大等待时间
ds.setMaxWaitMillis(4000);// 毫秒
// 从数据源中拿到的连接,关闭其自动提交的事务
ds.setDefaultAutoCommit(false);
}
// 定义连接保存在ThreadLocal类中,将当前线程对象作为key,保存连接conn对象,避免多线程访问业务层和dao层导致使用的conn连接不一致问题
// service里绑定每个线程对象的连接,调用的dao层获取该处理线程对象的连接
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
/**
* 连接数据库
*
* @return
*/
public static Connection getConnection() {
//先从threadLocal中获得连接,threadLocal类似map存放,ThreadLocal中是以键为当前线程对象,值为conn连接存放的。
Connection conn = threadLocal.get();
//如果连接为null,从连接池中获取连接
if (conn == null) {
try {
conn = ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将得到的连接设置到threadLocal中
threadLocal.set(conn);
}
return conn;
}
/**
* 关闭连接
*
* @param rs
* @param stmt
* @param conn
*/
public static void close(ResultSet rs, Statement stmt) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
// 提交事务
public static void commit() {
Connection conn = threadLocal.get();
if(conn != null){
try {
conn.commit();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//关闭连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//清理当前线程所绑定的连接
threadLocal.remove();
}
}
}
// 回滚事务
public static void rollback() {
Connection conn = threadLocal.get();
if(conn != null){
try {
conn.rollback();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//关闭连接
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//清理当前线程所绑定的连接
threadLocal.remove();
}
}
}
}
3、业务层
使用同一个连接对象,调用多个Dao层处理。所有事务处理完成之后,统一操作成功,调用DBUtils.commit()方法提交事务,一个失败,全部回滚。
package com.yueqian.store.service;
import java.util.List;
import com.yueqian.store.common.DBUtils;
import com.yueqian.store.dao.ProductTypeDao;
import com.yueqian.store.domain.ProductType;
/**
* 商品类别业务层
*
* @author LinChi
*
*/
public class ProductTypeService {
private ProductTypeDao typeDao = new ProductTypeDao();
/**
* 查询所有商品类目
*
* @return
*/
public List<ProductType> findAllProType() {
List<ProductType> list = null;
try {
//调用多个Dao对象处理,最后统一事务提交
list = typeDao.findAllProType();
//使用同一个连接对象,执行后续的DAO方法
//提交事务
DBUtils.commit();
} catch (Exception e) {
//执行失败回滚事务
DBUtils.rollback();
}
return list;
}
}
4、dao层
dao层直接调用DBUtils.getConnection()方法获得连接,将dao层所有出现的异常向上抛到业务层,业务层统一处理,最后统一提交并回滚事务,dao层不处理异常,一直声明抛出即可,
package com.yueqian.store.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.yueqian.store.common.DBUtils;
import com.yueqian.store.domain.ProductType;
public class ProductTypeDao {
/**
* 查询所有商品类目表
* @return
* @throws SQLException 将dao层所有出现的异常向上抛到业务层,业务层统一处理,最后统一提交并回滚事务
*/
public List<ProductType> findAllProType()throws SQLException{
Connection conn = null;
PreparedStatement pstm = null;
List<ProductType> list = new ArrayList<ProductType>();
ResultSet rs = null;
ProductType proType = null;
conn = DBUtils.getConnection();
String sql = "SELECT p.product_type_id,p.name FROM product_types p";
try {
pstm = conn.prepareStatement(sql);
rs = pstm.executeQuery();
while(rs.next()) {
proType = new ProductType();
proType.setTypeId(rs.getInt(1));
proType.setTypeName(rs.getString(2));
list.add(proType);
}
} finally {
DBUtils.close(rs, pstm);
}
return list;
}
}
5、controller层
调用业务层,处理相应的逻辑
- BaseServlet.java
这是个通过反射机制,将前端传入String的参数类型转换为对应的类型。其他servlet集成该类即可方便转换类型。参数如下(前端name名称,请求对象req,要转化类型的字节码Integer.class)
package com.yueqian.store.controller;
import java.sql.Date;
import java.text.SimpleDateFormat;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
public class BaseServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 获取请求的参数,根据请求的参数转化为特定的类型
public <T> T paseParamter(String parseName, HttpServletRequest req, Class<T> clz) {
// 获取请求参数
String parmValue = req.getParameter(parseName);
// 定义返回类型的对象
T result = null;
// clz类型 Integer.class String.class Double.class,Float.class,Date.class
try {
if (parmValue != null || parmValue.length() > 0) {
// 因为Float类型没有String参数的构造方法,所以只能传入long参数的构造方法
if (clz == Date.class) {
// 将paramValue转换成long类型的值
long longValue = sdf.parse(parmValue).getTime();
// 创建指定类型的String参数构造方法
result = clz.getDeclaredConstructor(long.class).newInstance(longValue);
} else {
// 将paramValue作为指定类型构造方法的参数
result = clz.getDeclaredConstructor(String.class).newInstance(parmValue);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
具体使用如下:
package com.yueqian.store.controller;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.yueqian.store.dao.ProductDAO;
import com.yueqian.store.dao.ProductTypeDao;
import com.yueqian.store.domain.ProductInfo;
import com.yueqian.store.domain.ProductType;
import com.yueqian.store.service.ProductService;
import com.yueqian.store.service.ProductTypeService;
public class ProductServlet extends BaseServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
private ProductService productService = new ProductService();
private ProductTypeService typeService = new ProductTypeService();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();
if (uri.indexOf("/prd/list") >= 0) {
// 商品列表
List<ProductInfo> findAllProduct = productService.findAllProduct();
// 设置列表到请求页面
req.setAttribute("findAllProduct", findAllProduct);
// 请求转发到目标页面
req.getRequestDispatcher("/product/product_list.jsp").forward(req, resp);
} else if (uri.indexOf("/prd/add") >= 0) {
// 查看所有类目
List<ProductType> findAllProType = typeService.findAllProType();
// 存放到请求域中转发到其他页面
req.setAttribute("findAllProType", findAllProType);
req.getRequestDispatcher("/product/product_add.jsp").forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String uri = req.getRequestURI();
if (uri.indexOf("/prd/add") > 0) {
// 获取请求的参数
Integer typeId = super.paseParamter("typeId", req, Integer.class);
String productName = super.paseParamter("productName", req, String.class);
String desc = super.paseParamter("desc", req, String.class);
Float price = super.paseParamter("price", req, Float.class);
System.out.println("productName:"+productName+"-------------------------");
System.out.println("price:"+price+"-------------------------");
//输入校验
//数据库中商品名称不能为null,如果为null,将返回消息和用户填写的其他数据
if(productName == null || productName.equals("")) {
req.setAttribute("msg", "商品名称不能为空!");
req.setAttribute("typeId", typeId);
req.setAttribute("desc", desc);
req.setAttribute("price", price);
req.setAttribute("findAllProType", this.typeService.findAllProType());
//请求转发到添加页面
req.getRequestDispatcher("/product/product_add.jsp").forward(req, resp);
return;
}
// 添加新商品
ProductInfo info = new ProductInfo();
if (typeId != null && typeId > 0) {
info.setProductTypeId(typeId);
}
info.setProductName(productName);
info.setDesc(desc);
info.setPrice(price);
int count = productService.saveInfo(info);
System.out.println(info.getProductName()+"========================");
// 重定向到添加请求url (此处不能使用请求转发,因为表单刷新会发送第二次请求,导致添加两次商品)
resp.sendRedirect(
req.getContextPath() + "/prd/add?count=" + count + "&from=add&proId=" + info.getProductId());
}
}
}
6、view层
将servlet传入的数据,通过jsp展示到页面给用户
<%@page import="com.yueqian.store.domain.ProductType"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品添加页面</title>
<script type="text/javascript">
window.onload = function() {
var returnNode = document.getElementById('returnBtn');
returnNode.onclick = function() {
location.href = "<%=request.getContextPath()%>/main.jsp";
}
}
</script>
</head>
<body>
<%
List<ProductType> typeList = (List<ProductType>) request.getAttribute("findAllProType");
//获取传递的url参数
String count = request.getParameter("count");
String from = request.getParameter("from");
String proId = request.getParameter("proId");
%>
<button id="returnBtn" type="button">返回主页</button>
<form action='<%=request.getContextPath()%>/prd/add' method="post">
商品类别:<select name="typeId">
<option value="0">请选择商品类别</option>
<%
if (typeList != null && typeList.size() > 0) {
for (ProductType types : typeList) {
%>
<option value='<%=types.getTypeId()%>'<%=request.getAttribute("findAllProType").equals(types.getTypeId())? "selected=selected":"selected=0"%>><%=types.getTypeName()%></option>
<%
}
}
%>
</select><br />
商品名称:<input type="text" name="productName" maxlength="30" /><br />
商品描述:
<textarea rows="7" cols="30" name="desc"><% Object desc = request.getParameter("desc"); if(desc != null) out.print(desc);%></textarea>
<br /> 商品价格:<input type="text" name="price" maxlength="10" <% Object price = request.getParameter("price"); if(price != null) out.print(price);%>/><br />
<input type="submit" value="添加">
</form>
<%
if (count != null && from != null) {
int intCount = Integer.parseInt(count);
String f = ("add".equals(from) ? "录入" : "");
if (intCount > 0) {
out.print(f + "了" + count + "件商品,刚刚录入的商品编号为:" + proId);
}else{
out.print(f+"失败!");
}
}
Object msg = request.getAttribute("msg");
if(msg!=null){
out.print(msg);
}
%>
</body>
</html>
好了,该项目的部分代码如上所示,这节你将学到如何优化数据库连接、mvc分层创建web项目流程 、反射机制转换前端传递的类型。
这就是DBCP连接池的使用,可以大大提供系统的性能,可能现在我们的项目比较小,还没有真正体会到效率提高的到底有多少,相信在今后接触到更大的项目,我们会经常使用连接池的,继续前行……