From 709b8d7a92b874dbbdddbd36f6aed5ddb159032a Mon Sep 17 00:00:00 2001 From: ZhangZiSheng001 <18826241741@163.com> Date: Sun, 1 Sep 2019 11:32:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E9=A6=96=E6=AC=A1=E5=88=9B?= =?UTF-8?q?=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 37 ++ README.md | 478 ++++++++++++++++++++++++ pom.xml | 38 ++ src/main/java/cn/zzs/dbcp/JDBCUtil.java | 136 +++++++ src/main/resources/jdbc.properties | 100 +++++ src/main/resources/log4j.properties | 183 +++++++++ src/test/java/cn/zzs/dbcp/DBCPTest.java | 146 ++++++++ 7 files changed, 1118 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/cn/zzs/dbcp/JDBCUtil.java create mode 100644 src/main/resources/jdbc.properties create mode 100644 src/main/resources/log4j.properties create mode 100644 src/test/java/cn/zzs/dbcp/DBCPTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c09c71a --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# maven ignore +target/ +*.jar +!.mvn/wrapper/* +*.war +*.zip +*.tar +*.tar.gz + +# eclipse ignore +.settings/ +.project +.classpath + +# idea ignore +.idea/ +*.ipr +*.iml +*.iws + +# temp ignore +*.log +*.cache +*.diff +*.patch +*.tmp + +# system ignore +.DS_Store +Thumbs.db +*.orig + +# flatten ignore +.flattened-pom.xml + +# license check result +license-list diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ba1e77 --- /dev/null +++ b/README.md @@ -0,0 +1,478 @@ + +# DBCP + +## 简介 +DBCP用于创建和管理连接,利用连接池的方式复用连接减少了资源开销。 + +连接池的参数可以采用`properties`文件来配置:配置包括驱动、链接、账号密码,连接池基本参数,事务相关参数,连接测试的参数以及内存回收参数等。 + +DBCP对外交互主要是一个`BasicDataDource`,用于设置连接池参数和获取连接对象,作用有点类似于JDK的`DriverManager`。通过源码可以看到,`BasicDataSource`内部有一个`dataSource` 和`connectionPool`字段。 + +`dataSource`用于从连接池中获取连接。 +`connectionPool`用于创建,存储和管理池中的连接,里面有一个`Map`对象和`LinkedBlockingDeque`对象,分别存储着所有连接和空闲连接,构成所谓的“池”。 + + +## 使用例子 +### 需求 +使用DBCP连接池获取连接对象,对用户数据进行增删改查。 + +### 工程环境 +JDK:1.8.0_201 +maven:3.6.1 +IDE:Spring Tool Suites4 for Eclipse +mysql驱动:8.0.15 +mysql:5.7 + +### 主要步骤 +DBCP对外交互主要是一个`BasicDataDource`,用于设置连接池参数和获取连接对象。 +1. 通过`BasicDataSourceFactory.createDataSource(properties)`设置连接池参数,并获得`BasicDataDource`对象; +2. 获取连接对象:调用`BasicDataDource`对象的`getConnection()`方法获取`Connection`对象。 + +### 创建表 +```sql +CREATE DATABASE `demo`CHARACTER SET utf8 COLLATE utf8_bin; +User `demo`; +CREATE TABLE `user` ( + `id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id', + `name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户名', + `age` int(10) unsigned DEFAULT NULL COMMENT '用户年龄', + `gmt_create` datetime DEFAULT NULL COMMENT '记录创建时间', + `gmt_modified` datetime DEFAULT NULL COMMENT '记录最后修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; +``` + +### 创建项目 +项目类型Maven Project,打包方式jar + +### 引入依赖 +```xml + + + junit + junit + 4.12 + test + + + + org.apache.commons + commons-dbcp2 + 2.6.0 + + + + log4j + log4j + 1.2.17 + + + + mysql + mysql-connector-java + 8.0.15 + +``` + +### 编写jdbc.prperties +路径:resources目录下 +```properties +#数据库配置 +driverClassName=com.mysql.cj.jdbc.Driver +url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true +username=root +password=root +``` + +### 编写JDBCUtil用于获得连接对象 +这里设置工具类的目的是避免多个线程使用同一个连接对象,并提供了释放资源的方法(注意,考虑到重用性,这里并不会关闭连接)。 +路径:`cn.zzs.jdbc` +```java +/** + * @ClassName: JDBCUtil + * @Description: 用于获取数据库连接对象的工具类 + * @author: zzs + * @date: 2019年8月31日 下午9:05:08 + */ +public class JDBCUtil { + private static DataSource dataSource; + private static ThreadLocal tl = new ThreadLocal<>(); + private static Object obj = new Object(); + + static { + init(); + } + /** + * + * @Title: getConnection + * @Description: 获取数据库连接对象的方法,线程安全 + * @author: zzs + * @date: 2019年8月31日 下午9:22:29 + * @return: Connection + */ + public static Connection getConnection(){ + //从当前线程中获取连接对象 + Connection connection = tl.get(); + //判断为空的话,创建连接并绑定到当前线程 + if(connection == null) { + synchronized (obj) { + if(tl.get() == null) { + connection = createConnection(); + tl.set(connection); + } + } + } + return connection; + } + /** + * + * @Title: release + * @Description: 释放资源 + * @author: zzs + * @date: 2019年8月31日 下午9:39:24 + * @param conn + * @param statement + * @return: void + */ + public static void release(Connection conn,Statement statement,ResultSet resultSet) { + if(resultSet!=null) { + try { + resultSet.close(); + } catch (SQLException e) { + System.err.println("关闭ResultSet对象异常"); + e.printStackTrace(); + } + } + if(statement != null) { + try { + statement.close(); + } catch (SQLException e) { + System.err.println("关闭Statement对象异常"); + e.printStackTrace(); + } + } + //注意:这里不关闭连接 + if(conn!=null) { + try { + //如果连接失效的话,从当前线程的绑定中删除 + if(!conn.isValid(3)) { + tl.remove(); + } + } catch (SQLException e) { + System.err.println("校验连接有效性"); + e.printStackTrace(); + } + } + } + + /** + * + * @Title: createConnection + * @Description: 创建数据库连接 + * @author: zzs + * @date: 2019年8月31日 下午9:27:03 + * @return: Connection + */ + private static Connection createConnection(){ + Connection conn = null; + //获得连接 + try { + conn = dataSource.getConnection(); + } catch (SQLException e) { + System.err.println("从数据源获取连接失败"); + e.printStackTrace(); + } + return conn; + } + + /** + * @Title: init + * @Description: 根据指定配置文件创建数据源对象 + * @author: zzs + * @date: 2019年9月1日 上午10:53:05 + * @return: void + */ + private static void init() { + //导入配置文件 + Properties properties = new Properties(); + InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"); + try { + properties.load(in); + //根据配置文件内容获得数据源对象 + dataSource = BasicDataSourceFactory.createDataSource(properties); + } catch (IOException e) { + System.err.println("导入配置文件出错"); + e.printStackTrace(); + } catch (Exception e) { + System.err.println("根据指定配置文件创建数据源出错"); + e.printStackTrace(); + } + } +} + +``` + +### 编写测试类 +路径:test目录下的`cn.zzs.jdbc` + +#### 添加用户 +注意:这里引入了事务 +```java +/** + * 测试添加用户 + * @throws SQLException + */ +@Test +public void saveUser() throws Exception { + //创建sql + String sql = "insert into user values(null,?,?,?,?)"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + try { + //设置非自动提交 + connection.setAutoCommit(false); + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setString(1, "zzs001"); + statement.setInt(2, 18); + statement.setDate(3, new Date(System.currentTimeMillis())); + statement.setDate(4, new Date(System.currentTimeMillis())); + //执行 + statement.executeUpdate(); + //提交事务 + connection.commit(); + } catch (Exception e) { + System.out.println("异常导致操作回滚"); + connection.rollback(); + e.printStackTrace(); + } finally { + //释放资源 + JDBCUtil.release(connection, statement,null); + } +} +``` +#### 更新用户 +```java +/** + * 测试更新用户 + */ +@Test +public void updateUser() throws Exception { + //创建sql + String sql = "update user set age = ?,gmt_modified = ? where name = ?"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + try { + //设置非自动提交 + connection.setAutoCommit(false); + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setInt(1, 19); + statement.setDate(2, new Date(System.currentTimeMillis())); + statement.setString(3, "zzs001"); + //执行 + statement.executeUpdate(); + //提交事务 + connection.commit(); + } catch (Exception e) { + System.out.println("异常导致操作回滚"); + connection.rollback(); + e.printStackTrace(); + } finally { + //释放资源 + JDBCUtil.release(connection, statement,null); + } +} +``` +#### 查询用户 +```java +/** + * 测试查找用户 + */ +@Test +public void findUser() throws Exception { + //创建sql + String sql = "select * from user where name = ?"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + ResultSet resultSet = null; + try { + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setString(1, "zzs001"); + //执行 + resultSet = statement.executeQuery(); + //遍历结果集 + while (resultSet.next()) { + String name = resultSet.getString(2); + int age = resultSet.getInt(3); + System.out.println("用户名:" + name + ",年龄:" + age); + } + } finally { + //释放资源 + JDBCUtil.release(connection, statement,resultSet); + } +} +``` +#### 删除用户 +```java +/** + * 测试删除用户 + */ +@Test +public void deleteUser() throws Exception { + //创建sql + String sql = "delete from user where name = ?"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + try { + //设置非自动提交 + connection.setAutoCommit(false); + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setString(1, "zzs001"); + //执行 + statement.executeUpdate(); + //提交事务 + connection.commit(); + } catch (Exception e) { + System.out.println("异常导致操作回滚"); + connection.rollback(); + e.printStackTrace(); + } finally { + //释放资源 + JDBCUtil.release(connection, statement,null); + } +} +``` + +### dbcp配置文件详解 +#### 数据库基本配置 +```properties +driverClassName=com.mysql.cj.jdbc.Driver +url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true +username=root +password=root +``` +#### 连接数据相关参数 +```properties +#-------------连接数据相关参数-------------------------------- +#初始化连接:连接池启动时创建的初始化连接数量 +#默认为0 +initialSize=0 +#最大活动连接 +#连接池在同一时间能够分配的最大活动连接的数量, 如果设置为非正数则表示不限制 +#默认为8 +maxActive=8 +#最大空闲连接 +#连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制 +#默认为8 +maxIdle=8 +#最小空闲连接 +#连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建 +#默认为0 +minIdle=0 +#最大等待时间 +#当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为-1表示无限等待 +#默认无限 +maxWait=-1 +``` + +#### 事务相关的属性 +```properties +#-------------事务相关的属性-------------------------------- +#连接池创建的连接的默认的auto-commit状态 +#默认为true +defaultAutoCommit=false +#连接池创建的连接的默认的read-only状态. 如果没有设置则setReadOnly方法将不会被调用. (某些驱动不支持只读模式,比如:Informix) +#默认值由驱动决定 +#defaultReadOnly=false +#连接池创建的连接的默认的TransactionIsolation状态 +#可用值为下列之一:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE +#默认值由驱动决定 +defaultTransactionIsolation=REPEATABLE_READ +#连接池创建的连接的默认的catalog +#defaultCatalog +``` +#### 连接检查情况 +```properties +#-------------连接检查情况-------------------------------- +#SQL查询,用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录 +validationQuery= select 1 +#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个. +#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 +#默认为true +testOnBorrow=true +#指明是否在归还到池中前进行检验 +#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 +#默认为false +testOnReturn=false +#是否开启空闲资源监测。 +#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 +#默认为false +testWhileIdle= true +#空闲资源的检测周期(单位为毫秒)。默认-1:不检测。建议设置,周期自行选择。timeBetweenEvictionRunsMillis=30000 +#做空闲资源检测时,每次的采样数。默认3。 +#可根据自身应用连接数进行微调,如果设置为-1,就是对所有连接做空闲监测。 +numTestsPerEvictionRun=3 +#资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除。 +#默认值1000*60*30 = 30分钟。建议默认,或根据自身业务选择。 +minEvictableIdleTimeMillis=1800000 +``` + +#### 缓存语句 +```properties +#-------------缓存语句-------------------------------- +#开启池的prepared statement 池功能 +#注意: 确认连接还有剩余资源可以留给其他statement +#默认为false +poolPreparedStatements=false +#statement池能够同时分配的打开的statements的最大数量, 如果设置为0表示不限制 +#默认为0 +maxOpenPreparedStatements=0 +``` + +#### 连接泄漏回收参数 +```properties +#-------------连接泄漏回收参数-------------------------------- +#标记是否删除泄露的连接,如果他们超过了removeAbandonedTimout的限制. +#如果设置为true, 连接被认为是被泄露并且可以被删除,如果空闲时间超过removeAbandonedTimeout. +#设置为true可以为写法糟糕的没有关闭连接的程序修复数据库连接. +#默认为false +removeAbandoned=false +#泄露的连接可以被删除的超时值, 单位秒 +#默认为300 +removeAbandonedTimeout=300 +#标记当Statement或连接被泄露时是否打印程序的stack traces日志。 +#被泄露的Statements和连接的日志添加在每个连接打开或者生成新的Statement,因为需要生成stack trace。 +#默认为false +logAbandoned=false +#如果开启"removeAbandoned",那么连接在被认为泄露时可能被池回收. +#这个机制在(getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)时被触发. +#举例当maxActive=20, 活动连接为18,空闲连接为1时可以触发"removeAbandoned". +#但是活动连接只有在没有被使用的时间超过"removeAbandonedTimeout"时才被删除,默认300秒.在resultset中游历不被计算为被使用. +``` + +#### 其他 +```properties +#-------------其他-------------------------------- +#控制PoolGuard是否容许获取底层连接 +#默认为false +accessToUnderlyingConnectionAllowed=false +#如果容许则可以使用下面的方式来获取底层物理连接: +# Connection conn = ds.getConnection(); +# Connection dconn = ((DelegatingConnection) conn).getInnermostDelegate(); +# ... +# conn.close(); +``` + +> 学习使我快乐!! diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..22d0f2b --- /dev/null +++ b/pom.xml @@ -0,0 +1,38 @@ + + 4.0.0 + cn.zzs.dbcp + DBCP-demo + jar + 1.0.0 + DBCP-demo + http://maven.apache.org + + + + junit + junit + 4.12 + test + + + + org.apache.commons + commons-dbcp2 + 2.6.0 + + + + log4j + log4j + 1.2.17 + + + + mysql + mysql-connector-java + 8.0.15 + + + diff --git a/src/main/java/cn/zzs/dbcp/JDBCUtil.java b/src/main/java/cn/zzs/dbcp/JDBCUtil.java new file mode 100644 index 0000000..f21df69 --- /dev/null +++ b/src/main/java/cn/zzs/dbcp/JDBCUtil.java @@ -0,0 +1,136 @@ +package cn.zzs.dbcp; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp2.BasicDataSourceFactory; + +/** + * @ClassName: JDBCUtil + * @Description: 用于获取数据库连接对象的工具类 + * @author: zzs + * @date: 2019年8月31日 下午9:05:08 + */ +public class JDBCUtil { + private static DataSource dataSource; + private static ThreadLocal tl = new ThreadLocal<>(); + private static Object obj = new Object(); + + static { + init(); + } + /** + * + * @Title: getConnection + * @Description: 获取数据库连接对象的方法,线程安全 + * @author: zzs + * @date: 2019年8月31日 下午9:22:29 + * @return: Connection + */ + public static Connection getConnection(){ + //从当前线程中获取连接对象 + Connection connection = tl.get(); + //判断为空的话,创建连接并绑定到当前线程 + if(connection == null) { + synchronized (obj) { + if(tl.get() == null) { + connection = createConnection(); + tl.set(connection); + } + } + } + return connection; + } + /** + * + * @Title: release + * @Description: 释放资源 + * @author: zzs + * @date: 2019年8月31日 下午9:39:24 + * @param conn + * @param statement + * @return: void + */ + public static void release(Connection conn,Statement statement,ResultSet resultSet) { + if(resultSet!=null) { + try { + resultSet.close(); + } catch (SQLException e) { + System.err.println("关闭ResultSet对象异常"); + e.printStackTrace(); + } + } + if(statement != null) { + try { + statement.close(); + } catch (SQLException e) { + System.err.println("关闭Statement对象异常"); + e.printStackTrace(); + } + } + //注意:这里不关闭连接 + if(conn!=null) { + try { + //如果连接失效的话,从当前线程的绑定中删除 + if(!conn.isValid(3)) { + tl.remove(); + } + } catch (SQLException e) { + System.err.println("校验连接有效性"); + e.printStackTrace(); + } + } + } + + /** + * + * @Title: createConnection + * @Description: 创建数据库连接 + * @author: zzs + * @date: 2019年8月31日 下午9:27:03 + * @return: Connection + */ + private static Connection createConnection(){ + Connection conn = null; + //获得连接 + try { + conn = dataSource.getConnection(); + } catch (SQLException e) { + System.err.println("从数据源获取连接失败"); + e.printStackTrace(); + } + return conn; + } + + /** + * @Title: init + * @Description: 根据指定配置文件创建数据源对象 + * @author: zzs + * @date: 2019年9月1日 上午10:53:05 + * @return: void + */ + private static void init() { + //导入配置文件 + Properties properties = new Properties(); + InputStream in = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties"); + try { + properties.load(in); + //根据配置文件内容获得数据源对象 + dataSource = BasicDataSourceFactory.createDataSource(properties); + } catch (IOException e) { + System.err.println("导入配置文件出错"); + e.printStackTrace(); + } catch (Exception e) { + System.err.println("根据指定配置文件创建数据源出错"); + e.printStackTrace(); + } + } +} + diff --git a/src/main/resources/jdbc.properties b/src/main/resources/jdbc.properties new file mode 100644 index 0000000..56f0c42 --- /dev/null +++ b/src/main/resources/jdbc.properties @@ -0,0 +1,100 @@ +#数据库基本配置 +driverClassName=com.mysql.cj.jdbc.Driver +url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true +username=root +password=root + +#-------------连接数据相关参数-------------------------------- +#初始化连接:连接池启动时创建的初始化连接数量 +#默认为0 +initialSize=0 +#最大活动连接 +#连接池在同一时间能够分配的最大活动连接的数量, 如果设置为非正数则表示不限制 +#默认为8 +maxActive=8 +#最大空闲连接 +#连接池中容许保持空闲状态的最大连接数量,超过的空闲连接将被释放,如果设置为负数表示不限制 +#默认为8 +maxIdle=8 +#最小空闲连接 +#连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接,如果设置为0则不创建 +#默认为0 +minIdle=0 +#最大等待时间 +#当没有可用连接时,连接池等待连接被归还的最大时间(以毫秒计数),超过时间则抛出异常,如果设置为-1表示无限等待 +#默认无限 +maxWait=-1 + +#-------------事务相关的属性-------------------------------- +#连接池创建的连接的默认的auto-commit状态 +#默认为true +defaultAutoCommit=false +#连接池创建的连接的默认的read-only状态. 如果没有设置则setReadOnly方法将不会被调用. (某些驱动不支持只读模式,比如:Informix) +#默认值由驱动决定 +#defaultReadOnly=false +#连接池创建的连接的默认的TransactionIsolation状态 +#可用值为下列之一:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE +#默认值由驱动决定 +defaultTransactionIsolation=REPEATABLE_READ +#连接池创建的连接的默认的catalog +#defaultCatalog + +#-------------连接检查情况-------------------------------- +#SQL查询,用来验证从连接池取出的连接,在将连接返回给调用者之前.如果指定,则查询必须是一个SQL SELECT并且必须返回至少一行记录 +validationQuery= select 1 +#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个. +#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 +#默认为true +testOnBorrow=true +#指明是否在归还到池中前进行检验 +#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 +#默认为false +testOnReturn=false +#是否开启空闲资源监测。 +#注意: 设置为true后如果要生效,validationQuery参数必须设置为非空字符串 +#默认为false +testWhileIdle= true +#空闲资源的检测周期(单位为毫秒)。默认-1:不检测。建议设置,周期自行选择。timeBetweenEvictionRunsMillis=30000 +#做空闲资源检测时,每次的采样数。默认3。 +#可根据自身应用连接数进行微调,如果设置为-1,就是对所有连接做空闲监测。 +numTestsPerEvictionRun=3 +#资源池中资源最小空闲时间(单位为毫秒),达到此值后空闲资源将被移除。 +#默认值1000*60*30 = 30分钟。建议默认,或根据自身业务选择。 +minEvictableIdleTimeMillis=1800000 + +#-------------缓存语句-------------------------------- +#开启池的prepared statement 池功能 +#注意: 确认连接还有剩余资源可以留给其他statement +#默认为false +poolPreparedStatements=false +#statement池能够同时分配的打开的statements的最大数量, 如果设置为0表示不限制 +#默认为0 +maxOpenPreparedStatements=0 + +#-------------连接泄漏回收参数-------------------------------- +#标记是否删除泄露的连接,如果他们超过了removeAbandonedTimout的限制. +#如果设置为true, 连接被认为是被泄露并且可以被删除,如果空闲时间超过removeAbandonedTimeout. +#设置为true可以为写法糟糕的没有关闭连接的程序修复数据库连接. +#默认为false +removeAbandoned=false +#泄露的连接可以被删除的超时值, 单位秒 +#默认为300 +removeAbandonedTimeout=300 +#标记当Statement或连接被泄露时是否打印程序的stack traces日志。 +#被泄露的Statements和连接的日志添加在每个连接打开或者生成新的Statement,因为需要生成stack trace。 +#默认为false +logAbandoned=false +#如果开启"removeAbandoned",那么连接在被认为泄露时可能被池回收. +#这个机制在(getNumIdle() < 2) and (getNumActive() > getMaxActive() - 3)时被触发. +#举例当maxActive=20, 活动连接为18,空闲连接为1时可以触发"removeAbandoned". +#但是活动连接只有在没有被使用的时间超过"removeAbandonedTimeout"时才被删除,默认300秒.在resultset中游历不被计算为被使用. + +#-------------其他-------------------------------- +#控制PoolGuard是否容许获取底层连接 +#默认为false +accessToUnderlyingConnectionAllowed=false +#如果容许则可以使用下面的方式来获取底层物理连接: +# Connection conn = ds.getConnection(); +# Connection dconn = ((DelegatingConnection) conn).getInnermostDelegate(); +# ... +# conn.close(); \ No newline at end of file diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties new file mode 100644 index 0000000..393e598 --- /dev/null +++ b/src/main/resources/log4j.properties @@ -0,0 +1,183 @@ +#OFF,systemOut,logFile,logDailyFile,logRollingFile,logMail,logDB,ALL +#①配置根Logger +log4j.rootLogger=debug,systemOut +#OFF,systemOut,logFile,logDailyFile,logRollingFile,logMail,logDB,ALL +#②配置其他Logger +#log4j.logger.myLogger=debug,systemOut +#log4j.additivity.mobileLogger=false + +#输出到控制台 +log4j.appender.systemOut= org.apache.log4j.ConsoleAppender +log4j.appender.systemOut.layout= org.apache.log4j.PatternLayout +log4j.appender.systemOut.layout.ConversionPattern=[%p][Thread:%t]: %m%n +log4j.appender.systemOut.Threshold= debug +log4j.appender.systemOut.ImmediateFlush= TRUE +log4j.appender.systemOut.Target= System.out + +#输出到文件 +log4j.appender.logFile= org.apache.log4j.FileAppender +log4j.appender.logFile.layout= org.apache.log4j.PatternLayout +log4j.appender.logFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread:%t][Class:%c Method: %M]%n%p: %m%n +log4j.appender.logFile.Threshold= DEBUG +log4j.appender.logFile.ImmediateFlush= TRUE +log4j.appender.logFile.Append= TRUE +log4j.appender.logFile.File= logs/file_log.log +log4j.appender.logFile.Encoding= utf-8 + +#按DatePattern输出到文件 +log4j.appender.logDailyFile= org.apache.log4j.DailyRollingFileAppender +log4j.appender.logDailyFile.layout= org.apache.log4j.PatternLayout +log4j.appender.logDailyFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread:%t][Class:%c Method: %M]%n%p: %m%n +#[%-5p][%-22d{yyyy/MM/dd HH:mm:ssS}][%l]%n%m%n +log4j.appender.logDailyFile.Threshold= warn +log4j.appender.logDailyFile.ImmediateFlush= TRUE +log4j.appender.logDailyFile.Append= TRUE +log4j.appender.logDailyFile.File= logs/daily_log +log4j.appender.logDailyFile.DatePattern= '_'yyyy-MM-dd-HH-mm'.log' +log4j.appender.logDailyFile.Encoding= utf-8 + +#设定文件大小输出到文件 +log4j.appender.logRollingFile= org.apache.log4j.RollingFileAppender +log4j.appender.logRollingFile.layout= org.apache.log4j.PatternLayout +log4j.appender.logRollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread:%t][Class:%c Method: %M]%n%p: %m%n +log4j.appender.logRollingFile.Threshold= DEBUG +log4j.appender.logRollingFile.ImmediateFlush= TRUE +log4j.appender.logRollingFile.Append= TRUE +log4j.appender.logRollingFile.File=logs/rolling_log.log +log4j.appender.logRollingFile.MaxFileSize= 1mb +log4j.appender.logRollingFile.MaxBackupIndex= 10 +log4j.appender.logRollingFile.Encoding= utf-8 + +#用Email发送日志 +log4j.appender.logMail= org.apache.log4j.net.SMTPAppender +log4j.appender.logMail.layout= org.apache.log4j.HTMLLayout +log4j.appender.logMail.layout.LocationInfo= TRUE +log4j.appender.logMail.layout.Title= My Mail LogFile +log4j.appender.logMail.Threshold= DEBUG +log4j.appender.logMail.SMTPDebug= FALSE +log4j.appender.logMail.SMTPHost= SMTP.163.com +log4j.appender.logMail.From= zzs@163.com +log4j.appender.logMail.To= zzs@gmail.com +#log4j.appender.logMail.Cc= xly3000@gmail.com +#log4j.appender.logMail.Bcc= xly3000@gmail.com +log4j.appender.logMail.SMTPUsername= zzs +log4j.appender.logMail.SMTPPassword= zzs +log4j.appender.logMail.Subject= Log4j Log Messages +#log4j.appender.logMail.BufferSize= 1024 +#log4j.appender.logMail.SMTPAuth= TRUE + +#将日志登录到MySQL数据库 +log4j.appender.logDB= org.apache.log4j.jdbc.JDBCAppender +log4j.appender.logDB.layout= org.apache.log4j.PatternLayout +log4j.appender.logDB.Driver= com.mysql.cj.jdbc.Driver +log4j.appender.logDB.URL= jdbc:mysql://127.0.0.1:3306/crm_logs?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=true +log4j.appender.logDB.User= root +log4j.appender.logDB.Password= root +log4j.appender.logDB.Sql= INSERT INTO T_log4j(project_name,create_date,level,category,file_name,thread_name,line,all_category,message)values('Struts2','%d{yyyy-MM-ddHH:mm:ss}','%p','%c','%F','%t','%L','%l','%m') +################################################################################ +#①配置根Logger,其语法为: +# +#log4j.rootLogger =[level],appenderName,appenderName2,... +#level是日志记录的优先级,分为OFF,TRACE,DEBUG,INFO,WARN,ERROR,FATAL,ALL +##Log4j建议只使用四个级别,优先级从低到高分别是DEBUG,INFO,WARN,ERROR +#通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关 +#比如在这里定义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来 +#appenderName就是指定日志信息输出到哪个地方。可同时指定多个输出目的 +################################################################################ +################################################################################ +#②配置日志信息输出目的地Appender,其语法为: +# +#log4j.appender.appenderName =fully.qualified.name.of.appender.class +#log4j.appender.appenderName.optionN =valueN +# +#Log4j提供的appender有以下几种: +#1)org.apache.log4j.ConsoleAppender(输出到控制台) +#2)org.apache.log4j.FileAppender(输出到文件) +#3)org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件) +#4)org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件) +#5)org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方) +# +#1)ConsoleAppender选项属性 +# -Threshold = DEBUG:指定日志消息的输出最低层次 +# -ImmediateFlush = TRUE:默认值是true,所有的消息都会被立即输出 +# -Target = System.err:默认值System.out,输出到控制台(err为红色,out为黑色) +# +#2)FileAppender选项属性 +# -Threshold = INFO:指定日志消息的输出最低层次 +# -ImmediateFlush = TRUE:默认值是true,所有的消息都会被立即输出 +# -File = C:\log4j.log:指定消息输出到C:\log4j.log文件 +# -Append = FALSE:默认值true,将消息追加到指定文件中,false指将消息覆盖指定的文件内容 +# -Encoding = UTF-8:可以指定文件编码格式 +# +#3)DailyRollingFileAppender选项属性 +#-Threshold = WARN:指定日志消息的输出最低层次 +#-ImmediateFlush = TRUE:默认值是true,所有的消息都会被立即输出 +# -File =C:\log4j.log:指定消息输出到C:\log4j.log文件 +# -Append= FALSE:默认值true,将消息追加到指定文件中,false指将消息覆盖指定的文件内容 +#-DatePattern='.'yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。还可以按用以下参数: +# '.'yyyy-MM:每月 +# '.'yyyy-ww:每周 +# '.'yyyy-MM-dd:每天 +# '.'yyyy-MM-dd-a:每天两次 +# '.'yyyy-MM-dd-HH:每小时 +# '.'yyyy-MM-dd-HH-mm:每分钟 +#-Encoding = UTF-8:可以指定文件编码格式 +# +#4)RollingFileAppender选项属性 +#-Threshold = ERROR:指定日志消息的输出最低层次 +#-ImmediateFlush = TRUE:默认值是true,所有的消息都会被立即输出 +# -File =C:/log4j.log:指定消息输出到C:/log4j.log文件 +# -Append= FALSE:默认值true,将消息追加到指定文件中,false指将消息覆盖指定的文件内容 +#-MaxFileSize = 100KB:后缀可以是KB,MB,GB.在日志文件到达该大小时,将会自动滚动.如:log4j.log.1 +#-MaxBackupIndex = 2:指定可以产生的滚动文件的最大数 +#-Encoding = UTF-8:可以指定文件编码格式 +################################################################################ +################################################################################ +#③配置日志信息的格式(布局),其语法为: +# +#log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class +#log4j.appender.appenderName.layout.optionN= valueN +# +#Log4j提供的layout有以下几种: +#5)org.apache.log4j.HTMLLayout(以HTML表格形式布局) +#6)org.apache.log4j.PatternLayout(可以灵活地指定布局模式) +#7)org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串) +#8)org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息) +#9)org.apache.log4j.xml.XMLLayout(以XML形式布局) +# +#5)HTMLLayout选项属性 +#-LocationInfo = TRUE:默认值false,输出java文件名称和行号 +#-Title=Struts Log Message:默认值 Log4JLog Messages +# +#6)PatternLayout选项属性 +#-ConversionPattern = %m%n:格式化指定的消息(参数意思下面有) +# +#9)XMLLayout选项属性 +#-LocationInfo = TRUE:默认值false,输出java文件名称和行号 +# +#Log4J采用类似C语言中的printf函数的打印格式格式化日志信息,打印参数如下: +#%m 输出代码中指定的消息 +#%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL +#%r 输出自应用启动到输出该log信息耗费的毫秒数 +#%c 输出所属的类目,通常就是所在类的全名 +#%t 输出产生该日志事件的线程名 +#%n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n” +#%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式 +# 如:%d{yyyy年MM月dd日HH:mm:ss,SSS},输出类似:2012年01月05日 22:10:28,921 +#%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数 +# 如:Testlog.main(TestLog.java:10) +#%F 输出日志消息产生时所在的文件名称 +#%L 输出代码中的行号 +#%x 输出和当前线程相关联的NDC(嵌套诊断环境),像javaservlets多客户多线程的应用中 +#%% 输出一个"%"字符 +# +# 可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如: +# %5c: 输出category名称,最小宽度是5,category<5,默认的情况下右对齐 +# %-5c:输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格 +# %.5c:输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格 +# %20.30c:category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉 +################################################################################ +################################################################################ +#④指定特定包的输出特定的级别 +#log4j.logger.org.springframework=DEBUG +################################################################################ \ No newline at end of file diff --git a/src/test/java/cn/zzs/dbcp/DBCPTest.java b/src/test/java/cn/zzs/dbcp/DBCPTest.java new file mode 100644 index 0000000..98053b3 --- /dev/null +++ b/src/test/java/cn/zzs/dbcp/DBCPTest.java @@ -0,0 +1,146 @@ +package cn.zzs.dbcp; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.junit.Test; + +/** + * @ClassName: DBCPTest + * @Description: 测试DBCP + * @author: zzs + * @date: 2019年8月31日 下午9:39:54 + */ +public class DBCPTest { + /** + * 测试添加用户 + * @throws SQLException + */ + @Test + public void saveUser() throws Exception { + //创建sql + String sql = "insert into user values(null,?,?,?,?)"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + try { + //设置非自动提交 + connection.setAutoCommit(false); + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setString(1, "zzs001"); + statement.setInt(2, 18); + statement.setDate(3, new Date(System.currentTimeMillis())); + statement.setDate(4, new Date(System.currentTimeMillis())); + //执行 + statement.executeUpdate(); + //提交事务 + connection.commit(); + } catch (Exception e) { + System.out.println("异常导致操作回滚"); + connection.rollback(); + e.printStackTrace(); + } finally { + //释放资源 + JDBCUtil.release(connection, statement,null); + } + } + + /** + * 测试更新用户 + */ + @Test + public void updateUser() throws Exception { + //创建sql + String sql = "update user set age = ?,gmt_modified = ? where name = ?"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + try { + //设置非自动提交 + connection.setAutoCommit(false); + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setInt(1, 19); + statement.setDate(2, new Date(System.currentTimeMillis())); + statement.setString(3, "zzs001"); + //执行 + statement.executeUpdate(); + //提交事务 + connection.commit(); + } catch (Exception e) { + System.out.println("异常导致操作回滚"); + connection.rollback(); + e.printStackTrace(); + } finally { + //释放资源 + JDBCUtil.release(connection, statement,null); + } + } + + /** + * 测试查找用户 + */ + @Test + public void findUser() throws Exception { + //创建sql + String sql = "select * from user where name = ?"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + ResultSet resultSet = null; + try { + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setString(1, "zzs001"); + //执行 + resultSet = statement.executeQuery(); + //遍历结果集 + while (resultSet.next()) { + String name = resultSet.getString(2); + int age = resultSet.getInt(3); + System.out.println("用户名:" + name + ",年龄:" + age); + } + } finally { + //释放资源 + JDBCUtil.release(connection, statement,resultSet); + } + } + + /** + * 测试删除用户 + */ + @Test + public void deleteUser() throws Exception { + //创建sql + String sql = "delete from user where name = ?"; + //获得连接 + Connection connection = JDBCUtil.getConnection(); + PreparedStatement statement = null; + try { + //设置非自动提交 + connection.setAutoCommit(false); + //获得Statement对象 + statement = connection.prepareStatement(sql); + //设置参数 + statement.setString(1, "zzs001"); + //执行 + statement.executeUpdate(); + //提交事务 + connection.commit(); + } catch (Exception e) { + System.out.println("异常导致操作回滚"); + connection.rollback(); + e.printStackTrace(); + } finally { + //释放资源 + JDBCUtil.release(connection, statement,null); + } + } +}