2017 年记录

php转java小结

经过2个多月的奋战,运营及电催系统php转java终于上线。为开发的过程做个小结。

虽然大多java开发不太懂php,好在语言这个东西虽然大家不会使用php来做开发,但是基本上能看懂php代码的逻辑。

不吐槽php代码里面的一些神奇的逻辑,针对于此次重构只从技术上做写总结。

一、Java8特性

在我们的代码中,有大量的List

stream结合lambda让这些转变变成so easy。

List->List

  1. List<Case> caseList = caseMapper.selectByExample(caseExample);
  2. List<Integer> companyIdList = caseList.stream()
  3. .map(Case::getClientCompanyId)
  4. .collect(Collectors.toList());
  1. List<Integer> companyIdList1 = caseList.stream()
  2. .map(caze->{
  3. if(caze.getClientCompanyId()==0){
  4. return -1;
  5. }
  6. return caze.getClientCompanyId()+0;
  7. })
  8. .collect(Collectors.toList());

过滤list

  1. /**
  2. * 过滤null
  3. */
  4. relationshipTargetList = relationshipTargetList.stream().filter(r -> r!=null).collect(Collectors.toList());

List->Map

  1. List<Debtor> debtorList = debtorMapper.selectByExample(debtorExample);
  2. Map<Integer, Debtor> debtorMap = debtorList.stream().collect(Collectors.toMap(Debtor::getCaseId, d -> d,
  3. (key1, key2) -> key1));
  1. Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
  2. Function<? super T, ? extends U> valueMapper,
  3. BinaryOperator<U> mergeFunction)

Map.getOrDefault()

再也不用担心空指针了

  1. /**
  2. * # 案件发布公司
  3. */
  4. CompanyInfo companyInfo = companyInfoMap.getOrDefault(caze.getClientCompanyId(),defaultCompanyInfo);
  5. result.setClient_company(companyInfo.getName());

二、Apache common包的使用

  1. StringUtils绝对是字符串操作利器,大部分对字符串的常用操作都有实现,不用担心空指针。推荐指数五星级。
  2. NumberUtils数字转换利器,不管你是toInttoLong还是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

等于号=

  1. QueryBuilders.termQuery("isDel", 1))
  2. // termQuery相当于=,相当于isDel=1

大于小于号

  1. QueryBuilders.rangeQuery("overdueStarttime").lte(startDate.getTime())
  2. //rangeQuery
  3. //lte <=
  4. //lt <
  5. //gte >=
  6. //gt >

or、and

  1. boolQueryBuilder.must(QueryBuilders.termQuery("isDel", req.getIs_del()));
  2. //must相当于and
  3. caseStatusQuery.should(QueryBuilders.termQuery("caseStatus", s));
  4. //should 相当于or

in

  1. QueryBuilders.termsQuery("clientCompanyId",req.getClientCompanyIdList())
  2. //termsQuery 相当于in,可以传递list

like

  1. QueryBuilders.wildcardQuery("phone1", "*" + req.getTelephone() + "*")
  2. //wildcard 正则匹配
  3. QueryBuilders.matchPhraseQuery("debtorName", req.getDebtor_name())
  4. //matchPhrase 不会分词,直接匹配有完整相关短语的记录

sum && count

  1. AggregationBuilder debtTotalSum = AggregationBuilders.sum(DEBT_TOTAL_SUM_KEY).field("debtTotal");
  2. AggregationBuilder debtTotalCount = AggregationBuilders.count(DEBT_TOTAL_COUNT_KEY).field("debtTotal");

group by

  1. AggregationBuilder clientCompanyIdAgg = AggregationBuilders.terms("clientCompanyIds").field("clientCompanyId").size(5000);
  2. //以clientCompanyId字段聚合,别名为clientCompanyIds

order by and limit

  1. SearchResponse searchResponse = client.prepareSearch(ElasticSearchUtil.getIndexName())
  2. .setTypes(ElasticSearchUtil.TYPE_NAME)
  3. .setQuery(boolQueryBuilder)
  4. //指定查询字段
  5. .addStoredField("id")
  6. .addSort(order, sortOrder)
  7. //分页
  8. .setFrom((currentPage - 1) * limit).setSize(limit)
  9. .execute()
  10. .actionGet();

