一、数据库连接池
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("执行了事务回滚...");
}
}
}