数据库连接池和DbUtil工具使用

一、数据库连接池

1.1 数据库连接池介绍

因为创建Connection对象也会消耗系统资源。因此,如果应用程序需要大量创建Connection对象,就有可能会导致服务器的资源很快就被消耗完。

  • 解决办法:

当服务器启动的时候,就预先创建一批的Connection对象保存在一个容器中。每次用户访问数据库的时候,先从该容器中获取一个数据库连接。如果用完之后,再重新把该connection放回到容器中,给其他用户使用。这个容器就是“数据库连接池”。

使用数据库连接池的好处:
1) 减少创建数据库连接的数量;
2) 提高程序的执行效率;

1.2 实现数据库连接池

第一步:定义一个类,实现DataSource接口。
第二步:定义一些变量用来存储连接池的相关信息;
第三步:实现getConnection方法;

class MyPool implements DataSource {
	private String url = "jdbc:mysql://localhost:3306/entor_db"; //数据库URL
	private String userName = "root"; // 数据库用户名
	private String password = "root"; // 数据库用户密码
	private int maxPoolSize = 5;  // 连接池最大的连接数
	private int currentPoolSize = 0; // 当前连接池的连接数
	
	//加载驱动
	static {
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	LinkedList<Connection> pool = new LinkedList<Connection>(); //保存数据库连接
	
	//从连接池中获取一个数据库连接
	@Override
	public Connection getConnection() throws SQLException {
		if (pool.size() > 0) {
			return pool.removeLast();
		}
		if (currentPoolSize < maxPoolSize) {
			return createConnection();
		}
		throw new RuntimeException("连接池的连接已经用完了!");
	}
	
	//创建一个数据库连接,然后保存到连接池中
	public Connection createConnection() throws SQLException {
		Connection conn = DriverManager.getConnection(url, userName, password);
		currentPoolSize++;
		return conn;
	}
	
	//把数据库连接重新返回到连接池中
	public void release(Connection conn) {
		pool.add(conn);
	}}

public class Demo1 {

	public static void main(String[] args) throws SQLException {
		//从连接池中获取5个数据库连接
		MyPool myPool = new MyPool();
		for (int i = 0; i < 5; i++) {
			Connection conn = myPool.getConnection();
			System.out.println("第" + (i + 1) + "连接:" + conn);
			if (i == 3) {
				//把数据库连接返回到连接池中
				myPool.release(conn); 
			}
		}
		
		System.out.println(myPool.getConnection());
	}

}

1.3 装饰者设计模式

如果需要对一个类的功能进行增强,而又不能够修改原有类的代码。那么就可以使用装饰者设计模式。

  • 实现装饰者设计模式的思路:

第一步:让装饰者和被装饰者类都共同实现相同的接口或继承相同的父类;
第二步:在装饰类中包含被装饰的类;
第三步:在装饰类定义一个方法,该方法用来增强被装饰类的方法;

例如:自定义BufferedReader类。

功能要求:
1)输出每一行的前面都要加上行号;
2)输出每一行的后面都加上

3)整合前面这两点的功能;

class LineNumBufferedReader extends BufferedReader {
	BufferedReader br;
	static int num = 1; //记录当前第几行

	public LineNumBufferedReader(BufferedReader br) {
		super(br); //为了不让它出错
		this.br = br;
	}

	@Override
	public String readLine() throws IOException {
		String line = br.readLine();
		if (line == null) {
			return null;
		} 
		return (num++) + "." + line;
	}
}

//每一行后面输出<br/>
class NewLineBufferedReader extends BufferedReader {
	BufferedReader br;
	
	public NewLineBufferedReader(BufferedReader br) {
		super(br);
		this.br = br;
	}
	
	@Override
	public String readLine() throws IOException {
		String line = br.readLine();
		if (line == null) {
			return null;
		}
		return line + "<br/>";
	}
	
}

public class Demo2 {

	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new FileReader(
				"D:/workspace-entor/day21/src/com/entor/demo03装饰者设计模式/Demo1.java"));
		//在每一行的前面加上行号 
		LineNumBufferedReader lineNumBr = new LineNumBufferedReader(br);
		//在每一行的后面加上<br/>
		NewLineBufferedReader newLineBr = new NewLineBufferedReader(lineNumBr);
		String line = null;
		while ((line = newLineBr.readLine()) != null) {
			System.out.println(line);
		}
	}

}

