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());
}
}