写在开头:本文是学习尚硅谷JavaWeb的个人笔记,便于自己后期复习,也供各位参考评论,指出错误共同进步。
主要内容
1 DAO操作
简要介绍:DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
作用:为了实现功能的模块化,更有利于代码的维护和升级。
(在没有使用框架的阶段,我们需要对数据库的操作进行封装,对外体现一个Dao,BaseDao封装了对数据表的通用操作(增删改查))。
下图是尚硅谷书城项目basedao ,bookdao接口,bookdaoimple实现的结构图:(看一哈)
接下来以之前的Customer表做示例;下面的示例都用到了之前的工具类JDBCutils和Bean中的Customer类
BaseDAO示例:
第一步:造所有操作的父类BaseDAO类
package com.atguigu2.dao;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.atguigu1.util.JDBCUtils;
/*
* 封装了针对于数据表的操作(增删改查)。考虑了事务的增删改查
* 具体的表操作我们提供具体的dao,具体的dao继承BaseDAO
*/
public abstract class BaseDAO {
//通用增删改操作version2.0(从外部传一个连接进来)
public int update(Connection conn,String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
PreparedStatement ps = null;
try {
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);//小心参数声明错误!!
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.资源的关闭
JDBCUtils.closeResource(null, ps);//连接不关
}
return 0;
}
//通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑上事务)
public <T> T getInstance(Connection conn,Class<T> clazz,String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//通用的查询操作,用于返回数据表多条记录构成的集合(version 2.0:考虑上事务)
public<T> List<T> getForList(Connection conn,Class<T> clazz, String sql,Object...args){
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();//检索此 ResultSet对象的列的数量,类型和属性。
//通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<T>();
while(rs.next()){
T t = clazz.newInstance();
//处理结果集一行数据中的每一个列:给t对象指定的属性赋值。
for(int i = 0;i <columnCount;i++){
//获取列值
Object columValue = rs.getObject(i + 1);
//获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
//给cust对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);//getDeclaredField是可以获取一个类本身的所有字段.
field.setAccessible(true);
field.set(t, columValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//用于查询特殊值的通用的方法
public <E> E getValue(Connection conn,String sql,Object...args){
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
if(rs.next()){
return (E) rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
具体的表操作我们提供具体的dao,具体的dao继承BaseDAO
第二步:让代码结构更加规范,先造一个接口:CustomerDAO。(针对于Customer这个表我们需要做些什么事(增删改查)),后面具体对customer表操作的类就实现这个接口。
package com.atguigu2.dao;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import com.atguigu2.bean.Customer;
/*
* 此接口用于规范针对于customers表的常用操作
*/
public interface CustomerDAO {
/**
*
* @Description 将cust对象添加到数据库中
* @param conn
* @param cust
*/
void insert(Connection conn,Customer cust);
/**
*
* @Description 针对指定的id,删除表中的一条记录
* @param conn
* @param id
*/
void deleteById(Connection conn,int id);
/**
*
* @Description 针对内存中的cust对象,去修改数据表中指定的记录
* @param conn
* @param cust
*/
void update(Connection conn,Customer cust);
/**
*
* @Description 针对指定的id查询得到对应的Customer对象
* @param conn
* @param id
*/
Customer getCustomerById(Connection conn,int id);
/**
*
* @Description 查询表中的所有记录构成的集合
* @param conn
* @return
*/
List<Customer> getAll(Connection conn);
/**
*
* @Description 返回数据表中的数据的条目数
* @param conn
* @return
*/
Long getCount(Connection conn);
/**
*
* @Description 返回数据表中最大的生日
* @param conn
* @return
*/
Date getMaxBirth(Connection conn);
}
第三步:对Customer表的具体操作:CustomerDAOImpl继承BaseDAO类(封装了针对所有表的增删改查操作) 并实现CustomerDAO(针对某表的一个操作)
package com.atguigu2.dao;
/*
* 对CustomerDAO(接口要实现哪些功能)的具体实现
* BaseDAO封装了能够实现的功能增删改出
*/
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import com.atguigu2.bean.Customer;
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO{
@Override
public void insert(Connection conn, Customer cust) {
String sql = "insert into customers(name,email,birth)values(?,?,?)";
update(conn, sql,cust.getName(),cust.getEmail(),cust.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql = "delete from customers where id = ?";
update(conn, sql, id);
}
@Override
public void update(Connection conn, Customer cust) {
String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
update(conn, sql,cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getInstance(conn,Customer.class, sql,id);
return customer;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql = "select id,name,email,birth from customers";
List<Customer> list = getForList(conn, Customer.class, sql);
return list;
}
@Override
public Long getCount(Connection conn) {
String sql = "select count(*) from customers";
return getValue(conn, sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql = "select max(birth) from customers";
return getValue(conn, sql);
}
}
对Customer表的操作测试(用到的就是前面的1、2、3步)
package com.atguigu2.dao.junit;
import static org.junit.Assert.*;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import org.junit.Test;
import com.atguigu1.util.JDBCUtils;
import com.atguigu2.bean.Customer;
import com.atguigu2.dao.CustomerDAOImpl;
public class CustomerDAOImplTest {
private CustomerDAOImpl dao = new CustomerDAOImpl();
@Test
public void testInsert() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer cust = new Customer(1, "于小飞", "xiaofei@126.com",new Date(43534646435L));
dao.insert(conn, cust);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testDeleteById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
dao.deleteById(conn, 13);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testUpdateConnectionCustomer() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer cust = new Customer(18,"贝多芬","beiduofen@126.com",new Date(453465656L));
dao.update(conn, cust);
System.out.println("修改成功");
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetCustomerById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer cust = dao.getCustomerById(conn, 19);
System.out.println(cust);
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetAll() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
List<Customer> list = dao.getAll(conn);
list.forEach(System.out::println);
System.out.println("");
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetCount() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Long count = dao.getCount(conn);
System.out.println("表中的记录数为:" + count);
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetMaxBirth() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Date maxBirth = dao.getMaxBirth(conn);
System.out.println("最大的生日为:" + maxBirth);
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
}
2 数据库连接池
2.1 JDBC数据库连接池的必要性
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
这种模式开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
2.2 数据库连接池技术
- 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完毕之后再放回去。 - 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库 连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
工作原理
2.3 数据库连接池技术的优点
- 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。 - 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间 - 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源 - 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
2.3 多种开源的数据库连接池
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快(现在的主流)
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
特别注意:
数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
3 C3P0数据库连接池的两种连接
首先导入C3P0jar包
方式一:C3P0实现方式一(不推荐)
@Test
public void testGetConnection() throws Exception{
//获取c3p0数据库连接池
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //四个基本信息
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
cpds.setUser("root");
cpds.setPassword("root");
//通过设置相关的参数,对数据库连接池进行管理:
//设置初始时数据库连接池中的连接数
cpds.setInitialPoolSize(10);
Connection conn = cpds.getConnection();//使用连接池中的一个连接
System.out.println(conn);
//销毁c3p0数据库连接池,一般不关
// DataSources.destroy( cpds );
}
方式一:C3P0实现方式二(使用配置文件)
首先:创建配置文件c3p0-config.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="hellc3p0"><!-- 配置文件名 -->
<!-- 提供获取连接的4个基本信息 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///test</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 进行数据库连接池管理的基本信息 -->
<!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 -->
<property name="acquireIncrement">5</property>
<!-- c3p0数据库连接池中初始化时的连接数 -->
<property name="initialPoolSize">10</property>
<!-- c3p0数据库连接池维护的最少连接数 -->
<property name="minPoolSize">10</property>
<!-- c3p0数据库连接池维护的最多的连接数 -->
<property name="maxPoolSize">100</property>
<!-- c3p0数据库连接池最多维护的Statement的个数 -->
<property name="maxStatements">50</property>
<!-- 每个连接中可以最多使用的Statement的个数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
其次:方式二:使用配置文件,创建连接池,获取连接。
@Test
public void testGetConnection1() throws SQLException{
ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//配置文件名
Connection conn = cpds.getConnection();
System.out.println(conn);
}
}
4 DBCP数据库连接池的两种连接
首先导入dbcp jar包和 pool包(dbcp依赖于pool)
方式一:不推荐
@Test
public void testGetConnection() throws SQLException{
//创建了DBCP的数据库连接池
BasicDataSource source = new BasicDataSource();
//设置基本信息
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql:///test");
source.setUsername("root");
source.setPassword("root");
//还可以设置其他涉及数据库连接池管理的相关属性:
source.setInitialSize(10);
source.setMaxActive(10);
//。。。
Connection conn = source.getConnection();
System.out.println(conn);
}
方式二:使用配置文件
首先写dbcp.properties配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
username=root
password=root
initialSize=10
//方式二:推荐:使用配置文件
@Test
public void testGetConnection1() throws Exception{
Properties pros = new Properties();
//方式1:类的加载器加载文件
// InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
//方式2:
FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);//加载流
DataSource source = BasicDataSourceFactory.createDataSource(pros);//BasicDataSourceFactory创建一个BasicDataSource对象
Connection conn = source.getConnection();
System.out.println(conn);
}
5 Durid数据库连接池的实现
首先写durid.properties配置文件:
url=jdbc:mysql:///test
username=root
password=root
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=10
其次使用配置文件
package com.atguigu4.connection;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class DruidTest {
@Test
public void getConnection() throws Exception{//z
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);//加载配置文件
DataSource source = DruidDataSourceFactory.createDataSource(pros);
Connection conn = source.getConnection();
System.out.println(conn);
}
}