1.4 增强close方法

第一步:创建一个MyConnection装饰类,实现Connection接口;
第二步:实现该接口的createStatement方法和close方法;
第三步:把MyPool中的所有Connection全部替换成MyConnection;

public class MyConnection implements Connection {
	private MyPool pool;
	private Connection conn; //被装饰类
	
	public MyConnection(MyPool pool, Connection conn) {
		this.pool = pool;
		this.conn = conn;
	}
	
	@Override
	public Statement createStatement() throws SQLException {
		return conn.createStatement();
	}
	
	@Override
	public void close() throws SQLException {
		pool.release(this);
	}

}

class MyPool implements DataSource {
	private String url = "jdbc:mysql://localhost:3306/entor_db"; //数据库URL
	private String userName = "root"; //用户名
	private String password = "root"; //密码
	private int maxPoolSize = 5;  //连接池最大的连接数
	private int currentPoolSize = 0; //当前连接池的连接数
	
	//加载驱动
	static {
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	LinkedList<MyConnection> pool = new LinkedList<MyConnection>(); //保存数据库连接
	
	//从连接池中获取一个数据库连接
	@Override
	public MyConnection getConnection() throws SQLException {
		if (pool.size() > 0) {
			return pool.removeLast();
		}
		if (currentPoolSize < maxPoolSize) {
			return createConnection();
		}
		throw new RuntimeException("连接池的连接已经用完了!");
	}
	
	//创建一个数据库连接,然后保存到连接池中
	public MyConnection createConnection() throws SQLException {
		Connection conn = DriverManager.getConnection(url, userName, password);
		MyConnection myConn = new MyConnection(this, conn);
		currentPoolSize++;
		return myConn;
	}
	
	//把数据库连接重新返回到连接池中
	public void release(MyConnection conn) {
		pool.add(conn);
	}}

二、 C3P0连接池

2.1 使用C3P0数据库连接池的基本步骤

第一步:把C3P0的jar包导入到工程中;
第二步:创建数据库连接池对象;

ComboPooledDataSource ds = new ComboPooledDataSource();

第三步:配置连接池;

  • setDriverClass():指定驱动的名字;
  • setJdbcUrl():指定数据库的URL;
  • setUser():指定数据库的用户名;
  • setPassword() :指定数据库的密码;
  • setInitialPoolSize():连接池的默认连接数
  • setMaxPoolSize():连接池的最大连接数
  • setMinPoolSize():连接池的最少连接数
  • setAcquireIncrement():每次增加多少个连接
  • setCheckoutTimeout():获取连接的等待时间,以毫秒为单位

第四步:获取数据库连接;

Connection conn = ds.getConnection();

第五步:把数据库连接释放出来;

conn.close();

例如:

public class Demo1 {

	public static void main(String[] args) throws Exception {
		//创建数据库连接池对象
		ComboPooledDataSource ds = new ComboPooledDataSource();
		//配置连接池
		ds.setDriverClass("com.mysql.jdbc.Driver");
		ds.setJdbcUrl("jdbc:mysql://localhost:3306/entor_db");
		ds.setUser("root");
		ds.setPassword("root");
		ds.setInitialPoolSize(5):连接池的默认连接数
		ds.setMaxPoolSize(5):连接池的最大连接数
		ds.setMinPoolSize(3):连接池的最少连接数
		ds.setAcquireIncrement(3):每次增加多少个连接
		ds.setCheckoutTimeout(3000):获取连接的等待时间,以毫秒为单位
		
		
		for (int i = 0; i < 5; i++) {
			Connection conn = ds.getConnection();
			System.out.println(conn);
			if (i == 3) {
				conn.close();
			}
		}
		ds.getConnection();
		
	}

}

2.2 使用配置文件配置连接池信息

第一步:在src目录下新建一个c3p0.properties文件,把连接池的配置信息定义在里面。
第二步:创建ComboPooledDataSource对象,不需要再设置连接池的参数。该对象会自动地从src目录下加载名为c3p0.properties或c3p0-config.xml文件中的配置信息;

<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>
	<default-config> 
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/entor_db</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="user">root</property>
		<property name="password">root</property>
		
