1、前言
DBUtils是apache下的一个小巧的JDBC轻量级封装的工具包,其最核心的特性是 结果集的封装 ,可以直接将查询出来的结果集封装成JavaBean,这就为我们做了最枯燥乏味、最容易出错的一大部分工作。
核心类介绍:
1:DbUtils:连接数据库对象----jdbc辅助方法的集合类,线程安全。作用:控制连接,控制驱动加载。与druid集成的话,此类用不到
2:QueryRunner:SQL语句的操作对象。作用:可以设置查询结果集的封装策略,线程安全
3:ResultSetHandle:封装数据的策略对象------将封装结果集中的数据,转换到另一个对象。策略:封装数据到对象的方式(eg:将数据库保存在自定义java bean、或保存到数组、保存到集合)
Druid号称是Java语言中最好的 数据库连接池 。Druid能够提供强大的监控和扩展功能 。监控配置可参阅:
https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE>
https://github.com/alibaba/druid/wiki/配置_StatFilter
本文以postgresql为例对druid与DBuitls的集成做一总结
2、需求
项目中需要对postgresql数据库中存储的代理IP做检查,定时将过期的IP记录删除。定时功能看下一篇。
3、代码
先添加依赖
dependencies {
compile "commons-dbutils:commons-dbutils:${commonsDbutilsVersion}"
compile 'org.apache.logging.log4j:log4j-core:2.8.2'
compile "com.alibaba:druid:${druidVersion}"
compile "mysql:mysql-connector-java:${mysqlConnectorVersion}"
compile 'org.postgresql:postgresql:42.2.5'
}
ext {
commonsDbutilsVersion = '1.6'
druidVersion = '1.0.18'
mysqlConnectorVersion = '5.1.37'
}
代理IP的属性如下所示,其中字段ip包含了IP地址和端口,提前在数据库中新建表,字段与该类属性对应。
public class IPItem
{
private Long id;
private String did;
private String ip;
private String type;
private String position;
private String speed;
private String lastCheckTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDid() {
return did;
}
public void setDid(String did) {
this.did = did;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public String getSpeed() {
return speed;
}
public void setSpeed(String speed) {
this.speed = speed;
}
public String getLastCheckTime() {
return lastCheckTime;
}
public void setLastCheckTime(String lastCheckTime) {
this.lastCheckTime = lastCheckTime;
}
}
我们一般基于Druid做数据库连接池封装(通常设为单例),即读取配置文件中的数据库相关配置。在与dbutils集成的时候暴露一个QueryRunner对象,供其他类调用。将封装的数据库连接池命名为PostgreSQLPool ,代码如下:
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.**.ConfigParser;
import com.**.Logger;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.dbutils.QueryRunner;
public class PostgreSQLPool {
private static PostgreSQLPool instance = null;
private static final Logger logger = Logger.getLogger(PostgreSQLPool.class);
private DruidDataSource dds;
private QueryRunner runner;
private Properties properties;
public QueryRunner getRunner() {
return this.runner;
}
private PostgreSQLPool() {
ConfigParser parser = ConfigParser.getInstance();
String dbAlias = "postgresql-data";
Map<String, Object> dbConfig = parser.getModuleConfig("database");
Map<String, Object> postgresqlConfig = (Map)parser.assertKey(dbConfig, dbAlias, "database");
Properties properties = new Properties();
String url = (String)parser.assertKey(postgresqlConfig, "url", "database." + dbAlias);
String username = (String)parser.assertKey(postgresqlConfig, "username", "database." + dbAlias);
String password = (String)parser.assertKey(postgresqlConfig, "password", "database." + dbAlias);
properties.setProperty("url", url);
properties.setProperty("username", username);
properties.setProperty("password", password);
properties.setProperty("maxActive", "20");
this.properties = properties;
try {
this.dds = (DruidDataSource)DruidDataSourceFactory.createDataSource(properties);
} catch (Exception var10) {
logger.error("Failed to connect data PostgreSQL db,Exception:{}", var10);
}
this.runner = new QueryRunner(this.dds);
}
public static PostgreSQLPool getInstance() {
if (instance == null) {
Class var0 = PostgreSQLPool.class;
synchronized(PostgreSQLPool.class) {
if (instance == null) {
instance = new PostgreSQLPool();
}
}
}
return instance;
}
}
这里可以自定义数据库配置文件的读取方式,将ConfigParser用你的读取方式替换即可,测试用的话也可先写死在里面。ConfigParser类为我司爬虫项目的配置读取类,主要是读取yaml文件,解析层次结构的配置对象。"config.yml"为项目路径src/main/resource或src/main/config目录下的配置文件名,ConfigParser代码如下:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Map;
import org.yaml.snakeyaml.Yaml;
public class ConfigParser {
private static final Logger logger = Logger.getLogger(ConfigParser.class);
private static ConfigParser instance = new ConfigParser();
private static final String CONFIG_FILENAME = "config.yml";
private Yaml yaml = null;
private Object config;
private ConfigParser() {
if (this.yaml == null) {
this.yaml = new Yaml();
}
File f = ResourceUtils.loadResouces("config.yml");
try {
this.config = this.yaml.load(new FileInputStream(f));
logger.info("file {} is loaded", f.getAbsoluteFile());
} catch (FileNotFoundException var3) {
var3.printStackTrace();
}
}
public static ConfigParser getInstance() {
return instance;
}
public Object getConfig() {
return this.config;
}
public Map<String, Object> getModuleConfig(String name) {
return this.getModuleConfig(name, this.config);
}
public Map<String, Object> getModuleConfig(String name, Object parent) {
Map<String, Object> rtn = (Map)((Map)parent).get(name);
return rtn;
}
public Object assertKey(Map<String, Object> config, String key, String parent) {
Object value = config.get(key);
if (value == null) {
logger.error("{}.{} is a mandatory configuration", new Object[]{parent, key});
System.exit(0);
}
return value;
}
public Object getValue(Map<String, Object> config, String key, Object def, String parent) {
Object value = config.get(key);
if (value == null) {
logger.warn("{}.{} is't configured, default value {} is used", new Object[]{parent, key, def});
config.put(key, def);
return def;
} else {
return value;
}
}
public void dumpConfig() {
System.out.println(this.yaml.dump(this.config));
}
}
config.yml配置如下:
apps:
## 基本属性
spider-ipxundaili:
common:
group: ipproxy-xundaili-zhg
cron: "0 */5 * * * ?"
firstpage: 1
totalpages: 1
distribute: false
fixed: true
order: desc
## 数据源
source:
baseurl: http://api.xdaili.cn/xdaili-api//greatRecharge/getGreatIp?spiderId=***&orderno=***&returnType=2&count=10
listpageregex: "http://www\\.xdaili\\.cn/"
## 存储路径
storage:
## dbType: MySQL HBase Hive MongoDB Kafka
dbtype: PostgreSQL
dbalias: postgresql-data
filter:
searchfilter: false
contentfilter: false
## 反爬虫
antirobot:
ipproxy: false
listipproxy: false
# 15分钟提取一次 一天提取96次 一次10个 共计960个IP
sleeptime: 900000
analysis:
sentiment: false
distribute:
scheduler: com.cetiti.ddc.scheduler.MemberScheduler
# dbtype: redis
# dbalias: redis
database:
postgresql-data:
url: "jdbc:postgresql://ip:port/dbname"
username: postgres
password: ***
table-pre: ip_
table: proxy
redis:
ip: 10.0.30.75
port: 6379
编写Dao层代码,delete方法为根据入参item的id删除ip_proxy表中的代理IP记录,另一个getAllItemFromDB方法是在ip_proxy表中查询出前2000条代理IP记录。其中查询结果集用new BeanListHandler(IPItem.class)封装。
import com.**.IPItem;
import com.**.PostgreSQLPool;
import com.**.logger.Logger;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class PostgreSQLDao {
private static final Logger logger = Logger.getLogger(PostgreSQLDao.class);
private static QueryRunner runner;
public PostgreSQLDao(){
runner = PostgreSQLPool.getInstance().getRunner();
}
public void delete(IPItem item){
String iSql = "delete from ip_proxy where id = ?";
Object[] iParams={
item.getId()
};
try {
runner.insert(iSql,new MapHandler(),iParams);
} catch (SQLException e) {
logger.error("Failed to storage item to PostgreSQL db,Exception:{}",e);
}
}
@SuppressWarnings("unchecked")
public List<IPItem> getAllItemFromDB(){
try {
String qSql = "select * from ip_proxy limit 2000";
@SuppressWarnings("rawtypes")
BeanListHandler blh = new BeanListHandler(IPItem.class);
//以自定义类IPItem的list封装查询结果集
return (ArrayList<IPItem>) runner.query(qSql,blh);
} catch (SQLException e) {
logger.error("getAllIpFromDB", e);
return null;
}
}
}
封装处理策略如下:
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个对象数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的Java Bean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的Java Bean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler:将结果集中的每一行数据都封装到一个Map里,然后再根据指定的key把每个Map再存放到一个Map里。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List。
- ScalarHandler:返回指定列的一个值或返回一个统计函数的值,通常用于封装类似count、avg、max、min、sum······函数的执行结果
接口调用(先查询出表中记录,然后逐一删除之):
public static void main(String[] args) {
List<IPItem> items = postgreSQLDao.getAllItemFromDB();
for(IPItem item : items){
postgresql.delete(item);
logger.info("cancel the proxy IP {} ! ",item.getIp());
}
}