批量插入

  1. public void batchInsert(List<CaseTable> list,Client client) {
  2. LOGGER.info("---------案件批量插入索引开始--------");
  3. BulkRequestBuilder bulkRequest = client.prepareBulk();
  4. long start = System.currentTimeMillis();
  5. BulkRequestBuilder delRequest = client.prepareBulk();
  6. for (CaseTable caseTable :list){
  7. IndexRequestBuilder ir = client.prepareIndex()
  8. .setIndex(ElasticSearchUtil.INDEX_NAME_CASE)
  9. .setType(ElasticSearchUtil.TYPE_NAME_CASE)
  10. .setId(caseTable.getId().toString())
  11. .setSource(JSON.toJSONString(caseTable));
  12. bulkRequest.add(ir);
  13. start = System.currentTimeMillis();
  14. BulkResponse bulkResponse = bulkRequest.execute().actionGet();
  15. LOGGER.info("案件批量插入循环所用时间 search time:{}ms", System.currentTimeMillis() - start);
  16. if (!bulkResponse.hasFailures()) {
  17. LOGGER.info("----------案件批量插入成功一轮------------");
  18. }
  19. }

踩过的坑

  • 当查询条件多余1024个时,会报错
  • 当分页的记录大于10000时,会报错
  • 当使用浮点数进行sum时会有精度丢失
  • 当group by字段是非数字时,会报错
  • 如果terms(in)条件比较多的话,使用must则非常慢
  • 大数值的比较不生效

如1505446228090(13位)居然小于905446228090(12位)

  • ES的数据存放目录被设置到了系统盘,导致磁盘被沾满

四、数据库的读写分离

实现spring封装的AbstractRoutingDataSource,很容易在多数据源之间进行切换

AbstractRoutingDataSource 一些关键代码

  1. public Connection getConnection() throws SQLException {
  2. return this.determineTargetDataSource().getConnection();
  3. }
  4. protected DataSource determineTargetDataSource() {
  5. Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  6. Object lookupKey = this.determineCurrentLookupKey();
  7. DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
  8. if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
  9. dataSource = this.resolvedDefaultDataSource;
  10. }
  11. if(dataSource == null) {
  12. throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
  13. } else {
  14. return dataSource;
  15. }
  16. }

流程
当调用getConnection()方法获取connection连接时,AbstractRoutingDataSource 会先执行determineTargetDataSource()方法来找到当前的数据源。

先根据lookupKey从resolvedDataSources(Map resolvedDataSources)查找数据源,如果没有找到则取默认的数据源。

动态数据源的一个实现

  1. public class DynamicDataSource extends AbstractRoutingDataSource {
  2. private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>();
  3. public static void setDataSourceType(DatabaseType databaseType) {
  4. contextHolder.set(databaseType);
  5. }
  6. public static DatabaseType getDataSourceType() {
  7. return contextHolder.get();
  8. }
  9. public static void clearCustomerType() {
  10. contextHolder.remove();
  11. }
  12. @Override
  13. protected Object determineCurrentLookupKey() {
  14. return getDataSourceType();
  15. }
  16. }

DynamicDataSource的使用

  1. /**
  2. * 数据源设置
  3. */
  4. @Bean
  5. public DynamicDataSource dataSource() throws SQLException {
  6. DruidDataSource primaryDataSource = primaryDataSource();
  7. Map<Object, Object> mapDataSources = new HashMap<Object, Object>();
  8. mapDataSources.put(DatabaseType.zichan360_case, primaryDataSource);
  9. mapDataSources.put(DatabaseType.zichan360_case_read,primaryDataSourceRead());
  10. DynamicDataSource dataSource = new DynamicDataSource();
  11. dataSource.setTargetDataSources(mapDataSources);// 该方法是AbstractRoutingDataSource的方法
  12. dataSource.setDefaultTargetDataSource(primaryDataSource);// 默认的datasource设置为primaryDataSource
  13. return dataSource;
  14. }

怎么切换读写数据源呢

  1. AOP根据关键字切分肯定不行,没有对应的约定。
  2. 一个事务内必须是同一个数据源,不然事务会有问题

对于代码无入侵的方式我们还没有想出来,欢迎大家提供思路。

我们的做法
利用spring事务的默认传播属性,有事务则加入,没有事务则开启新事务。我们只需要在开启事务的入口处能判断出来即可。

覆盖事务管理器

  1. public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
  2. public DynamicDataSourceTransactionManager() {
  3. }
  4. public DynamicDataSourceTransactionManager(DataSource dataSource) {
  5. super(dataSource);
  6. }
  7. /**
  8. * 只读事务到读库,读写事务到写库
  9. * @param transaction
  10. * @param definition
  11. */
  12. @Override
  13. protected void doBegin(Object transaction, TransactionDefinition definition) {
  14. //设置数据源
  15. boolean readOnly = definition.isReadOnly();
  16. if(readOnly) {
  17. DynamicDataSource.setDataSourceType(DatabaseType.zichan360_case_read);
  18. } else {
  19. DynamicDataSource.setDataSourceType(DatabaseType.zichan360_case);
  20. }
  21. super.doBegin(transaction, definition);
  22. }
  23. /**
  24. * 清理本地线程的数据源
  25. * @param transaction
  26. */
  27. @Override
  28. protected void doCleanupAfterCompletion(Object transaction) {
  29. super.doCleanupAfterCompletion(transaction);
  30. DynamicDataSource.clearCustomerType();
  31. }
  32. }
  1. //配置事务管理器
  2. @Bean
  3. public PlatformTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
  4. return new DynamicDataSourceTransactionManager(dataSource);
  5. }

配置代码@Transactional(readOnly = true) 一起使用