		<property name="acquireIncrement">3</property>
		<property name="initialPoolSize">5</property>
		<property name="minPoolSize">2</property>
		<property name="maxPoolSize">5</property>
	</default-config>
</c3p0-config>

注意如果在src目录下同时存在c3p0.properties或c3p0-config.xml文件,那么优先加在c3p0-config.xml文件。

三、 自定义数据库框架

3.1 数据库的元数据

元数据:数据库中的数据库。它是用来保存数据库的一些信息。因此,通过元数据可以获取数据库的信息。例如:数据库的版本号,名称等等。

3.1.1 DatabaseMetaData

DataBaseMetaData代表数据库的原信息。

//获取数据库信息
	public static void method01() throws SQLException {
		//获取数据库连接
		Connection conn = DbUtil.getConnection();
		//获取数据库的元数据
		DatabaseMetaData metaData = conn.getMetaData();
		System.out.println("数据库的版本号:" + metaData.getDatabaseProductVersion());
		System.out.println("数据库的名称:" + metaData.getDatabaseProductName());
	}

3.2.2 ParameterMetaData

ParameterMetaData代表方法参数的元数据。

//获取SQL的参数信息
	public static void method02() throws SQLException {
		//获取SQL中参数的个数
		Connection conn = DbUtil.getConnection();
		//编写SQL
		String sql = "insert into emp(empno, ename) values(?, ?)";
		//创建PreparedStatement对象
		PreparedStatement pstmt = conn.prepareStatement(sql);
		//获取参数元数据
		ParameterMetaData parameterMetaData = pstmt.getParameterMetaData();
		System.out.println("获取参数的个数:" + parameterMetaData.getParameterCount());
	}

获取结果集元数据:@ResultSetMetaData
```java
//获取结果集元数据
	public static void method03() throws SQLException {
		//获取SQL中参数的个数
		Connection conn = DbUtil.getConnection();
		String sql = "select * from emp";
		PreparedStatement pstmt = conn.prepareStatement(sql);
		//获取结果集元数据
		ResultSetMetaData metaData = pstmt.getMetaData();
		int count = metaData.getColumnCount();
		System.out.println("列的数量:" + count);
		//获取所有列的名字
		for (int i = 1; i <= count; i++) {
			String columnName = metaData.getColumnName(i);
			System.out.println(columnName);
		}
	}

3.2 自定义数据库框架

所谓的数据库框架,其实就是一些操作数据库的工具类组合。可以简化数据库的操作,提高开发效率。

3.2.1 自定义数据库框架的步骤

第一步:创建一个BaseDao类,该类提供一些执行数据库增删查改的方法;

/*
 * 提供一些基本的数据库操作
 */
public class BaseDao {

	/**
	 * 实现数据库的增删改操作
	 * @throws SQLException 
	 */
	public void update(String sql, Object[] params) throws SQLException {
		//获取数据库连接
		Connection conn = DbUtil.getConnection();
		//创建Statement对象
		PreparedStatement pstmt = conn.prepareStatement(sql);
		//设置参数
		//获取参数的个数
		ParameterMetaData parameterMetaData = pstmt.getParameterMetaData();
		//获取需要传入参数的个数
		int count = parameterMetaData.getParameterCount();
		if (count != params.length) {
			throw new RuntimeException("传入参数个数不匹配!");
		}
		//设置参数
		for (int i = 1; i <= count; i++) {
			pstmt.setObject(i, params[i - 1]);
		}
		//执行SQL
		pstmt.executeUpdate();
		//释放资源
		DbUtil.release(conn, pstmt, null);
	}
	
}

第二步:其他的Dao类中继承BaseDao类,然后调用它的方法访问数据库。

public class EmpDao extends BaseDao {

	public void addEmp() throws SQLException {
		String sql = "insert into emp(empno, ename) values(?, ?)";
		update(sql, new Object[]{1015, "铁蛋"});
	}
	
	//修改员工
	public void updateEmp() throws SQLException {
		//编写SQL
		String sql = "update emp set ename = ? where empno = ?";
		update(sql, new Object[]{"小宝", 1015});
	}
	
	public void deleteEmp() throws SQLException {
		//编写SQL
		String sql = "delete from emp where empno = ?";
		update(sql, new Object[]{1015});
	}
	
	public void findEmp() {
		
	}
	
	public static void main(String[] args) throws SQLException {
		EmpDao empDao = new EmpDao();
		//empDao.addEmp();
		//empDao.updateEmp();
		empDao.deleteEmp();
	}
	
}

3.2.2 JavaBean介绍

JavaBean的作用:一个JavaBean对象用来存储一行数据。JavaBean对象的每一个属性对应着数据库表的一个字段。而且属性名和字段名相同。
p3,

  • 定义javaBean的语法规则:

1) 把成员属性私有化;
2) 为每一个成员属性提供对应的setter和getter方法;
3) 提供一个无参的构造函数;
4) 实现Seriablizable接口;

public class Emp {
	private int empno;
	private String ename;
	
	public Emp() {
	}
	
	public Emp(int empno, String ename) {
		this.empno = empno;
		this.ename = ename;
	}

	public int getEmpno() {
		return empno;
	}
	
	public void setEmpno(int empno) {
		this.empno = empno;
	}
	
	public String getEname() {
		return ename;
	}
	
	public void setEname(String ename) {
		this.ename = ename;
	}

	@Override
	public String toString() {
		return "Emp [empno=" + empno + ", ename=" + ename + "]";
	}
	
}

### 3.2.3 自定义结果处理器
作用:对查询的结果进行相关的处理。例如,获取结果集中的数据,然后再把它们封装到JavaBean或List集合中。

```java
interface ResultHandler {

