https://www.hoshino.asia/archives/hutool

概述

由来

Hutool-db是一个在JDBC基础上封装的数据库操作工具类,通过包装,使用ActiveRecord思想操作数据库。在Hutool-db中,使用Entity(本质上是个Map)代替Bean来使数据库操作更加灵活,同时提供Bean和Entity的转换来提供传统ORM的兼容支持。

整体的架构

整体分为几部分:

  1. 数据源 DataSource

  2. SQL执行器 SqlExecutor

  3. CRUD的封装 DbSqlConnRunner SqlRunner

  4. 支持事务的CRUD封装 Session

  5. 各种结果集处理类 handler

  6. 数据库的一些工具方法汇总 DbUtil

还有就是没有列出来的dialect(数据库方言),我会根据给定的DataSource、Connection等对象自动识别是什么数据库,然后使用不同的方言构造SQL语句,暂时支持的数据库有MySQL、Oracle、SqlLite3,当然如果识别失败会用ANSI SQL,这样遇到不支持的数据库,也可以搞定大部分方法。

下面解释下:

CRUD的封装 Db SqlConnRunner SqlRunner

这两个类有些相似,里面都封装了增、删、改、查、分页、个数方法,差别是SqlConnRunner需要每个方法都传Connection对象,而SqlRunner继承自SqlConnRunner,在传入DataSource后会自动获取Connection对象。

各种结果集处理类 handler

此包中有个叫做RsHandler的接口,传入ResultSet对象,返回什么则在handle方法中自己指定。 实现的类有:

  1. EntityListHandler 转换为Entity列表

  2. NumberHandler 当使用select count(1)这类语句的时候,或者返回只有一个结果,且为数字结果的时候,用这个handler

  3. EntityHandler 返回一条记录的时候用这个

数据库的一些工具方法汇总

DbUtil 提供一些工具方法,最常用的就是close方法了,由于JDK7才把ResultSetStatementPreparedStatementConnection这几个接口实现了Closeable接口,所以之前只能判断类型再去关闭,这样一个close方法可以关闭多个对象。

对象解释

1. Entity

在ORM中,我把一张表中的一条数据映射成为一个叫做Entity的类,继承自HashMap,key是字段名,value是Object类型,字段值,这样一个Entity对象就是数据库表中的一条记录,当然这个对象中还有个字段是表的名字,方便之后的操作。之后对数据库增删改查操作的对象大多是这个。

这个对象充当着两种角色,一个是数据的载体,表示一条数据,另一个就是where语句的条件,充当where条件时,key依旧是字段名,value是字段条件值。例如:

Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");

表示的where语句是:

WHERE `条件1` = 条件值

当然到时候会用PreparedStatement,不会出现SQL注入。

2. TableColumn

这两个对象主要是描述数据库表结构的,暂时和ORM本身没啥关系,只是当你想获得一些字段信息的时候,这样来获得表结构信息:

// 获得当前库的所有表的表名
List<String> tableNames = MetaUtil.getTables(dataSource);
Log.info("{}", tableNames);

/*
 * 获得表结构 表结构封装为一个表对象,里面有Column对象表示一列,列中有列名、类型、大小、是否允许为空等信息
 */
Table table = MetaUtil.getTableMeta(dataSource, TABLE_NAME);
Log.info("{}", table);

SQL执行器-SqlExecutor

介绍

这是一个静态类,对JDBC的薄封装,里面的静态方法只有两种:执行非查询的SQL语句和查询的SQL语句

使用

