1. dbutils回顾
package com.xp.connectionpool;
import java.sql.Connection;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.junit.Test;
import com.xp.utils.JdbcUtil;
public class DbutilsDemo {
// 1. 使用DbUtils组件更新
@Test
public void testUpdate() throws Exception {
String sql = "delete from admin where id=10;";
//1.1 连接
Connection con = JdbcUtil.getConnection();
//1.2 创建核心工具类
QueryRunner qr = new QueryRunner();
//1.3 更新
qr.update(con, sql);
con.close();
}
// 2. 使用DbUtils组件查询
@Test
public void testQuery() throws Exception {
String sql = "select * from admin";
//1.1 连接
Connection con = JdbcUtil.getConnection();
//1.2 创建核心工具类
QueryRunner qr = new QueryRunner();
//1.3 查询
List list = qr.query(con, sql, new BeanListHandler(Admin.class));
System.out.println(list);
con.close();
}
}
1. 自定义连接池
连接资源宝贵;需要对连接管理
连接:
a) 操作数据库,创建连接
b) 操作结束, 关闭!
分析:
涉及频繁的连接的打开、关闭,影响程序的运行效率!
连接管理:
预先创建一组连接,有的时候每次取出一个; 用完后,放回;
代理:
如果对某个接口中的某个指定的方法的功能进行扩展,而不想实现接口里所有方法,可以使用(动态)代理模式!
Java中代理模式:静态/动态/Cglib代理(spring)
使用动态代理,可以监测接口中方法的执行!
如何对Connection对象,生成一个代理对象:
|--Proxy
static Object newProxyInstance(
ClassLoader loader, 当前使用的类加载器
Class>[] interfaces, 目标对象(Connection)实现的接口类型
InvocationHandler h 事件处理器:当执行上面接口中的方法的时候,就会自动触发事件处理器代码,把当前执行的方法(method)作为参数传入。
)
package com.xp.connectionpool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
/**
* 自定义连接池, 管理连接 代码实现: 1. MyPool.java 连接池类, 2. 指定全局参数: 初始化数目、最大连接数、当前连接、 连接池集合 3.
* 构造函数:循环创建3个连接 4. 写一个创建连接的方法 5. 获取连接 ------> 判断: 池中有连接, 直接拿 ------> 池中没有连接,
* ------> 判断,是否达到最大连接数; 达到,抛出异常;没有达到最大连接数, 创建新的连接 6. 释放连接 -------> 连接放回集合中(..)
*
*/
public class MyPool {
private int init_count = 3; // 初始化连接数目
private int max_count = 6; // 最大连接数
private int current_count = 0; // 记录当前使用连接数
// 连接池 (存放所有的初始化连接)
private LinkedList pool = new LinkedList();
// 1. 构造函数中,初始化连接放入连接池
public MyPool() {
// 初始化连接
for (int i = 0; i < init_count; i++) {
// 记录当前连接数目
current_count++;
// 创建原始的连接对象
Connection con = createConnection();
// 把连接加入连接池
pool.addLast(con);
}
}
// 2. 创建一个新的连接的方法
private Connection createConnection() {
try {
Class.forName("com.mysql.jdbc.Driver");
// 原始的目标对象
final Connection con = DriverManager.getConnection(
"jdbc:mysql:///jdbc_demo", "root", "xiongpan");
/********** 对con对象代理 **************/
// 对con创建其代理对象
Connection proxy = (Connection) Proxy.newProxyInstance(
con.getClass().getClassLoader(), // 类加载器
// con.getClass().getInterfaces(), // 当目标对象是一个具体的类的时候
new Class[] { Connection.class }, // 目标对象实现的接口
new InvocationHandler() { // 当调用con对象方法的时候, 自动触发事务处理器
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
// 方法返回值
Object result = null;
// 当前执行的方法的方法名
String methodName = method.getName();
// 判断当执行了close方法的时候,把连接放入连接池
if ("close".equals(methodName)) {
System.out.println("begin:当前执行close方法开始!");
// 连接放入连接池 (判断..)
pool.addLast(con);
System.out.println("end: 当前连接已经放入连接池了!");
} else {
// 调用目标对象方法
result = method.invoke(con, args);
}
return result;
}
});
return proxy;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 3. 获取连接
public Connection getConnection() {
// 3.1 判断连接池中是否有连接, 如果有连接,就直接从连接池取出
if (pool.size() > 0) {
return pool.removeFirst();
}
// 3.2 连接池中没有连接: 判断,如果没有达到最大连接数,创建;
if (current_count < max_count) {
// 记录当前使用的连接数
current_count++;
// 创建连接
return createConnection();
}
// 3.3 如果当前已经达到最大连接数,抛出异常
throw new RuntimeException("当前连接已经达到最大连接数目 !");
}
// 4. 释放连接
public void realeaseConnection(Connection con) {
// 4.1 判断: 池的数目如果小于初始化连接,就放入池中
if (pool.size() < init_count) {
pool.addLast(con);
} else {
try {
// 4.2 关闭
current_count--;
con.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws SQLException {
MyPool pool = new MyPool();
System.out.println("当前连接: " + pool.current_count); // 3
// 使用连接
pool.getConnection();
pool.getConnection();
Connection con4 = pool.getConnection();
Connection con3 = pool.getConnection();
Connection con2 = pool.getConnection();
Connection con1 = pool.getConnection();
// 释放连接, 连接放回连接池
// pool.realeaseConnection(con1);
/*
* 希望:当关闭连接的时候,要把连接放入连接池!【当调用Connection接口的close方法时候,希望触发pool.addLast(con)
* ;操作】 把连接放入连接池 解决1:实现Connection接口,重写close方法 解决2:动态代理
*/
con1.close();
// 再获取
pool.getConnection();
System.out.println("连接池:" + pool.pool.size()); // 0
System.out.println("当前连接: " + pool.current_count); // 3
}
}
代理的总结:(了解会用)
public class DBCPDemo {
// 1. 硬编码方式实现连接池
@Test
public void testDbcp() throws Exception {
// DBCP连接池核心类
BasicDataSource dataSouce = new BasicDataSource();
// 连接池参数配置:初始化连接数、最大连接数 / 连接字符串、驱动、用户、密码
dataSouce.setUrl("jdbc:mysql:///jdbc_demo");//数据库连接字符串
dataSouce.setDriverClassName("com.mysql.jdbc.Driver"); //数据库驱动
dataSouce.setUsername("root");//数据库连接用户
dataSouce.setPassword("xiongpan");//数据库连接密码
dataSouce.setInitialSize(3); // 初始化连接
dataSouce.setMaxActive(6); // 最大连接
dataSouce.setMaxIdle(3000); // 最大空闲时间
// 获取连接
Connection con = dataSouce.getConnection();
con.prepareStatement("delete from admin where id=3").executeUpdate();
// 关闭
con.close();
}
@Test
// 2. 【推荐】配置方式实现连接池 , 便于维护
public void testProp() throws Exception {
// 加载prop配置文件
Properties prop = new Properties();
// 获取文件流
InputStream inStream = DBCPDemo.class.getResourceAsStream("db.properties");
// 加载属性配置文件
prop.load(inStream);
// 根据prop配置,直接创建数据源对象
DataSource dataSouce = BasicDataSourceFactory.createDataSource(prop);
// 获取连接
Connection con = dataSouce.getConnection();
con.prepareStatement("delete from admin where id=4").executeUpdate();
// 关闭
con.close();
}
}
配置方式实现DBCP连接池, 配置文件中的key与BaseDataSouce中的属性一样:
url=jdbc:mysql:///jdbc_demo
driverClassName=com.mysql.jdbc.Driver
username=root
password=xiongpan
initialSize=3
maxActive=6
maxIdle=3000
1.
2.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
参数 描述
package com.xp.connectionpool;
import java.sql.Connection;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class C3p0PoolDemo {
@Test
//1. 硬编码方式,使用C3P0连接池管理连接
public void testCode() throws Exception {
// 创建连接池核心工具类
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 设置连接参数:url、驱动、用户密码、初始连接数、最大连接数
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_demo");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("xiongpan");
dataSource.setInitialPoolSize(3);
dataSource.setMaxPoolSize(6);
dataSource.setMaxIdleTime(1000);
// ---> 从连接池对象中,获取连接对象
Connection con = dataSource.getConnection();
// 执行更新
con.prepareStatement("delete from admin where id=5").executeUpdate();
// 关闭
con.close();
}
@Test
//2. XML配置方式,使用C3P0连接池管理连接
public void testXML() throws Exception {
// 创建c3p0连接池核心工具类
// 自动加载src下c3p0的配置文件【c3p0-config.xml】
ComboPooledDataSource dataSource = new ComboPooledDataSource();// 使用默认的配置
// 获取连接
Connection con = dataSource.getConnection();
// 执行更新
con.prepareStatement("delete from admin where id=5").executeUpdate();
// 关闭
con.close();
}
}
优化
项目,连接的管理,交给连接池!
package com.xp.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.management.RuntimeErrorException;
import javax.sql.DataSource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JdbcUtilC3p0 {
//初始化连接池
private static DataSource dataSource;
//静态代码块初始化连接池资源,只加载一次
static{
dataSource=new ComboPooledDataSource();
}
/**
* 返回连接对象
*/
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void closeAll(Connection con, Statement stmt, ResultSet rs) {
try {
if (rs != null) {
rs.close(); // 快速异常捕获 Alt + shift + z
rs = null; // 建议垃圾回收期回收资源
}
if (stmt != null) {
stmt.close();
stmt = null;
}
if (con != null && !con.isClosed()) {
con.close();
con = null;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
配置tomcat数据源
Context initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env");
dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
特别提醒:此种配置下,驱动jar文件需放置在tomcat的lib下
5.分页分析过程
分页技术:
JSP页面,用来显示数据! 如果数据有1000条,分页显示,每页显示10条,共100页; 好处: 利于页面布局,且显示的效率高!
分页关键点:
1. 分页SQL语句;
-- 分页查询
-- limit第一个参数,查询的起始行(从0开始)
-- limit第二个参数,返回的行数
-- 每页显示4条
-- 第一页
SELECT * FROM employee LIMIT0,4;-- (当前页-1)*每页显示的行数
-- 第二页
SELECT * FROM employee LIMIT4,4;
-- 第一页
SELECT * FROM employee LIMIT8,4;
SELECT * FROM LIMIT (当前页-1)*每页显示的行数,每页显示的行数
1. 后台处理:dao/service/servlet/JSP
6.分页实现(1)
实现步骤:
0. 环境准备
a) 引入jar文件及引入配置文件
i. 数据库驱动包
ii. C3P0连接池jar文件 及 配置文件
iii. DbUtis组件: QueryRunner qr = newQueryRuner(dataSouce);
qr.update(sql);
b) 公用类: JdbcUtils.java
1. 先设计:PageBean.java
2. Dao接口设计/实现: 2个方法
3. Service/servlet
4. JSP
引入c3po配置文件
jdbc:mysql://localhost:3306/jdbc_demo
com.mysql.jdbc.Driver
root
xiongpan
3
6
1000
jdbc:mysql://localhost:3306/jdbc_demo
com.mysql.jdbc.Driver
root
xiongpan
3
6
1000
第二步创建连接池工具类
package com.xp.utils;
import javax.sql.DataSource;
import org.apache.commons.dbutils.QueryRunner;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* 工具类
* 1. 初始化C3P0连接池
* 2. 创建DbUtils核心工具类对象
*/
public class JdbcUtils {
/**
* 1. 初始化C3P0连接池
*/
private static DataSource dataSource;
static {
dataSource = new ComboPooledDataSource();
}
/**
* 2. 创建DbUtils核心工具类对象
*/
public static QueryRunner getQueryRuner(){
// 创建QueryRunner对象,传入连接池对象
// 在创建QueryRunner对象的时候,如果传入了数据源对象;
// 那么在使用QueryRunner对象方法的时候,就不需要传入连接对象;
// 会自动从数据源中获取连接(不用关闭连接)
return new QueryRunner(dataSource);
}
}
第三步:封装分页的参数
package com.xp.utils;
import java.util.List;
/**
* 封装分页的参数
*/
public class PageBean {
private int currentPage = 1; // 当前页, 默认显示第一页
private int pageCount = 4; // 每页显示的行数(查询返回的行数), 默认每页显示4行
private int totalCount; // 总记录数
private int totalPage; // 总页数 = 总记录数 / 每页显示的行数 (+ 1)
private List pageData; // 分页查询到的数据
// 返回总页数
public int getTotalPage() {
if (totalCount % pageCount == 0) {
totalPage = totalCount / pageCount;
} else {
totalPage = totalCount / pageCount + 1;
}
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getPageCount() {
return pageCount;
}
public void setPageCount(int pageCount) {
this.pageCount = pageCount;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public List getPageData() {
return pageData;
}
public void setPageData(List pageData) {
this.pageData = pageData;
}
}
第三步:编写实体类
package com.xp.entity;
/**
* 1. 实体类设计 (因为用了DbUtils组件,属性要与数据库中字段一致)
*/
public class Employee {
private int empId; // 员工id
private String empName; // 员工名称
private int dept_id; // 部门id
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public int getDept_id() {
return dept_id;
}
public void setDept_id(int deptId) {
dept_id = deptId;
}
}
第四步:编写数据访问层:接口设计
package com.xp.dao;
import com.xp.entity.Employee;
import com.xp.utils.PageBean;
public interface IEmployeeDao {
/**
* 分页查询数据
*/
public void getAll(PageBean pb);
/**
* 查询总记录数
*/
public int getTotalCount();
}
第五步;编写数据访问层实现类
package com.xp.dao.impl;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import com.xp.dao.IEmployeeDao;
import com.xp.entity.Employee;
import com.xp.utils.JdbcUtils;
import com.xp.utils.PageBean;
public class EmployeeDao implements IEmployeeDao {
@Override
public void getAll(PageBean pb) {
//2. 查询总记录数; 设置到pb对象中
int totalCount = this.getTotalCount();
pb.setTotalCount(totalCount);
/*
* 问题: jsp页面,如果当前页为首页,再点击上一页报错!
* 如果当前页为末页,再点下一页显示有问题!
* 解决:
* 1. 如果当前页 <= 0; 当前页设置当前页为1;
* 2. 如果当前页 > 最大页数; 当前页设置为最大页数
*/
// 判断
if (pb.getCurrentPage() <=0) {
pb.setCurrentPage(1); // 把当前页设置为1
} else if (pb.getCurrentPage() > pb.getTotalPage()){
pb.setCurrentPage(pb.getTotalPage()); // 把当前页设置为最大页数
}
//1. 获取当前页: 计算查询的起始行、返回的行数
int currentPage = pb.getCurrentPage();
int index = (currentPage -1 ) * pb.getPageCount(); // 查询的起始行
int count = pb.getPageCount(); // 查询返回的行数
//3. 分页查询数据; 把查询到的数据设置到pb对象中
String sql = "select * from employee limit ?,?";
try {
// 得到Queryrunner对象
QueryRunner qr = JdbcUtils.getQueryRuner();
// 根据当前页,查询当前页数据(一页数据)
List pageData = qr.query(sql, new BeanListHandler(Employee.class), index, count);
// 设置到pb对象中
pb.setPageData(pageData);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int getTotalCount() {
String sql = "select count(*) from employee";
try {
// 创建QueryRunner对象
QueryRunner qr = JdbcUtils.getQueryRuner();
// 执行查询, 返回结果的第一行的第一列
Long count = qr.query(sql, new ScalarHandler());
return count.intValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
第六步:编写业务逻辑层接口
package com.xp.service;
import com.xp.entity.Employee;
import com.xp.utils.PageBean;
public interface IEmployeeService {
/**
* 分页查询数据
*/
public void getAll(PageBean pb);
}
第七步:编写业务逻辑层实现
package com.xp.service.impl;
import com.xp.dao.IEmployeeDao;
import com.xp.dao.impl.EmployeeDao;
import com.xp.entity.Employee;
import com.xp.service.IEmployeeService;
import com.xp.utils.PageBean;
/**
* 3. 业务逻辑层,实现
*/
public class EmployeeService implements IEmployeeService {
// 创建Dao实例
private IEmployeeDao employeeDao = new EmployeeDao();
@Override
public void getAll(PageBean pb) {
try {
employeeDao.getAll(pb);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
第八步:控制层开发
package com.xp.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xp.entity.Employee;
import com.xp.service.IEmployeeService;
import com.xp.service.impl.EmployeeService;
import com.xp.utils.PageBean;
/**
* 4. 控制层开发
*/
public class IndexServlet extends HttpServlet {
// 创建Service实例
private IEmployeeService employeeService = new EmployeeService();
// 跳转资源
private String uri;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
//1. 获取“当前页”参数; (第一次访问当前页为null)
String currPage = request.getParameter("currentPage");
// 判断
if (currPage == null || "".equals(currPage.trim())){
currPage = "1"; // 第一次访问,设置当前页为1;
}
// 转换
int currentPage = Integer.parseInt(currPage);
//2. 创建PageBean对象,设置当前页参数; 传入service方法参数
PageBean pageBean = new PageBean();
pageBean.setCurrentPage(currentPage);
//3. 调用service
employeeService.getAll(pageBean); // 【pageBean已经被dao填充了数据】
//4. 保存pageBean对象,到request域中
request.setAttribute("pageBean", pageBean);
//5. 跳转
uri = "/WEB-INF/list.jsp";
} catch (Exception e) {
e.printStackTrace(); // 测试使用
// 出现错误,跳转到错误页面;给用户友好提示
uri = "/error/error.jsp";
}
request.getRequestDispatcher(uri).forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
分页查询数据
序号
员工编号
员工姓名
${vs.count }
${emp.empId }
${emp.empName }
对不起,没有你要找的数据
当前${requestScope.pageBean.currentPage }/${requestScope.pageBean.totalPage }页
首页
上一页
下一页
末页