	/**
	 * 对结果进行处理
	 * @param rs 结果集
	 * @return 处理后的结果对象,它的值是JavaBean或List集合
	 * @throws Exception
	 */
	Object handle(ResultSet rs) throws Exception;
	
}

BeanHandler是专门用来处理单行结果的处理器对象。它可以把一行的数据封装成一个JavaBean对象。

public class BeanHandler implements ResultHandler {
	private Class clazz;
	
	public BeanHandler(Class clazz) {
		this.clazz = clazz;
	}

	@Override
	public Object handle(ResultSet rs) throws Exception {
		//获取它的构造函数
		Constructor constructor = clazz.getConstructor();
		//通过构造函数创建对象
		Object o = constructor.newInstance();
		if (rs.next()) {
			//获取结果集的元数据
			ResultSetMetaData metaData = rs.getMetaData();
			//获取列的数量
			int count = metaData.getColumnCount();
			for (int i = 1; i <= count; i++) {
				//获取每一列的名字
				String columnName = metaData.getColumnName(i);
				//根据列名获取值
				Object columnValue = rs.getObject(columnName);
				System.out.println("----------->" + columnName);
				//设置对象的成员数据
				Field field = clazz.getDeclaredField(columnName);
				//暴力反射
				field.setAccessible(true);
				//设置属性的值
				field.set(o, columnValue);
			}
		}
		return o;
	}

}

BeanListHandler是专门用来处理多行结果的处理器对象。它可以把多行的数据封装到一个List集合中。

public class BeanListHandler implements ResultHandler {
	private Class clazz;
	
	public BeanListHandler(Class clazz) {
		this.clazz = clazz;
	}

	@Override
	public Object handle(ResultSet rs) throws Exception {
		//获取它的构造函数
		Constructor constructor = clazz.getConstructor();
		//通过构造函数创建对象
		Object o = constructor.newInstance();
		//保存多行数据
		List dataList = new ArrayList();
		while (rs.next()) {
			//获取结果集的元数据
			ResultSetMetaData metaData = rs.getMetaData();
			//获取列的数量
			int count = metaData.getColumnCount();
			for (int i = 1; i <= count; i++) {
				//获取每一列的名字
				String columnName = metaData.getColumnName(i);
				//根据列名获取值
				Object columnValue = rs.getObject(columnName);
				//设置对象的成员数据
				Field field = clazz.getDeclaredField(columnName);
				//暴力反射
				field.setAccessible(true);
				//设置属性的值
				field.set(o, columnValue);
			}
			dataList.add(o);
		}
		return dataList;
	}

}

在BaseDao类中实现公共的查找功能。

/**
	 * 该方法主要把查询的结果封装成JavaBean对象或者List集合,并返回该对象。
	 * @param sql 要执行的SQL
	 * @param params 传入的参数
	 * @param rh 专门处理结果的处理器。
	 * @return 如果传入的处理器是BeanHandler对象,那么返回一个JavaBean对象。如果传入的是BeanListHandler,那么返回一个List集合
	 * @throws Exception 
	 */
	public static Object find(String sql, Object[] params, ResultHandler rh) throws Exception {
		//获取数据库连接
		Connection conn = DbUtil.getConnection();
		//创建Statement对象
		PreparedStatement pstmt = conn.prepareStatement(sql);
		//设置参数
		//获取参数的个数
		ParameterMetaData parameterMetaData = pstmt.getParameterMetaData();
		//获取需要传入参数的个数
		int count = parameterMetaData.getParameterCount();
		if (params != null && count != params.length) {
			throw new RuntimeException("传入参数个数不匹配!");
		}
		//设置参数
		for (int i = 1; i <= count; i++) {
			pstmt.setObject(i, params[i - 1]);
		}
		//执行SQL
		ResultSet rs = pstmt.executeQuery();
		//对结果进行处理
		Object o = rh.handle(rs);
		//释放资源
		DbUtil.release(conn, pstmt, rs);
		return o;
	}

四、DbUtil工具

DbUtil是一个数据库的工具。用来简化数据库的操作。

4.1 使用DbUtil的步骤

第一步:把DbUtil的jar导入到工程中;
第二步:创建一个QueryRunner对象;
QueryRunner():创建一个QueryRunner对象。该QueryRunner对象可以手动地指定事务;
QueryRunner(DataSource ds):指定连接池来创建一个不带事务QueryRunner对象;
第三步:调用该对象的方法执行数据库的操作;