Connection conn = null;
try {
	conn = ds.getConnection();
	// 执行非查询语句,返回影响的行数
	int count = SqlExecutor.execute(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
	log.info("影响行数:{}", count);
	// 执行非查询语句,返回自增的键,如果有多个自增键,只返回第一个
	Long generatedKey = SqlExecutor.executeForGeneratedKey(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
	log.info("主键:{}", generatedKey);

	/* 执行查询语句,返回实体列表,一个Entity对象表示一行的数据,Entity对象是一个继承自HashMap的对象,存储的key为字段名,value为字段值 */
	List<Entity> entityList = SqlExecutor.query(conn, "select * from " + TABLE_NAME + " where param1 = ?", new EntityListHandler(), "值");
	log.info("{}", entityList);
} catch (SQLException e) {
	Log.error(log, e, "SQL error!");
} finally {
	DbUtil.close(conn);
}

支持事务的CRUD-Session

介绍

Session非常类似于SqlRunner,差别是Session对象中只有一个Connection,所有操作也是用这个Connection,便于事务操作,而SqlRunner每执行一个方法都要从DataSource中去要Connection。样例如下:

Session创建

SqlRunner类似,Session也可以通过调用create

//默认数据源
Session session = Session.create();

//自定义数据源(此处取test分组的数据源)
Session session = Session.create(DSFactory.get("test"));

事务CRUD

session.beginTransaction()表示事务开始,调用后每次执行语句将不被提交,只有调用commit方法后才会合并提交,提交或者回滚后会恢复默认的自动提交模式。

  1. 新增

Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
try {
	session.beginTransaction();
	// 增,生成SQL为 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
	session.insert(entity);
	session.commit();
} catch (SQLException e) {
	session.quietRollback();
}
  1. 更新

Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");
try {
	session.beginTransaction();
	// 改,生成SQL为 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `条件1` = ?
	session.update(entity, where);
	session.commit();
} catch (SQLException e) {
	session.quietRollback();
}
  1. 删除

Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");
try {
	session.beginTransaction();
	// 删,生成SQL为 DELETE FROM `table_name` WHERE `条件1` = ?
	session.del(where);
	session.commit();
} catch (SQLException e) {
	session.quietRollback();
}

数据库简单操作-Db

由来

数据库操作不外乎四门功课:增删改查,在Java的世界中,由于JDBC的存在,这项工作变得简单易用,但是也并没有做到使用上的简化。于是出现了JPA(Hibernate)、MyBatis、Jfinal、BeetlSQL等解决框架,或解决多数据库差异问题,或解决SQL维护问题。而Hutool对JDBC的封装,多数为在小型项目中对数据处理的简化,尤其只涉及单表操作时。OK,废话不多说,来个Demo感受下。

使用

我们以MySQL为例

1、添加配置文件

Maven项目中在src/main/resources目录下添加db.setting文件(非Maven项目添加到ClassPath中即可):

## db.setting文件

url = jdbc:mysql://localhost:3306/test
user = root
pass = 123456

## 可选配置
# 是否在日志中显示执行的SQL
showSql = true
# 是否格式化显示的SQL
formatSql = false
# 是否显示SQL参数
showParams = true
# 打印SQL的日志等级,默认debug,可以是info、warn、error
sqlLevel = debug

2、引入MySQL JDBC驱动jar

<!--mysql数据库驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
</dependency>

注意 此处不定义MySQL版本,请参考官方文档使用匹配的驱动包版本。

3、增删改查

Db.use().insert(
    Entity.create("user")
    .set("name", "unitTestUser")
    .set("age", 66)
);

插入数据并返回自增主键:

Db.use().insertForGeneratedKey(
    Entity.create("user")
    .set("name", "unitTestUser")
    .set("age", 66)
);

Db.use().del(
    Entity.create("user").set("name", "unitTestUser")//where条件
);

注意 考虑安全性,使用del方法时不允许使用空的where条件,防止全表删除,如有相关操作需要,请调用execute方法执行SQL实现。

Db.use().update(
    Entity.create().set("age", 88), //修改的数据
    Entity.create("user").set("name", "unitTestUser") //where条件
);

注意 条件语句除了可以用=精确匹配外,也可以范围条件匹配,例如表示 age < 12 可以这样构造Entity:Entity.create("user").set("age", "< 12"),但是通过Entity方式传入条件暂时不支持同字段多条件的情况。

  1. 查询全部字段

//user为表名
Db.use().findAll("user");
  1. 条件查询

Db.use().findAll(Entity.create("user").set("name", "unitTestUser"));
  1. 模糊查询

Db.use().findLike("user", "name", "Test", LikeType.Contains);

或者:

List<Entity> find = Db.use().find(Entity.create("user").set("name", "like 王%"));
  1. 分页查询

//Page对象通过传入页码和每页条目数达到分页目的
PageResult<Entity> result = Db.use().page(Entity.create("user").set("age", "> 30"), new Page(10, 20));
  1. 执行SQL语句

//查询
List<Entity> result = Db.use().query("select * from user where age < ?", 3);
//模糊查询
List<Entity> result = Db.use().query("select * from user where name like ?", "王%");
//新增
Db.use().execute("insert into user values (?, ?, ?)", "张三", 17, 1);
//删除
Db.use().execute("delete from user where name = ?", "张三");
//更新
Db.use().execute("update user set age = ? where name = ?", 3, "张三");
  1. 事务

Db.use().tx(new TxFunc() {
    @Override
    public void call(Db db) throws SQLException {
        db.insert(Entity.create("user").set("name", "unitTestUser"));
        db.update(Entity.create().set("age", 79), Entity.create("user").set("name", "unitTestUser"));
    }
});

JDK8中可以用lambda表达式(since:5.x):

Db.use().tx(db -> {
	db.insert(Entity.create("user").set("name", "unitTestUser2"));
	db.update(Entity.create().set("age", 79), Entity.create("user").set("name", "unitTestUser2"));
});
  1. 支持命名占位符的SQL执行

有时候使用?占位符比较繁琐,且在复杂SQL中很容易出错,Hutool支持使用命名占位符来执行SQL。

Map<String, Object> paramMap = MapUtil.builder("name1", (Object)"张三").put("age", 12).put("subName", "小豆豆").build();
Db.use().query("select * from table where id=@id and name = @name1 and nickName = @subName", paramMap);

在Hutool中,占位符支持以下几种形式:

  • :name

  • ?name

  • @name

  1. IN查询

我们在执行类似于select * from user where id in 1,2,3这类SQL的时候,Hutool封装如下:

List<Entity> results = db.findAll(
    Entity.create("user")
        .set("id", "in 1,2,3"));

当然你也可以直接:

List<Entity> results = db.findAll(
    Entity.create("user")
        .set("id", new long[]{1, 2, 3}));

数据源工厂-DsFactory

释义

数据源(DataSource)的概念来自于JDBC规范中,一个数据源表示针对一个数据库(或者集群)的描述,从数据源中我们可以获得N个数据库连接,从而对数据库进行操作。

每一个开源JDBC连接池都有对DataSource的实现,比如Druid为DruidDataSource,Hikari为HikariDataSource。但是各大连接池配置各不相同,配置文件也不一样,Hutool的针对常用的连接池做了封装,最大限度简化和提供一致性配置。

Hutool的解决方案是:在ClassPath中使用config/db.setting一个配置文件,配置所有种类连接池的数据源,然后使用DsFactory.get()方法自动识别数据源以及自动注入配置文件中的连接池配置(包括数据库连接配置)。DsFactory通过try的方式按照顺序检测项目中引入的jar包来甄别用户使用的是哪种连接池,从而自动构建相应的数据源。

Hutool支持以下连接池,并按照其顺序检测存在与否:

  1. HikariCP

  2. Druid

  3. Tomcat

  4. Dbcp

  5. C3p0

在没有引入任何连接池的情况下,Hutool会使用其内置的连接池:Hutool Pooled(简易连接池,不推荐在线上环境使用)。

基本使用

1. 引入连接池的jar

Hutool不会强依赖于任何第三方库,在Hutool支持的连接池范围内,用户需自行选择自己喜欢的连接池并引入。

2. 编写配置文件

Maven项目中,在src/main/resources/config下创建文件db.setting,编写配置文件即可。这个配置文件位置就是Hutool与用户间的一个约定(符合约定大于配置的原则):

配置文件分为两部分

1. 基本连接信息

## 基本配置信息
# JDBC URL,根据不同的数据库,使用相应的JDBC连接字符串
url = jdbc:mysql://<host>:<port>/<database_name>
# 用户名,此处也可以使用 user 代替
username = 用户名
# 密码,此处也可以使用 pass 代替
password = 密码
# JDBC驱动名,可选(Hutool会自动识别)
driver = com.mysql.jdbc.Driver

小提示 其中driver是可选的,Hutool会根据url自动加载相应的Driver类。基本连接信息是所有连接池通用的,原则上,只有基本信息就可以成功连接并操作数据库。

2. 连接池特有配置信息

针对不同的连接池,除了基本信息外的配置都各不相同,Hutool针对不同的连接池封装了其配置项,可以在项目的src/test/resources/example中看到针对不同连接池的配置文件样例。

我们以HikariCP为例:

# 自动提交
autoCommit = true
# 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒
connectionTimeout = 30000
# 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟
idleTimeout = 600000
# 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL wait_timeout参数(show variables like '%timeout%';)
maxLifetime = 1800000
# 获取连接前的测试SQL
connectionTestQuery = SELECT 1
# 最小闲置连接数
minimumIdle = 10
# 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count)
maximumPoolSize = 10
# 连接只读数据库时配置为true, 保证安全
readOnly = false

3. 获取数据源

//获取默认数据源
DataSource ds = DSFactory.get()

是滴,就是这么简单,一个简单的方法,可以识别数据源并读取默认路径(config/db.setting)下信息从而获取数据源。

4. 直接创建数据源

当然你依旧可以按照连接池本身的方式获取数据源对象。我们以Druid为例:

//具体的配置参数请参阅Druid官方文档
DruidDataSource ds2 = new DruidDataSource();
ds2.setUrl("jdbc:mysql://localhost:3306/dbName");
ds2.setUsername("root");
ds2.setPassword("123456");

5. 创建简单数据源

有时候我们的操作非常简单,亦或者只是测试下远程数据库是否畅通,我们可以使用Hutool提供的SimpleDataSource:

DataSource ds = new SimpleDataSource("jdbc:mysql://localhost:3306/dbName", "root", "123456");

SimpleDataSource只是DriverManager.getConnection的简单包装,本身并不支持池化功能,此类特别适合少量数据库连接的操作。

同样的,SimpleDataSource也支持默认配置文件:

DataSource ds = new SimpleDataSource();

高级实用

1. 自定义连接池

有时候当项目引入多种数据源时,我们希望自定义需要的连接池,此时可以:

//自定义连接池实现为Tomcat-pool
DSFactory.setCurrentDSFactory(new TomcatDSFactory());
DataSource ds = DSFactory.get();

需要注意的是,DSFactory.setCurrentDSFactory是一个全局方法,必须在所有获取数据源的时机之前调用,调用一次即可(例如项目启动)。

2. 自定义配置文件

有时候由于项目规划的问题,我们希望自定义数据库配置Setting的位置,甚至是动态加载Setting对象,此时我们可以使用以下方法从其它的Setting对象中获取数据库连接信息:

//自定义数据库Setting,更多实用请参阅Hutool-Setting章节
Setting setting = new Setting("otherPath/other.setting");
//获取指定配置,第二个参数为分组,用于多数据源,无分组情况下传null
// 注意此处DSFactory需要复用或者关闭
DataSource ds = DSFactory.create(setting).getDataSource();

3. 多数据源

有的时候我们需要操作不同的数据库,也有可能我们需要针对线上、开发和测试分别操作其数据库,无论哪种情况,Hutool都针对多数据源做了很棒的支持。

多数据源有两种方式可以实现:

1. 多个配置文件分别获得数据源

就是按照自定义配置文件的方式读取多个配置文件即可。

2. 在同一配置文件中使用分组隔离不同的数据源配置:

[group_db1]
url = jdbc:mysql://<host>:<port>/<database_name>
username = 用户名
password = 密码

[group_db2]
url = jdbc:mysql://<host2>:<port>/<database_name>
username = 用户名
password = 密码

我们按照上面的方式编写db.setting文件,然后:

DataSource ds1 = DSFactory.get("group_db1");
DataSource ds2 = DSFactory.get("group_db2");

这样我们就可以在一个配置文件中实现多数据源的配置。

结语

Hutool通过多种方式获取DataSource对象,获取后除了可以在Hutool自身应用外,还可以将此对象传入不同的框架以实现无缝结合。

Hutool对数据源的封装很好的诠释了以下几个原则:

  1. 自动识别优于用户定义

  2. 便捷性与灵活性并存

  3. 适配与兼容

数据源配置db.setting样例

介绍

DsFactory默认读取的配置文件是config/db.setting或db.setting,db.setting的配置包括两部分:基本连接信息和连接池配置信息。

基本连接信息所有连接池都支持,连接池配置信息根据不同的连接池,连接池配置是根据连接池相应的配置项移植而来。

基本配置样例

#------------------------------------------------------------------------------------------
## 基本配置信息
# JDBC URL,根据不同的数据库,使用相应的JDBC连接字符串
url = jdbc:mysql://<host>:<port>/<database_name>
# 用户名,此处也可以使用 user 代替
username = 用户名
# 密码,此处也可以使用 pass 代替
password = 密码
# JDBC驱动名,可选(Hutool会自动识别)
driver = com.mysql.jdbc.Driver

## 可选配置
# 是否在日志中显示执行的SQL
showSql = true
# 是否格式化显示的SQL
formatSql = false
# 是否显示SQL参数
showParams = true
# 打印SQL的日志等级,默认debug
sqlLevel = debug
#------------------------------------------------------------------------------------------

HikariCP

## 连接池配置项
# 自动提交
autoCommit = true
# 等待连接池分配连接的最大时长(毫秒),超过这个时长还没可用的连接则发生SQLException, 缺省:30秒
connectionTimeout = 30000
# 一个连接idle状态的最大时长(毫秒),超时则被释放(retired),缺省:10分钟
idleTimeout = 600000
# 一个连接的生命时长(毫秒),超时而且没被使用则被释放(retired),缺省:30分钟,建议设置比数据库超时时长少30秒,参考MySQL wait_timeout参数(show variables like '%timeout%';)
maxLifetime = 1800000
# 获取连接前的测试SQL
connectionTestQuery = SELECT 1
# 最小闲置连接数
minimumIdle = 10
# 连接池中允许的最大连接数。缺省值:10;推荐的公式:((core_count * 2) + effective_spindle_count)
maximumPoolSize = 10
# 连接只读数据库时配置为true, 保证安全
readOnly = false

Druid

# 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
initialSize = 0
# 最大连接池数量
maxActive = 8
# 最小连接池数量
minIdle = 0
# 获取连接时最大等待时间,单位毫秒。配置了maxWait之后, 缺省启用公平锁,并发效率会有所下降, 如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
maxWait = 0
# 是否缓存preparedStatement,也就是PSCache。 PSCache对支持游标的数据库性能提升巨大,比如说oracle。 在mysql5.5以下的版本中没有PSCache功能,建议关闭掉。作者在5.5版本中使用PSCache,通过监控界面发现PSCache有缓存命中率记录, 该应该是支持PSCache。
poolPreparedStatements = false
# 要启用PSCache,必须配置大于0,当大于0时, poolPreparedStatements自动触发修改为true。 在Druid中,不会存在Oracle下PSCache占用内存过多的问题, 可以把这个数值配置大一些,比如说100
maxOpenPreparedStatements = -1
# 用来检测连接是否有效的sql,要求是一个查询语句。 如果validationQuery为null,testOnBorrow、testOnReturn、 testWhileIdle都不会其作用。
validationQuery = SELECT 1
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnBorrow = true
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturn = false
# 建议配置为true,不影响性能,并且保证安全性。 申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testWhileIdle = false
# 有两个含义: 1) Destroy线程会检测连接的间隔时间 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
timeBetweenEvictionRunsMillis = 60000
# 物理连接初始化的时候执行的sql
connectionInitSqls = SELECT 1
# 属性类型是字符串,通过别名的方式配置扩展插件, 常用的插件有: 监控统计用的filter:stat  日志用的filter:log4j 防御sql注入的filter:wall
filters = stat
# 类型是List<com.alibaba.druid.filter.Filter>, 如果同时配置了filters和proxyFilters, 是组合关系,并非替换关系
proxyFilters = 

Tomcat JDBC Pool

# (boolean) 连接池创建的连接的默认的auto-commit 状态
defaultAutoCommit = true
# (boolean) 连接池创建的连接的默认的read-only 状态。 如果没有设置则setReadOnly 方法将不会被调用。 ( 某些驱动不支持只读模式, 比如:Informix)
defaultReadOnly = false
# (String) 连接池创建的连接的默认的TransactionIsolation 状态。 下面列表当中的某一个: ( 参考javadoc) NONE READ_COMMITTED EAD_UNCOMMITTED REPEATABLE_READ SERIALIZABLE
defaultTransactionIsolation = NONE
# (int) 初始化连接: 连接池启动时创建的初始化连接数量,1。2 版本后支持
initialSize = 10
# (int) 最大活动连接: 连接池在同一时间能够分配的最大活动连接的数量, 如果设置为非正数则表示不限制
maxActive = 100
# (int) 最大空闲连接: 连接池中容许保持空闲状态的最大连接数量, 超过的空闲连接将被释放, 如果设置为负数表示不限制 如果启用,将定期检查限制连接,如果空闲时间超过minEvictableIdleTimeMillis 则释放连接 ( 参考testWhileIdle )
maxIdle = 8
# (int) 最小空闲连接: 连接池中容许保持空闲状态的最小连接数量, 低于这个数量将创建新的连接, 如果设置为0 则不创建 如果连接验证失败将缩小这个值( 参考testWhileIdle )
minIdle = 0
# (int) 最大等待时间: 当没有可用连接时, 连接池等待连接被归还的最大时间( 以毫秒计数), 超过时间则抛出异常, 如果设置为-1 表示无限等待
maxWait = 30000
# (String) SQL 查询, 用来验证从连接池取出的连接, 在将连接返回给调用者之前。 如果指定, 则查询必须是一个SQL SELECT 并且必须返回至少一行记录 查询不必返回记录,但这样将不能抛出SQL异常
validationQuery = SELECT 1
# (boolean) 指明是否在从池中取出连接前进行检验, 如果检验失败, 则从池中去除连接并尝试取出另一个。注意: 设置为true 后如果要生效,validationQuery 参数必须设置为非空字符串 参考validationInterval以获得更有效的验证
testOnBorrow = false
# (boolean) 指明是否在归还到池中前进行检验 注意: 设置为true 后如果要生效,validationQuery 参数必须设置为非空字符串
testOnReturn = false
# (boolean) 指明连接是否被空闲连接回收器( 如果有) 进行检验。 如果检测失败, 则连接将被从池中去除。注意: 设置为true 后如果要生效,validationQuery 参数必须设置为非空字符串
testWhileIdle = false

C3P0(不推荐)

# 连接池中保留的最大连接数。默认值: 15
maxPoolSize = 15
# 连接池中保留的最小连接数,默认为:3
minPoolSize = 3
# 初始化连接池中的连接数,取值应在minPoolSize与maxPoolSize之间,默认为3
initialPoolSize = 3
# 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0
maxIdleTime = 0
# 当连接池连接耗尽时,客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。默认: 0
checkoutTimeout = 0
# 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3
acquireIncrement = 3
# 定义在从数据库获取新连接失败后重复尝试的次数。默认值: 30 ;小于等于0表示无限次
acquireRetryAttempts = 0
# 重新尝试的时间间隔,默认为:1000毫秒
acquireRetryDelay = 1000
# 关闭连接时,是否提交未提交的事务,默认为false,即关闭连接,回滚未提交的事务
autoCommitOnClose = false
# c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。默认值: null
automaticTestTable = null
# 如果为false,则获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常,但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。默认: false
breakAfterAcquireFailure = false
# 检查所有连接池中的空闲连接的检查频率。默认值: 0,不检查
idleConnectionTestPeriod = 0
# c3p0全局的PreparedStatements缓存的大小。如果maxStatements与maxStatementsPerConnection均为0,则缓存不生效,只要有一个不为0,则语句的缓存就能生效。如果默认值: 0
maxStatements = 0
# maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。默认值: 0
maxStatementsPerConnection = 0

DBCP(不推荐)

# (boolean) 连接池创建的连接的默认的auto-commit 状态
defaultAutoCommit = true
# (boolean) 连接池创建的连接的默认的read-only 状态。 如果没有设置则setReadOnly 方法将不会被调用。 ( 某些驱动不支持只读模式, 比如:Informix)
defaultReadOnly = false
# (String) 连接池创建的连接的默认的TransactionIsolation 状态。 下面列表当中的某一个: ( 参考javadoc) NONE READ_COMMITTED EAD_UNCOMMITTED REPEATABLE_READ SERIALIZABLE
defaultTransactionIsolation = NONE
# (int) 初始化连接: 连接池启动时创建的初始化连接数量,1。2 版本后支持
initialSize = 10
# (int) 最大活动连接: 连接池在同一时间能够分配的最大活动连接的数量, 如果设置为非正数则表示不限制
maxActive = 100
# (int) 最大空闲连接: 连接池中容许保持空闲状态的最大连接数量, 超过的空闲连接将被释放, 如果设置为负数表示不限制 如果启用,将定期检查限制连接,如果空闲时间超过minEvictableIdleTimeMillis 则释放连接 ( 参考testWhileIdle )
maxIdle = 8
# (int) 最小空闲连接: 连接池中容许保持空闲状态的最小连接数量, 低于这个数量将创建新的连接, 如果设置为0 则不创建 如果连接验证失败将缩小这个值( 参考testWhileIdle )
minIdle = 0
# (int) 最大等待时间: 当没有可用连接时, 连接池等待连接被归还的最大时间( 以毫秒计数), 超过时间则抛出异常, 如果设置为-1 表示无限等待
maxWait = 30000
# (String) SQL 查询, 用来验证从连接池取出的连接, 在将连接返回给调用者之前。 如果指定, 则查询必须是一个SQL SELECT 并且必须返回至少一行记录 查询不必返回记录,但这样将不能抛出SQL异常
validationQuery = SELECT 1
# (boolean) 指明是否在从池中取出连接前进行检验, 如果检验失败, 则从池中去除连接并尝试取出另一个。注意: 设置为true 后如果要生效,validationQuery 参数必须设置为非空字符串 参考validationInterval以获得更有效的验证
testOnBorrow = false
# (boolean) 指明是否在归还到池中前进行检验 注意: 设置为true 后如果要生效,validationQuery 参数必须设置为非空字符串
testOnReturn = false
# (boolean) 指明连接是否被空闲连接回收器( 如果有) 进行检验。 如果检测失败, 则连接将被从池中去除。注意: 设置为true 后如果要生效,validationQuery 参数必须设置为非空字符串
testWhileIdle = false

案例1-导出Blob字段图像

需求:

有一张单表存储着图片(图片使用Blob字段)以及图片的相关信息,需求是从数据库中将这些Blob字段内容保存为图片文件,文件名为图片的相关信息。

环境

数据库:Oracle
本地:Windows
工具:Hutool-db模块

编码

数据库配置:src/main/resources/config/db.setting

#JDBC url,必须
url = jdbc:oracle:thin:@localhost:1521/orcl
#用户名,必须
user = test
#密码,必须,如果密码为空,请填写 pass = 
pass = test

代码:PicTransfer.java

public class PicTransfer {
	public static void main(String[] args) throws SQLException {
		Db.use().find(
				ListUtil.of("NAME", "TYPE", "GROUP", "PIC"), 
				Entity.create("PIC_INFO").set("TYPE", 1),
				rs -> {
					while(rs.next()){
						save(rs);
					}
					return null;
				}
		);
	}
	
	private static void save(ResultSet rs) throws SQLException{
		String destDir = "f:/pic";
		String path = StrUtil.format("{}/{}-{}.jpg", destDir, rs.getString("NAME"), rs.getString("GROUP"));
		FileUtil.writeFromStream(rs.getBlob("PIC").getBinaryStream(), path);
	}
}

常见问题

问题描述:no suitable driver found for jdbc

解答:出现此类问题一般是JDBC驱动版本不一致导致的,出现此问题的用户使用的是ojdbc14,服务端使用Oracle11g,JDK8,此处升级到ojdbc6即可。

版本对应见:https://www.oracle.com/technetwork/database/application-development/jdbc/downloads/index.html

NoSQL

Redis客户端封装-RedisDS

介绍

RedisDS基于Jedis封装,需自行引入Jedis依赖。

使用

引入依赖

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>3.7.0</version>
</dependency>

配置

在ClassPath(或者src/main/resources)的config目录下新建redis.setting

#-------------------------------------------------------------------------------
# Redis客户端配置样例
# 每一个分组代表一个Redis实例
# 无分组的Pool配置为所有分组的共用配置,如果分组自己定义Pool配置,则覆盖共用配置
# 池配置来自于:https://www.cnblogs.com/jklk/p/7095067.html
#-------------------------------------------------------------------------------

#----- 默认(公有)配置
# 地址,默认localhost
host = localhost
# 端口,默认6379
port = 6379
# 超时,默认2000
timeout = 2000
# 连接超时,默认timeout
connectionTimeout = 2000
# 读取超时,默认timeout
soTimeout = 2000
# 密码,默认无
password = 
# 数据库序号,默认0
database = 0
# 客户端名,默认"Hutool"
clientName = Hutool
# SSL连接,默认false
ssl = false;

#----- 自定义分组的连接
[custom]
# 地址,默认localhost
host = localhost
# 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
BlockWhenExhausted = true;
# 设置的逐出策略类名, 默认DefaultEvictionPolicy(当连接超过最大空闲时间,或连接数超过最大空闲连接数)
evictionPolicyClassName = org.apache.commons.pool2.impl.DefaultEvictionPolicy
# 是否启用pool的jmx管理功能, 默认true
jmxEnabled = true;
# 是否启用后进先出, 默认true
lifo = true;
# 最大空闲连接数, 默认8个
maxIdle = 8
# 最小空闲连接数, 默认0
minIdle = 0
# 最大连接数, 默认8个
maxTotal = 8
# 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
maxWaitMillis = -1
# 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
minEvictableIdleTimeMillis = 1800000
# 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
numTestsPerEvictionRun = 3;
# 对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断  (默认逐出策略)
SoftMinEvictableIdleTimeMillis = 1800000
# 在获取连接的时候检查有效性, 默认false
testOnBorrow = false
# 在空闲时检查有效性, 默认false
testWhileIdle = false
# 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
timeBetweenEvictionRunsMillis = -1

构建

Jedis jedis = RedisDS.create().getJedis();

MongoDB客户端封装-MongoDS

介绍

针对MongoDB客户端封装。客户端需自行引入依赖。

使用

引入依赖

<dependency>
	<groupId>org.mongodb</groupId>
	<artifactId>mongo-java-driver</artifactId>
	<version>3.12.10</version>
</dependency>

配置

在ClassPath(或者src/main/resources)的config目录下新建mongo.setting

#每个主机答应的连接数(每个主机的连接池大小),当连接池被用光时,会被阻塞住 ,默以为10 --int
connectionsPerHost=100
#线程队列数,它以connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出“Out of semaphores to get db”错误 --int
threadsAllowedToBlockForConnectionMultiplier=10
#被阻塞线程从连接池获取连接的最长等待时间(ms) --int
maxWaitTime = 120000
#在建立(打开)套接字连接时的超时时间(ms),默以为0(无穷) --int
connectTimeout=0
#套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int
socketTimeout=0
#是否打开长连接. defaults to false --boolean
socketKeepAlive=false

#---------------------------------- MongoDB实例连接
[master]
host = 127.0.0.1:27017

[slave]
host = 127.0.0.1:27018
#-----------------------------------------------------

使用

//master slave 组成主从集群
MongoDatabase db = MongoFactory.getDS("master", "slave").getDb("test");

Ciallo~(∠・ω< )⌒☆