简要介绍
在看代码前要必要了解一下java中的值传递和引用传递区别。
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,方法执行中形式参数值的改变不影响实际参 数的值。
引用传递:也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,方法执行中形式参数值的改变将会影响实际参数的值。
public static void mergeHotelTree() { long start = System.currentTimeMillis(); SpringContextAdaptor context = QunarServer.getService("SpringAdaptor"); LinkageClient client = context.getBean("linkageClient"); HotelIterator<Hotel> hotelIter = null; EasyConnection con = null; try { hotelIter = client.getLinkageInrement(); //第一次调用获取数据 List<Hotel> hotelList = new ArrayList<Hotel>(); int count = 0; while (hotelIter.hasNext()) { Hotel hotel = hotelIter.next(); hotelList.add(hotel); count++; if(count % 100 == 0) { Map<String, String> wrapperhotelEnableMap = getWHEnableAfterSendProcReq(hotelList); //发送proc请求,返回enable数据集 mergeHotelTreeHandleDB(con, hotelList, wrapperhotelEnableMap); //入库操作 hotelList.clear(); //处理完了,最后清空数据 } } if( hotelList != null && hotelList.size() > 0 ){ Map<String, String> wrapperhotelEnableMap = getWHEnableAfterSendProcReq(hotelList); //发送proc请求,返回enable数据集 mergeHotelTreeHandleDB(con, hotelList, wrapperhotelEnableMap); //入库操作 } } catch (Exception e) { if (hotelIter != null) { hotelIter.rollBack(); } QMonitor.recordOne("mergeHotelTree_fail",System.currentTimeMillis()-start); e.printStackTrace(); } finally { if (hotelIter != null) { try { hotelIter.close(); } catch (IOException e) { e.printStackTrace(); } } try { if (con != null) con.close(); } catch (java.sql.SQLException e) { e.printStackTrace(); } } } public static void mergeHotelTreeHandleDB(EasyConnection con, List<Hotel> hotelList, Map<String, String> wrapperhotelEnableMap) throws Exception { int count = 0; for (Hotel hotel: hotelList ) { String hotelSeq = null; long mergeStart = System.currentTimeMillis(); if (con == null || con.isClosed()) con = ConnectionManager.currentManager().getConnection( "hotelbase"); String cityCode = getCityFromHotel(hotel.getHotelSeq()); int affectRow = 0; con.setAutoCommit(false); ......大量DML操作 con.commit(); if ((++count) % 100 == 0) con.close(); QMonitor.recordOne("mergeHotelTree_succ", System.currentTimeMillis()-mergeStart); } }
代码逻辑问题分析
此处代码逻辑要处理的需求如下。
1.sync从qhotel系统可能一次获取hotel数据量过大,sync需要调用proc接口同步数据,但是大小有限制,所以获取的hotel列表需要分多次同步proc系统,每次同步100条hotel信息。
2.mergeHotelTree方法获取hotel信息列表,每循环迭代100次hotel,做一次DML操作。
3.目前代码问题是每循环迭代100次hotel才con.close()释放连接资源,如果最后一次循环迭代次数<100,mergeHotelTree方法是不会做close()的,而且如果出现DML操作异常,也是不能正常释放连接资源。
上述代码中实际参数con,其引用对象为null,实际在内存中表现是没有任何有效地址空间,在做形式参数传递过程中做了参数拷贝,con变量地址发生转移, 此时实际参数与形式参数的con栈对象地址不同,调用getConnection方法后所指向的堆内存也就不同。
请看如下图所示:
说明:
1.每当程序调用一个方法时,都会创建自己独立的栈内存(空间),调用完成后,会退栈。
2.mergeHotelTree方法中con对象指向地址为0x0,mergeHotelTreeHandleDB方法con对象,一直操作自己 的栈内存,与mergeHotelTree方法的栈内存无关,mergeHotelTree的con指向地址没有任何变化还是0x0。
方案1.
mergeHotelTreeHandleDB方法中,修改关闭连接对象判断代码:if ((++count) % hotelList.size() == 0),但这里有个问题,DML操作异常,也是不能正常释放连接资源。
方案2.
public static void mergeHotelTreeHandleDB(List<Hotel> hotelList, Map<String, String> wrapperhotelEnableMap) throws Exception { EasyConnection con = null; try { con = ConnectionManager.currentManager().getConnection( "hotelbase"); for (Hotel hotel: hotelList ) { String hotelSeq = null; long mergeStart = System.currentTimeMillis(); String cityCode = getCityFromHotel(hotel.getHotelSeq()); int affectRow = 0; con.setAutoCommit(false); 大量DML操作 con.commit(); QMonitor.recordOne("mergeHotelTree_succ", System.currentTimeMillis()-mergeStart); } } catch (Exception e) { logger.log(Level.SEVERE, e.getMessage(), e); throw e; } finally { try { if (con != null) con.close(); } catch (java.sql.SQLException e) { e.printStackTrace(); } } }
方案3:
如果是c/c+代码,用指针来修改原版代码,也是没有问题的。对于一个写了几年c/c+的工程师来说,如果不知道底层内存结构和分配,是寸步难行的。
代码如下:
static void HotelMergeQHotel::mergeHotelTree() { .......省略 std::Vector<Hotel> hotelIter = NULL; EasyConnection *con = NULL; .......省略 std::Vector<Hotel> hotelList = new Vector<Hotel>(); // stl Vector int count = 0; while (循环迭代) { hotelList.push_back(hotel); //具体代码省略处理 count++; if(count % 100 == 0) { .......省略 mergeHotelTreeHandleDB(&con, hotelList, wrapperhotelEnableMap); //入库操作 .......省略 } } if( hotelList != null && hotelList.size() > 0 ){ .......省略 mergeHotelTreeHandleDB(&con, hotelList, wrapperhotelEnableMap); //入库操作 } if( con != NULL) con->close(); } static void HotelMergeQHotel::mergeHotelTreeHandleDB(EasyConnection **con, std::Vector<Hotel> hotelList, .......省略) throws Exception { int count = 0; for (Hotel hotel: hotelList ) { if ((*con) == NULL || (*con)->isClosed()) *con = ConnectionManager.currentManager().getConnection( "hotelbase"); (*con)->setAutoCommit(false); ......大量DML操作 (*con)->commit(); if ((++count) % 100 == 0) (*con)->close(); } }
说明:
1.mergeHotelTree方法内定义con变量为指针,mergeHotelTreeHandleDB方法形式参数为二级指针,这种做法目 的就是把mergeHotelTree方法栈内con变量地址传递到了mergeHotelTree,当mergeHotelTree做参数拷贝时,是拷 贝的mergeHotelTree的con变量地址,当mergeHotelTreeHandleDB对con进行操作时,实际是对 mergeHotelTree的con操作.
重点是理解:mergeHotelTree栈内变量con存储的是mergeHotelTreeHandleDB栈内变量的地址。