  • update(String sql, Object[] params):执行更新;
  • query(String sql, ResultSetHandler rsh, Objectp[] params):执行查询;
public class Demo1 {
	//C3P0的连接池
	private static ComboPooledDataSource ds = new ComboPooledDataSource();
	//不带事务QueryRunner对象
	private static QueryRunner queryRunner = new QueryRunner(ds);

	public static void main(String[] args) throws SQLException {
		/*//添加数据
		String sql = "insert into emp(empno, ename) values(?, ?)";
		queryRunner.update(sql, new Object[]{1015, "小宝"});
		
		//修改数据
		String sql = "update emp set ename = ? where empno = ?";
		queryRunner.update(sql, new Object[]{"大宝", 1015});
		
		//删除数据
		String sql = "delete from emp where empno = ?";
		queryRunner.update(sql, 1015);
		
		//查询数据
		String sql = "select empno, ename from emp where empno = ?";
		Object o = queryRunner.query(sql, new BeanHandler(Emp.class), new Object[]{1001});
		System.out.println((Emp) o);
		
		//查询多行数据
		String sql = "select empno, ename from emp";
		Object o = queryRunner.query(sql, new BeanListHandler(Emp.class));
		System.out.println((List<Emp>) o);*/
		
		//统计emp表的总的记录数
		String sql = "select count(*) from emp";
		Object o = queryRunner.query(sql, new ScalarHandler());
		System.out.println((long) o);
		
	}

}

常用的结果处理器:

  • BeanHandler:处理单行结果的处理器;
  • BeanListHandler:处理多行结果的处理器;
  • ScalarHandler:处理单个结果的处理器;

4.2 使用事务

如果要使用事务功能,那么创建QueryRunner对象的时候就不能够传入一个数据源。而且在调用QueryRunner对象方法的时候,需要传入Connection对象。

  • update(Connection conn, String sql, Object[] params):执行更新;
  • query(Connection conn, String sql, ResultSetHandler rsh, Objectp[] params):执行查询;
public class Demo2 {
	private static QueryRunner queryRunner = new QueryRunner();
	private static ComboPooledDataSource ds = new ComboPooledDataSource();

	public static void main(String[] args) throws SQLException {
		//获取数据库连接
		Connection conn = ds.getConnection();
		try {
			//关闭自动提交事务功能
			conn.setAutoCommit(false);
			String sql = "insert into emp(empno, ename) values(?, ?)";
			queryRunner.update(conn, sql, new Object[]{1015, "小宝"});
			
			/*if (true) {
				throw new Exception("服务器可能会崩溃!");
			}*/
			
			String sql2 = "insert into emp(empno, ename) values(?, ?)";
			queryRunner.update(conn, sql2, new Object[]{1016, "大宝"});
			//提交事务
			conn.commit();
			//释放资源
			conn.close();
		} catch (Exception e) {
			//回滚事务
			conn.rollback();
			System.out.println("执行了事务回滚...");
		}
		
	}

}
发布了110 篇原创文章 · 获赞 41 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhongliwen1981/article/details/97842747
今日推荐