概述
由来
Hutool-db是一个在JDBC基础上封装的数据库操作工具类,通过包装,使用ActiveRecord思想操作数据库。在Hutool-db中,使用Entity(本质上是个Map)代替Bean来使数据库操作更加灵活,同时提供Bean和Entity的转换提供传统ORM的兼容支持。
整体的架构
整体分为几部分:
- 数据源
DataSource
- SQL执行器
SqlExecutor
- CRUD的封装
Db
、SqlConnRunner
SqlRunner
- 支持事务的CRUD封装
Session
- 各种结果集处理类
handler
- 数据库的一些工具方法汇总
DbUtil
还有就是没有列出来的dialect(数据库方言),我会根据给定的DataSource、Connection等对象自动识别是什么数据库,然后使用不同的方言构造SQL语句,暂时支持的数据库有MySQL、Oracle、SqlLite3,当然如果识别失败会用ANSI SQL,这样遇到不支持的数据,可以搞定大部分方法。
下面解释下:
CRUD的封装 Db
SqlConnRunner
SqlRunner
这两个类有些相似,里面都封装了增、删、改、查、分页、个数方法,差别是SqlConnRunner
需要每个方法都传Connection对象,而SqlRunner
继承自SqlConnRunner
,在传入DataSource会自动获取Connection对象。
各种结果集处理类 handler
此包中有个叫做RsHandler
的接口,传入ResultSet对象,返回什么则在handle方法中自己指定。 实现的类有:
- EntityListHandler 转换为Entity列表
- NumberHandler 当使用
select count(1)
这类语句的时候,或者返回只有一个结果,且为数字结果的时候,用这个handler - EntityHandler 返回一条记录的时候用这个
- 数据库的一些工具方法汇总
DbUtil
提供一些工具方法,最常用的就是close
方法了,由于JDK7才把ResultSet``Statement``PreparedStatement``Connection
这几个接口实现了Closeable接口,所以之前只能判断类型再去关闭,这样一个close方法可以关闭多个对象。
对象解释
1. Entity
在ORM中,我把一张表中的一条数据映射成为一个叫做Entity的类,继承自HashMap,key是字段名,value是Object类型,字段值,这样一个Entity对象就是数据库表中的一条记录,当然这个对象中还有个字段是表的名字,方便之后的操作。之后对数据库增删改查操作的对象大多是这个。
这个对象充当着两种角色,一个是数据的载体,表示一条数据,另一个就是where语句的条件,充当where条件时,key依旧是字段名,value是字段条件值。例如:
表示的where语句是:Entity where = Entity.create(TABLE_NAME).set("条件1", "条件值");
当然到时候会用PreparedStatement,不会出现SQL注入。WHERE `条件1` = 条件值
2. Table Column
这两个对象主要是描述数据库表结构的,暂时和ORM本身没啥关系,只是当你想获得一些字段信息的时候,这样来获得表结构信息:private static void getTableMetaInfo(DataSource ds) {
// 获得当前库的所有表的表名
List<String> tableNames = DbUtil.getTables(ds);
Log.info("{}", tableNames);
/*
* 获得表结构 表结构封装为一个表对象,里面有Column对象表示一列,列中有列名、类型、大小、是否允许为空等信息
*/
Table table = DbUtil.getTableMeta(ds, TABLE_NAME);
Log.info("{}", table);
}
数据库简单操作-Db
由来
数据库操作不外乎四门功课:增删改查,在Java的世界中,由于JDBC的存在,这项工作变得简单易用,但是也并没有做到使用上的简化。于是出现了JPA(Hibernate)、MyBatis、Jfinal、BeetlSQL等解决框架,或解决多数据库差异问题,或解决SQL维护问题。而Hutool对JDBC的封装,多数为在小型项目中对数据处理的简化,尤其只涉及单表操作时。OK,废话不多,来个Demo感受下。
使用
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方式传入条件暂时不支持同字段多条件的情况。
查
查询全部字段
//user为表名
Db.use().findAll("user");
条件查询
Db.use().findAll(Entity.create("user").set("name", "unitTestUser"));
模糊查询
Db.use().findLike("user", "name", "Test", LikeType.Contains);
或者:
List<Entity> find = Db.use().find(Entity.create("user").set("name", "like 王%"));
分页查询
//Page对象通过传入页码和每页条目数达到分页目的
PageResult<Entity> result = Db.use().page(Entity.create("user").set("age", "> 30"), new Page(10, 20));
执行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, "张三");
事务
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"));
});
支持命名占位符的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
支持事务的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
方法后才会合并提交,提交或者回滚后会恢复默认的自动提交模式。
新增
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();
}
更新
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();
}
删除
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.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 =
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
数据源工厂-DsFactory
释义
数据源(DataSource)的概念来自于JDBC规范中,一个数据源表示针对一个数据库(或者集群)的描述,从数据源中我们可以获得N个数据库连接,从而对数据库进行操作。
每一个开源JDBC连接池都有对DataSource的实现,比如Druid为DruidDataSource,Hikari为HikariDataSource。但是各大连接池配置各不相同,配置文件也不一样,Hutool的针对常用的连接池做了封装,最大限度简化和提供一致性配置。
Hutool的解决方案是:在ClassPath中使用config/db.setting
一个配置文件,配置所有种类连接池的数据源,然后使用DsFactory.get()
方法自动识别数据源以及自动注入配置文件中的连接池配置(包括数据库连接配置)。DsFactory
通过try
的方式按照顺序检测项目中引入的jar包来甄别用户使用的是哪种连接池,从而自动构建相应的数据源。
Hutool支持以下连接池,并按照其顺序检测存在与否:
- HikariCP
- Druid
- Tomcat
- Dbcp
- 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
DataSource ds = DSFactory.get(setting, null);
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对数据源的封装很好的诠释了以下几个原则:
- 自动识别优于用户定义
- 便捷性与灵活性并存
- 适配与兼容
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);
}
常见问题
问题描述:no suitable driver found for jdbc
解答:出现此类问题一般是JDBC驱动版本不一致导致的,出现此问题的用户使用的是ojdbc14,服务端使用Oracle11g,JDK8,此处升级到ojdbc6即可。
版本对应见:https://www.oracle.com/technetwork/database/application-development/jdbc/downloads/index.html