大赢合治 大赢合治
关注数: 0 粉丝数: 48 发帖数: 9,604 关注贴吧数: 1
血的教训!奉劝大家不要去北京仁德医院体检中心! 前几天,我由于新找到了工作,本来想在房山附近的良乡医院体检,可是疫情期间居然关闭了,无奈便找了另一个最近的北京仁德医院体检中心。 按照入职规定,体检项目包括:一般情况(身高、体重)、内科检查、外科检查、心电图、胸部正位片(DR)、血常规(三分类)、尿常规、肝功2项、静脉采血 腹部B超。 这些项目在别家体检中心就是一个套餐内都包含的,(当然这是我后来才知道的),但是在仁德这里,他们把一个套餐拆开了,把腹部B超单独抽出来作为一项,要价114元,所有项目加起来要价264元。她们也没有提醒腹部B超都包含哪些项目,就让我去体检了。当我拿到体检报告后,交给入职公司审核后,审核失败,说是腹部B超没有双肾的检查。于是,我打电话问仁德,他们说,在2021年10月双肾检查单独抽出来儿为泌尿系统B超了,我说你们没有提醒,太坑人了,我要投诉,他们说报告给上级,然后又说各家医院收费标准不一样,用这种理由搪塞。 入职公司推荐的体检机构所有检查项目加起来才121元(只是邮件接收的晚,等我做完体检了才收到邮件),我跟他们说这里便宜,他们居然笑着说“那您觉得那里便宜,您可以去那里做体检”,这卑劣的嘴脸真是让人气愤至极! 所以大家一定要认清他们的丑恶面目,把本来一套的检查拆开收费加价,良心真是让狗吃了!!! 以小见大,一个体检中心是这样,整个北京仁德医院也好不到哪去,所以,聪明人千万不要到这个黑心医院,免得耗时耗财,惹的一身騒。(这是北京仁德医院的外观)
血的教训!奉劝大家不要去北京仁德医院体检中心! 前几天,我由于新找到了工作,就在房山附近的良乡医院体检,可以疫情期间居然关闭了,便找了另一个最近的北京仁德医院体检中心。 按照入职规定,体检项目包括:一般情况(身高、体重)、内科检查、外科检查、心电图、胸部正位片(DR)、血常规(三分类)、尿常规、肝功2项、静脉采血 腹部B超。 这些项目在别家体检中心就是一个套餐内都包含的,(当然这是我后来才知道的),但是在仁德这里,他们把一个套餐拆开了,把腹部B超单独抽出来作为一项,要价114元,所有项目加起来要价264元。她们也没有提醒腹部B超都包含哪些项目,就让我去体检了。当我拿到体检报告后,交给入职公司审核后,审核失败,说是腹部B超没有双肾的检查。于是,我打电话问仁德,他们说,在2021年10月双肾检查单独抽出来儿为泌尿系统B超了,我说你们没有提醒,太坑人了,我要投诉,他们说报告给上级,然后又说各家医院收费标准不一样,用这种理由搪塞。 入职公司推荐的体检机构所有检查项目加起来才121元(只是邮件接收的晚,等我做完体检了才收到邮件),我跟他们说这里便宜,他们居然笑着说“那您觉得那里便宜,您可以去那里做体检”,这卑劣的嘴脸真是让人气愤至极! 所以大家一定要认清他们的丑恶面目,把本来一套的检查拆开收费加价,良心真是让狗吃了!!! 以小见大,一个体检中心是这样,整个北京仁德医院也好不到哪去,所以,聪明人千万不要到这个黑心医院,免得耗时耗财,惹的一身騒。(上面的图片是入职公司后来发的邮件里推荐的体检中心,一共才121元)(这是北京仁德医院的外观)
MyBatis的SQL执行流程 MyBatis的SQL执行流程 SQL语句的执行才是MyBatis的重要职责,该过程就是通过封装JDBC进行操作,然后使用Java反射技术完成JavaBean对象到数据库 参数之间的相互转换,这种映射关系就是有TypeHandler对象来完成的,在获取数据表对应的元数据时,会保存该表所有列的数据库类型,大致逻辑如下所示: /* 获取结果集元数据 ResultSetMetaData对象 */ ResultSetMetaData metaData = resultSet.getMetaData(); int column = metaData.getColumnCount(); for (int i = 1; i <= column; i++) { JdbcType jdbcType = JdbcType.forCode(metaData.getColumnType(i)); typeHandlers.add(TypeHandlerRegistry.getTypeHandler(jdbcType)); columnNames.add(metaData.getColumnName(i)); } 使用如下代码进行SQL查询操作: sqlSession = sessionFactory.openSession(); User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1); System.out.println(user); 创建sqlSession的过程其实就是根据configuration中的配置来创建对应的类,然后返回创建的sqlSession对象。 调用 selectOne方法进行SQL查询,selectOne方法最后调用的是selectList,在selectList中,会查询 configuration中存储的MappedStatement对象,mapper文件中一个sql语句的配置对应一个 MappedStatement对象,然后调用执行器进行查询操作。 // DefaultSqlSession类 public T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } 执行器在query操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。 // CachingExecutor类 public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { /* 获取关联参数的sql,boundSql */ BoundSql boundSql = ms.getBoundSql(parameterObject); /* 创建cache key值 */ CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { /* 获取二级缓存实例 */ Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List list = (List) tcm.getObject(cache, key); if (list == null) { list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; /** * 先往localCache中插入一个占位对象,这个地方 */ localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } /* 往缓存中写入数据,也就是缓存查询结果 */ localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } 真正的doQuery操作是由SimplyExecutor代理来完成的,该方法中有2个子流程,一个是SQL参数的设置,另一个是SQL查询操作和结果集的封装。 // SimpleExecutor类 public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); /* 子流程1: SQL查询参数的设置 */ stmt = prepareStatement(handler, ms.getStatementLog()); /* 子流程2: SQL查询操作和结果集封装 */ return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } 子流程1 SQL查询参数的设置: 首先获取数据库connection连接,然后准备statement,然后就设置SQL查询中的参数值。打开一个connection连接,在使用完后不会close,而是存储下来,当下次需要打开连接时就直接返回。 // SimpleExecutor类 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; /* 获取Connection连接 */ Connection connection = getConnection(statementLog); /* 准备Statement */ stmt = handler.prepare(connection, transaction.getTimeout()); /* 设置SQL查询中的参数值 */ handler.parameterize(stmt); return stmt; } // DefaultParameterHandler类 public void setParameters(PreparedStatement ps) { /** * 设置SQL参数值,从ParameterMapping中读取参数值和类型,然后设置到SQL语句中 */ ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } 子流程2 SQL查询结果集的封装: // SimpleExecutor类 public List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行查询操作 ps.execute(); // 执行结果集封装 return resultSetHandler. handleResultSets(ps); } // DefaultReseltSetHandler类 public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List multipleResults = new ArrayList(); int resultSetCount = 0; /** * 获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等。 * 这些信息都存储在了ResultSetWrapper中了 */ ResultSetWrapper rsw = getFirstResultSet(stmt); List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); } ResultSetWrapper是ResultSet的包装类,调用getFirstResultSet方法获取第一个ResultSet,同时 获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等,这些信息都存储在ResultSetWrapper类中了。然后调用 handleResultSet方法来来进行结果集的封装。 // DefaultResultSetHandler类 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } } 这里调用handleRowValues方法进行结果值的设置。 // DefaultResultSetHandler类 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 封装数据 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } } private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext resultContext = new DefaultResultContext(); skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // createResultObject为新创建的对象,数据表对应的类 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { // 这里把数据填充进去,metaObject中包含了resultObject信息 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null; } return rowValue; } private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (autoMapping.size() > 0) { // 这里进行for循环调用,因为user表中总共有7列,所以也就调用7次 for (UnMappedColumnAutoMapping mapping : autoMapping) { // 这里将esultSet中查询结果转换为对应的实际类型 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value); } } } return foundValues; } mapping.typeHandler.getResult会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对 应Java中的Integer类型,然后通过调用statement.getInt("id")来获取其int值,其类型为Integer。 metaObject.setValue方法会把获取到的Integer值设置到Java类中的对应字段。 // MetaObject类 public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null && prop.getChildren() != null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value); } else { objectWrapper.set(prop, value); } } metaValue.setValue方法最后会调用到Java类中对应数据域的set方法,这样也就完成了SQL查询结果集的Java类封装过程。最后贴一张调用栈到达Java类的set方法中的快照: MyBatis缓存 MyBatis提供查询缓存,用于减轻数据库压力,提高性能。MyBatis提供了一级缓存和二级缓存。 一级缓存是SqlSession级别的缓存,每个SqlSession对象都有一个哈希表用于缓存数据,不同SqlSession对象之间缓存不共 享。同一个SqlSession对象对象执行2遍相同的SQL查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了,直接返 回缓存结果即可。MyBatis默认是开启一级缓存的。 二级缓存是mapper级别的缓存,二级缓存是跨SqlSession的,多个SqlSession对象可以共享同一个二级缓存。不同的 SqlSession对象执行两次相同的SQL语句,第一次会将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis默认是不开启 二级缓存的,可以在配置文件中使用如下配置来开启二级缓存: 当SQL语句进行更新操作(删除/添加/更新)时,会清空对应的缓存,保证缓存中存储的都是最新的数据。MyBatis的二级缓存对细粒度的数据级 别的缓存实现不友好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用 mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为 单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存,具体业务具体实现。
1 下一页