2017 年记录
php转java小结
经过2个多月的奋战,运营及电催系统php转java终于上线。为开发的过程做个小结。
虽然大多java开发不太懂php,好在语言这个东西虽然大家不会使用php来做开发,但是基本上能看懂php代码的逻辑。
不吐槽php代码里面的一些神奇的逻辑,针对于此次重构只从技术上做写总结。
一、Java8特性
在我们的代码中,有大量的List
stream结合lambda让这些转变变成so easy。
List->List
List<Case> caseList = caseMapper.selectByExample(caseExample);List<Integer> companyIdList = caseList.stream().map(Case::getClientCompanyId).collect(Collectors.toList());
List<Integer> companyIdList1 = caseList.stream().map(caze->{if(caze.getClientCompanyId()==0){return -1;}return caze.getClientCompanyId()+0;}).collect(Collectors.toList());
过滤list
/*** 过滤null*/relationshipTargetList = relationshipTargetList.stream().filter(r -> r!=null).collect(Collectors.toList());
List->Map
List<Debtor> debtorList = debtorMapper.selectByExample(debtorExample);Map<Integer, Debtor> debtorMap = debtorList.stream().collect(Collectors.toMap(Debtor::getCaseId, d -> d,(key1, key2) -> key1));
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction)
Map.getOrDefault()
再也不用担心空指针了
/*** # 案件发布公司*/CompanyInfo companyInfo = companyInfoMap.getOrDefault(caze.getClientCompanyId(),defaultCompanyInfo);result.setClient_company(companyInfo.getName());
二、Apache common包的使用
StringUtils绝对是字符串操作利器,大部分对字符串的常用操作都有实现,不用担心空指针。推荐指数五星级。NumberUtils数字转换利器,不管你是toInt、toLong还是toDouble,使用它保证你不会后悔。
三、ElasticSearch
此次我们重构直接把版本升级至5.1.1,调用方式由原php的restful形式改成client的api接口形式的调用。
今天不解释分词、不介绍打分,只说我们项目中使用到的干货。
安装
这里有个简单的安装总结,大家参考一下
http://note.youdao.com/noteshare?id=b7ad559c78872fda66c53a39996e337f
使用需求
类比mysql,我们需要=、>、>=、<、<= 、or、and、in、like、count、sum、group by、order by、limit
在高版本的ES里面使用了boolquery替换了filter
等于号=
QueryBuilders.termQuery("isDel", 1))// termQuery相当于=,相当于isDel=1
大于小于号
QueryBuilders.rangeQuery("overdueStarttime").lte(startDate.getTime())//rangeQuery//lte <=//lt <//gte >=//gt >
or、and
boolQueryBuilder.must(QueryBuilders.termQuery("isDel", req.getIs_del()));//must相当于andcaseStatusQuery.should(QueryBuilders.termQuery("caseStatus", s));//should 相当于or
in
QueryBuilders.termsQuery("clientCompanyId",req.getClientCompanyIdList())//termsQuery 相当于in,可以传递list
like
QueryBuilders.wildcardQuery("phone1", "*" + req.getTelephone() + "*")//wildcard 正则匹配QueryBuilders.matchPhraseQuery("debtorName", req.getDebtor_name())//matchPhrase 不会分词,直接匹配有完整相关短语的记录
sum && count
AggregationBuilder debtTotalSum = AggregationBuilders.sum(DEBT_TOTAL_SUM_KEY).field("debtTotal");AggregationBuilder debtTotalCount = AggregationBuilders.count(DEBT_TOTAL_COUNT_KEY).field("debtTotal");
group by
AggregationBuilder clientCompanyIdAgg = AggregationBuilders.terms("clientCompanyIds").field("clientCompanyId").size(5000);//以clientCompanyId字段聚合,别名为clientCompanyIds
order by and limit
SearchResponse searchResponse = client.prepareSearch(ElasticSearchUtil.getIndexName()).setTypes(ElasticSearchUtil.TYPE_NAME).setQuery(boolQueryBuilder)//指定查询字段.addStoredField("id").addSort(order, sortOrder)//分页.setFrom((currentPage - 1) * limit).setSize(limit).execute().actionGet();
批量插入
public void batchInsert(List<CaseTable> list,Client client) {LOGGER.info("---------案件批量插入索引开始--------");BulkRequestBuilder bulkRequest = client.prepareBulk();long start = System.currentTimeMillis();BulkRequestBuilder delRequest = client.prepareBulk();for (CaseTable caseTable :list){IndexRequestBuilder ir = client.prepareIndex().setIndex(ElasticSearchUtil.INDEX_NAME_CASE).setType(ElasticSearchUtil.TYPE_NAME_CASE).setId(caseTable.getId().toString()).setSource(JSON.toJSONString(caseTable));bulkRequest.add(ir);start = System.currentTimeMillis();BulkResponse bulkResponse = bulkRequest.execute().actionGet();LOGGER.info("案件批量插入循环所用时间 search time:{}ms", System.currentTimeMillis() - start);if (!bulkResponse.hasFailures()) {LOGGER.info("----------案件批量插入成功一轮------------");}}
踩过的坑
- 当查询条件多余1024个时,会报错
- 当分页的记录大于10000时,会报错
- 当使用浮点数进行sum时会有精度丢失
- 当group by字段是非数字时,会报错
- 如果terms(in)条件比较多的话,使用must则非常慢
- 大数值的比较不生效
如1505446228090(13位)居然小于905446228090(12位)
- ES的数据存放目录被设置到了系统盘,导致磁盘被沾满
四、数据库的读写分离
实现spring封装的AbstractRoutingDataSource,很容易在多数据源之间进行切换
AbstractRoutingDataSource 一些关键代码
public Connection getConnection() throws SQLException {return this.determineTargetDataSource().getConnection();}protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = this.determineCurrentLookupKey();DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);if(dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if(dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");} else {return dataSource;}}
流程
当调用getConnection()方法获取connection连接时,AbstractRoutingDataSource 会先执行determineTargetDataSource()方法来找到当前的数据源。
先根据lookupKey从resolvedDataSources(Map
动态数据源的一个实现
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>();public static void setDataSourceType(DatabaseType databaseType) {contextHolder.set(databaseType);}public static DatabaseType getDataSourceType() {return contextHolder.get();}public static void clearCustomerType() {contextHolder.remove();}@Overrideprotected Object determineCurrentLookupKey() {return getDataSourceType();}}
DynamicDataSource的使用
/*** 数据源设置*/@Beanpublic DynamicDataSource dataSource() throws SQLException {DruidDataSource primaryDataSource = primaryDataSource();Map<Object, Object> mapDataSources = new HashMap<Object, Object>();mapDataSources.put(DatabaseType.zichan360_case, primaryDataSource);mapDataSources.put(DatabaseType.zichan360_case_read,primaryDataSourceRead());DynamicDataSource dataSource = new DynamicDataSource();dataSource.setTargetDataSources(mapDataSources);// 该方法是AbstractRoutingDataSource的方法dataSource.setDefaultTargetDataSource(primaryDataSource);// 默认的datasource设置为primaryDataSourcereturn dataSource;}
怎么切换读写数据源呢
- AOP根据关键字切分肯定不行,没有对应的约定。
- 一个事务内必须是同一个数据源,不然事务会有问题
对于代码无入侵的方式我们还没有想出来,欢迎大家提供思路。
我们的做法
利用spring事务的默认传播属性,有事务则加入,没有事务则开启新事务。我们只需要在开启事务的入口处能判断出来即可。
覆盖事务管理器
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {public DynamicDataSourceTransactionManager() {}public DynamicDataSourceTransactionManager(DataSource dataSource) {super(dataSource);}/*** 只读事务到读库,读写事务到写库* @param transaction* @param definition*/@Overrideprotected void doBegin(Object transaction, TransactionDefinition definition) {//设置数据源boolean readOnly = definition.isReadOnly();if(readOnly) {DynamicDataSource.setDataSourceType(DatabaseType.zichan360_case_read);} else {DynamicDataSource.setDataSourceType(DatabaseType.zichan360_case);}super.doBegin(transaction, definition);}/*** 清理本地线程的数据源* @param transaction*/@Overrideprotected void doCleanupAfterCompletion(Object transaction) {super.doCleanupAfterCompletion(transaction);DynamicDataSource.clearCustomerType();}}
//配置事务管理器@Beanpublic PlatformTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {return new DynamicDataSourceTransactionManager(dataSource);}
配置代码@Transactional(readOnly = true) 一起使用
