久久福利_99r_国产日韩在线视频_直接看av的网站_中文欧美日韩_久久一

您的位置:首頁技術文章
文章詳情頁

Mybatis #foreach中相同的變量名導致值覆蓋的問題解決

瀏覽:45日期:2023-10-18 13:51:16
目錄背景問題原因(簡略版)Mybatis流程源碼解析(長文警告,按需自取)一、獲取SqlSessionFactory二、獲取SqlSession三、執行SQL背景

使用Mybatis中執行如下查詢:

單元測試

@Testpublic void test1() { String resource = 'mybatis-config.xml'; InputStream inputStream = null; try {inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) {e.printStackTrace(); } SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession sqlSession = sqlSessionFactory.openSession()) {CommonMapper mapper = sqlSession.getMapper(CommonMapper.class);QueryCondition queryCondition = new QueryCondition();List<Integer> list = new ArrayList<>();list.add(1);list.add(2);list.add(3);queryCondition.setWidthList(list);System.out.println(mapper.findByCondition(queryCondition)); }}

XML

<select parameterType='cn.liupjie.pojo.QueryCondition' resultType='cn.liupjie.pojo.Test'> select * from test <where><if test='id != null'> and id = #{id,jdbcType=INTEGER}</if><if test='widthList != null and widthList.size > 0'> <foreach collection='widthList' open='and width in (' close=')' item='width' separator=','>#{width,jdbcType=INTEGER} </foreach></if><if test='width != null'> and width = #{width,jdbcType=INTEGER}</if> </where></select>

打印的SQL:DEBUG [main] - ==> Preparing: select * from test WHERE width in ( ? , ? , ? ) and width = ? DEBUG [main] - ==> Parameters: 1(Integer), 2(Integer), 3(Integer), 3(Integer)

Mybatis版本

<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version></dependency>

這是公司的老項目,在迭代的過程中遇到了此問題,以此記錄!PS: 此bug在mybatis-3.4.5版本中已經解決。并且Mybatis維護者也建議不要在item/index中使用重復的變量名。

Mybatis #foreach中相同的變量名導致值覆蓋的問題解決

Mybatis #foreach中相同的變量名導致值覆蓋的問題解決

問題原因(簡略版) 在獲取到DefaultSqlSession之后,會獲取到Mapper接口的代理類,通過調用代理類的方法來執行查詢 真正執行數據庫查詢之前,需要將可執行的SQL拼接好,此操作在DynamicSqlSource#getBoundSql方法中執行 當解析到foreach標簽時,每次循環都會緩存一個item屬性值與變量值之間的映射(如:width:1),當foreach標簽解析完成后,緩存的參數映射關系中就保留了一個(width:3) 當解析到最后一個if標簽時,由于width變量有值,因此if判斷為true,正常執行拼接,導致出錯 3.4.5版本中,在foreach標簽解析完成后,增加了兩行代碼來解決這個問題。

//foreach標簽解析完成后,從bindings中移除item context.getBindings().remove(item); context.getBindings().remove(index);Mybatis流程源碼解析(長文警告,按需自取)一、獲取SqlSessionFactory

入口,跟著build方法走

//獲取SqlSessionFactory, 解析完成后,將XML中的內容封裝到一個Configuration對象中,//使用此對象構造一個DefaultSqlSessionFactory對象,并返回SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

來到SqlSessionFactoryBuilder#build方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //獲取XMLConfigBuilder,在XMLConfigBuilder的構造方法中,會創建XPathParser對象 //在創建XPathParser對象時,會將mybatis-config.xml文件轉換成Document對象 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //調用XMLConfigBuilder#parse方法開始解析Mybatis的配置文件 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException('Error building SqlSession.', e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } }}

跟著parse方法走,來到XMLConfigBuilder#parseConfiguration方法

private void parseConfiguration(XNode root) { try { Properties settings = settingsAsPropertiess(root.evalNode('settings')); //issue #117 read properties first propertiesElement(root.evalNode('properties')); loadCustomVfs(settings); typeAliasesElement(root.evalNode('typeAliases')); pluginElement(root.evalNode('plugins')); objectFactoryElement(root.evalNode('objectFactory')); objectWrapperFactoryElement(root.evalNode('objectWrapperFactory')); reflectorFactoryElement(root.evalNode('reflectorFactory')); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode('environments')); databaseIdProviderElement(root.evalNode('databaseIdProvider')); typeHandlerElement(root.evalNode('typeHandlers')); //這里解析mapper mapperElement(root.evalNode('mappers')); } catch (Exception e) { throw new BuilderException('Error parsing SQL Mapper Configuration. Cause: ' + e, e); }}

來到mapperElement方法

//本次mappers配置:<mapper resource='xml/CommomMapper.xml'/>private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ('package'.equals(child.getName())) {String mapperPackage = child.getStringAttribute('name');configuration.addMappers(mapperPackage); } else {String resource = child.getStringAttribute('resource');String url = child.getStringAttribute('url');String mapperClass = child.getStringAttribute('class');if (resource != null && url == null && mapperClass == null) { //因此走這里,讀取xml文件,并開始解析 ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); //這里同上文創建XMLConfigBuilder對象一樣,在內部構造時,也將xml文件轉換為了一個Document對象 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //解析 mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface);} else { throw new BuilderException('A mapper element may only specify a url, resource or class, but not more than one.');} } } }}

XMLMapperBuilder類,負責解析SQL語句所在XML中的內容

//parse方法public void parse() { if (!configuration.isResourceLoaded(resource)) { //解析mapper標簽 configurationElement(parser.evalNode('/mapper')); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements();}//configurationElement方法private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute('namespace'); if (namespace == null || namespace.equals('')) { throw new BuilderException('Mapper’s namespace cannot be empty'); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode('cache-ref')); cacheElement(context.evalNode('cache')); parameterMapElement(context.evalNodes('/mapper/parameterMap')); resultMapElements(context.evalNodes('/mapper/resultMap')); sqlElement(context.evalNodes('/mapper/sql')); //解析各種類型的SQL語句:select|insert|update|delete buildStatementFromContext(context.evalNodes('select|insert|update|delete')); } catch (Exception e) { throw new BuilderException('Error parsing Mapper XML. Cause: ' + e, e); }}private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { //創建XMLStatementBuilder對象 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //解析 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}

XMLStatementBuilder負責解析單個select|insert|update|delete節點

public void parseStatementNode() { String id = context.getStringAttribute('id'); String databaseId = context.getStringAttribute('databaseId'); //判斷databaseId是否匹配,將namespace+’.’+id拼接,判斷是否已經存在此id if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute('fetchSize'); Integer timeout = context.getIntAttribute('timeout'); String parameterMap = context.getStringAttribute('parameterMap'); //獲取參數類型 String parameterType = context.getStringAttribute('parameterType'); //獲取參數類型的class對象 Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute('resultMap'); String resultType = context.getStringAttribute('resultType'); String lang = context.getStringAttribute('lang'); LanguageDriver langDriver = getLanguageDriver(lang); //獲取resultType的class對象 Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute('resultSetType'); StatementType statementType = StatementType.valueOf(context.getStringAttribute('statementType', StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); //獲取select|insert|update|delete類型 String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute('flushCache', !isSelect); boolean useCache = context.getBooleanAttribute('useCache', isSelect); boolean resultOrdered = context.getBooleanAttribute('resultOrdered', false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) //獲取SqlSource對象,langDriver為默認的XMLLanguageDriver,在new Configuration時設置 //若sql中包含元素節點或$,則返回DynamicSqlSource,否則返回RawSqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute('resultSets'); String keyProperty = context.getStringAttribute('keyProperty'); String keyColumn = context.getStringAttribute('keyColumn'); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute('useGeneratedKeys',configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}二、獲取SqlSession

由上文可知,此處的SqlSessionFactory使用的是DefaultSqlSessionFactory

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //創建執行器,默認是SimpleExecutor //如果在配置文件中開啟了緩存(默認開啟),則是CachingExecutor final Executor executor = configuration.newExecutor(tx, execType); //返回DefaultSqlSession對象 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException('Error opening session. Cause: ' + e, e); } finally { ErrorContext.instance().reset(); }}

這里獲取到了一個DefaultSqlSession對象

三、執行SQL

獲取CommonMapper的對象,這里CommonMapper是一個接口,因此是一個代理對象,代理類是MapperProxy

org.apache.ibatis.binding.MapperProxy@72cde7cc

執行Query方法,來到MapperProxy的invoke方法

@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //緩存 final MapperMethod mapperMethod = cachedMapperMethod(method); //執行操作:select|insert|update|delete return mapperMethod.execute(sqlSession, args);}

執行操作時,根據SELECT操作,以及返回值類型(反射方法獲取)確定executeForMany方法

caseSELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break;

來到executeForMany方法中,就可以看到執行查詢的操作,由于這里沒有進行分頁查詢,因此走else

if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds);} else { result = sqlSession.<E>selectList(command.getName(), param);}

來到DefaultSqlSession#selectList方法中

@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //根據key(namespace+'.'+id)來獲取MappedStatement對象 //MappedStatement對象中封裝了解析好的SQL信息 MappedStatement ms = configuration.getMappedStatement(statement); //通過CachingExecutor#query執行查詢 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(); }}

CachingExecutor#query

@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //解析SQL為可執行的SQL BoundSql boundSql = ms.getBoundSql(parameter); //獲取緩存的key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //執行查詢 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}

MappedStatement#getBoundSql

public BoundSql getBoundSql(Object parameterObject) { //解析SQL BoundSql boundSql = sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject); } //檢查是否有嵌套的ResultMap // check for nested result maps in parameter mappings (issue #30) for (ParameterMapping pm : boundSql.getParameterMappings()) { String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = configuration.getResultMap(rmId); if (rm != null) {hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql;}

由上文,此次語句由于SQL中包含元素節點,因此是DynamicSqlSource。由此來到DynamicSqlSource#getBoundSql。rootSqlNode.apply(context);這段代碼便是在執行SQL解析。

@Overridepublic BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); //執行SQL解析 rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql;}

打上斷點,跟著解析流程,來到解析foreach標簽的代碼,ForEachSqlNode#apply

@Overridepublic boolean apply(DynamicContext context) { Map<String, Object> bindings = context.getBindings(); final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings); if (!iterable.iterator().hasNext()) { return true; } boolean first = true; //解析open屬性 applyOpen(context); int i = 0; for (Object o : iterable) { DynamicContext oldContext = context; if (first) { context = new PrefixedContext(context, ''); } else if (separator != null) { context = new PrefixedContext(context, separator); } else {context = new PrefixedContext(context, ''); } int uniqueNumber = context.getUniqueNumber(); // Issue #709 //集合中的元素是Integer,走else if (o instanceof Map.Entry) { @SuppressWarnings('unchecked') Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { //使用index屬性 applyIndex(context, i, uniqueNumber); //使用item屬性 applyItem(context, o, uniqueNumber); } //當foreach中使用#號時,會將變量替換為占位符(類似__frch_width_0)(StaticTextSqlNode) //當使用$符號時,會將值直接拼接到SQL中(TextSqlNode) contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber)); if (first) { first = !((PrefixedContext) context).isPrefixApplied(); } context = oldContext; i++; } applyClose(context); return true;}private void applyItem(DynamicContext context, Object o, int i) { if (item != null) {//在參數映射中綁定item屬性值與集合值的關系//第一次:(width:1)//第二次:(width:2)//第三次:(width:3)context.bind(item, o);//在參數映射中綁定處理后的item屬性值與集合值的關系//第一次:(__frch_width_0:1)//第二次:(__frch_width_1:2)//第三次:(__frch_width_2:3)context.bind(itemizeItem(item, i), o); } }

到這里,結果就清晰了,在解析foreach標簽時,每次循環都會將item屬性值與參數集合中的值進行綁定,到最后就會保留(width:3)的映射關系,而在解析完foreach標簽后,會解析最后一個if標簽,此時在判斷if標簽是否成立時,答案是true,因此最終拼接出來一個錯誤的SQL。

在3.4.5版本中,代碼中增加了context.getBindings().remove(item);在foreach標簽解析完成后移除bindings中的參數映射。以下是源碼:

@Overridepublic boolean apply(DynamicContext context) { Map<String, Object> bindings = context.getBindings(); final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings); if (!iterable.iterator().hasNext()) { return true; } boolean first = true; applyOpen(context); int i = 0; for (Object o : iterable) { DynamicContext oldContext = context; if (first || separator == null) { context = new PrefixedContext(context, ''); } else { context = new PrefixedContext(context, separator); } int uniqueNumber = context.getUniqueNumber(); // Issue #709 if (o instanceof Map.Entry) { @SuppressWarnings('unchecked') Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { applyIndex(context, i, uniqueNumber); applyItem(context, o, uniqueNumber); } contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber)); if (first) { first = !((PrefixedContext) context).isPrefixApplied(); } context = oldContext; i++; } applyClose(context); //foreach標簽解析完成后,從bindings中移除item context.getBindings().remove(item); context.getBindings().remove(index); return true;}

到此這篇關于Mybatis #foreach中相同的變量名導致值覆蓋的問題解決的文章就介紹到這了,更多相關Mybatis #foreach相同變量名覆蓋內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Mybatis 數據庫
相關文章:
主站蜘蛛池模板: 精品视频久久久 | 一级黄色av片 | 99精品欧美一区二区三区 | 午夜电影网址 | 中文字幕丝袜 | 日本超碰| 亚洲午夜视频在线观看 | 91精品国产综合久久久久久丝袜 | 99久久精品国产一区二区三区 | www中文字幕 | 欧美大片一区二区 | 国产精品自产av一区二区三区 | 久久久精彩视频 | 一区二区三区在线播放 | 一级毛片免费在线 | 国产综合亚洲精品一区二 | 伦理午夜电影免费观看 | 二区三区 | 99成人精品 | 国产精品久久久久久久久久久不卡 | 国产精精品 | 成人二区 | 自拍视频在线 | 视频一区二区三区中文字幕 | 男女视频在线免费观看 | 久久精品日产高清版的功能介绍 | 亚洲1区2区在线 | 大香伊蕉在人线视频777 | 伊人电院网| 国产成人毛片 | 精品视频网站 | 欧美综合久久久 | 在线观看中文字幕亚洲 | 精品护士一区二区三区 | 99国产精品久久久久老师 | 国产精品久久免费视频 | 精品久久久久久久久久久院品网 | 91精品国产91久久久久久最新 | 精品视频久久 | 国产视频福利一区 | 国产精品免费av | 国产小视频网站 | 久久精品影视 | 国产一区二区三区在线免费 | 99精品国产高清一区二区麻豆 | 中文日韩在线 | 久久a毛片 | 久久综合久久久 | 一二区视频 | 国产在线a | 国产欧美综合在线 | 亚洲国产高清高潮精品美女 | 日韩在线免费视频 | 精品美女久久久 | 久草综合在线 | 午夜精品久久久久久久99黑人 | 国产91福利视频 | 呦呦在线观看 | 国产精品高潮呻吟久久av黑人 | 美女国产 | 激情开心成人网 | 在线精品一区二区 | 精品国产一区二区三区久久久 | 超碰在线天天 | 99久久久成人国产精品 | 午夜欧美| 色婷婷av久久久久久久 | 精品1区2区 | 国产精品自产拍在线观看 | 精品国产九九 | 亚洲一区二区三区在线 | 亚洲欧美综合乱码精品成人网 | 午夜激情视频在线观看 | 一本一道久久a久久精品逆3p | 日韩免费高清视频 | 国产精品久久久久久二区 | 亚洲精品福利在线观看 | 欧美日本一区二区三区 | 日韩精品在线播放 | www.一区 | 欧美成人激情 | 日韩av免费在线观看 | 成人一级视频在线观看 | √8天堂资源地址中文在线 成人欧美一区二区三区白人 | 国产中文字幕在线 | 久久久久久国产视频 | 成人精品视频 | 欧洲一级毛片 | 国产精品女教师av久久 | 一区二区三区四区精品 | 久久噜| 一级性大片 | 国产日韩一区二区 | 天天天插| 黑人巨大精品欧美一区二区免费 | 91视频电影 | 国产一区免费 | 波多野结衣av中文字幕 | 电家庭影院午夜 | 久久精品视频亚洲 | 国产综合精品一区二区三区 | 91婷婷射| 视频一区 国产精品 | 黄色国产| 成人午夜sm精品久久久久久久 | 一区二区三区精品视频 | 亚洲精品久久久久久久久久久 | 国产精久久久久久久妇剪断 | 少妇久久久 | 成人亚洲视频 | 欧美| 久久影音先锋 | 国产乱码精品一区二区三区手机版 | 天堂在线一区二区 | 91免费版在线观看 | 午夜免费一区二区播放 | 久久久精品一区 | 美女h视频 | 欧美理伦片在线播放 | 美女视频黄又黄又免费 | 日本视频一区二区三区 | 成人亚洲 | 少妇一级淫免费放 | 中文字幕黄色 | 交视频在线观看国产 | 国产乱码精品一区二区三区手机版 | 91亚洲国产成人精品性色 | 中文一区 | 午夜精品久久久久久久 | 亚洲福利av| 精品久久久久久 | 香蕉久久av一区二区三区 | 99爱在线观看 | 日韩一区二区三区在线播放 | 99热精品免费 | 91视频免费观看 | 精品久久久久久久 | 欧美理伦片在线播放 | 91免费影片 | 成人精品一区二区三区 | 在线小视频 | 一区二区三区日韩 | 国产成人免费视频网站视频社区 | 久久成人精品视频 | 欧美色图亚洲自拍 | 午夜婷婷色 | 成人日批 | 日韩一区二区在线观看 | 亚洲免费视频网站 | 国产一区二区三区四 | 日韩精品一区二区三区在线 | 亚洲在线| 国产精品高清在线 | 最新午夜| 亚洲一区二区三区爽爽爽爽爽 | 欧美一级特黄aaaaaa大片在线观看 | 婷婷色综合 | 中文字幕亚洲精品 | 国产一区二区三区在线 | 久草.com| 国产精品久久久久久久久久三级 | 免费黄在线看 | 精品一区二区三区四区五区 | 国产成人在线一区二区 | 免费激情网站 | 青青久久 | 97国产精品| 中文字幕av网站 | 婷婷丁香六月天 | 亚洲欧美国产精品久久久久 | 天天干天天谢 | av在线免费观看网站 | 久久久久在线 | www.精品 | 国产激情偷乱视频一区二区三区 | 高清国产一区二区三区 | 中文字幕在线观看www | 国产精品一区人伦免视频播放 | 日韩国产一区 | 日本久久久亚洲精品 | 91精品欧美久久久久久动漫 | 日韩1区| 日日操日日操 | 欧美日韩精品综合 | 精品国产欧美 | 在线欧美亚洲 | 国产成人精品久久 | 成人免费观看视频 | 伊人激情网 | 国产精品色婷婷亚洲综合看 | 日本亚洲视频 | 国产一级做a爰片在线看免费 | 日韩美女av在线 | 视频一区在线观看 | 午夜理伦三级 | 国产探花在线精品一区二区 | 国产精品久久久久久久美男 | 免费在线观看av | 亚洲免费观看视频 | 毛片网免费 | 日本超碰| www伊人| 亚洲天堂久久 | 日韩一区欧美 | 国产另类ts人妖一区二区 | 国产女人爽到高潮免费视频 | 在线无码 | 亚洲男人的天堂网站 | 日韩一区二区三区在线观看 | 亚洲视频区 | 欧美日韩国产一区二区在线观看 | 精品日韩 | 超碰人操 | 久久综合亚洲 | 久久av综合 | 亚洲狠狠爱| 欧美成人中文字幕 | 国产精品毛片一区二区三区 | 久久久91精品国产一区二区精品 | 亚洲国产精品久久久久久久 | 欧美日韩久久 | 日本久久久久久久 | 亚洲成av人片在线观看无码 | 日韩久久久久久久久久久 | 亚洲精品1区2区 | 毛片网在线观看 | 午夜私人影院 | 99精品久久久 | 欧美一级黄视频 | 99久久久99久久国产片鸭王 | 久久久久网站 | 久久精品国产99国产精品 | 黄色视频a级毛片 | 99爱精品在线 | 国产精品久久av | 一区二区三区影院 | 91精品一区二区 | 成年人网站在线免费看 | 97精品国产97久久久久久粉红 | 污视频在线观看免费 | 国产精品丝袜一区二区 | 国产精品xxxx | 亚洲视频在线观看免费 | 精品久久久久久久久久久 | 亚洲毛片网站 | 欧美国产综合一区 | 国产精品1区2区3区 国产在线观看一区 | jizz中国日本 | 国产精品一区二区精品 | 欧美福利| 欧美一级在线观看 | 精品国产不卡一区二区三区 | 成人免费在线观看视频 | 波多野结衣一区在线观看 | 狠狠躁夜夜躁人人爽天天高潮 | 亚洲一道本 | 日韩在线精品强乱中文字幕 | 亚洲国产精品视频 | 欧美激情视频一区二区三区 | 精品成人免费一区二区在线播放 | 午夜精品久久久久久久99黑人 | 欧美在线观看视频 | 最新天堂中文在线 | 在线免费观看毛片 | 成人一级电影在线观看 | 在线国产一区二区 | 国产精品久久久久久久久免费桃花 | 亚洲高清视频在线观看 | 欧美日韩国产精品一区二区亚洲 | 精品一区久久 | 天天夜夜操 | 亚洲三级在线看 | 国产 欧美 日韩 一区 | 色网站在线观看 | 久久久91精品国产一区二区三区 | 中文字幕在线网址 | 懂色av一区二区三区在线播放 | 骚视频在线观看 | 久久一区视频 | 自拍视频在线 | 久久综合一区二区三区 | 日韩欧美精品一区 | 亚洲精品91 | 99国产精品99久久久久久 | 中国特级黄色片 | 亚洲aaaaaa特级 | 日韩在线欧美 | 久久99精品久久久久婷婷暖91 | 伊人天堂在线 | 天天干天操 | 天天草草草 | 在线a电影 | 成人一区二区三区在线观看 | 99精品欧美一区二区三区 | 国产精品中文字幕在线 | 久久久久成人精品 | 日韩久久综合 | 永久av| 国产精品久久久久久久久久东京 | 中文字幕在线视频精品 | 高清视频一区 | 9l蝌蚪porny中文自拍 | 成人影音 | 欧美一级一 | 色欧美片视频在线观看 | 91免费在线视频 | 久久国产精品99久久久久久牛牛 | 日本精品一区二区三区视频 | 亚洲二区视频 | 伊人色播 | 中文字幕亚洲一区二区va在线 | 成人tv| 天天干人人 | 日韩精品日韩激情日韩综合 | 一级特黄aaa大片在线观看 | 国产在线观看一区 | 美女一级a毛片免费观看97 | 欧美三区 | 国产精品成人观看视频国产奇米 | 999视频在线 | 色婷婷亚洲一区二区三区 | 精品国产乱码久久久久久88av | 特黄av| 天天操天天干天天插 | 久久精品电影网 | 精品一区二区三区久久久 | 国产成人精品一区二区视频免费 | 欧美高清视频一区 | 中文在线a在线 | 日韩一区二区三区在线观看 | 九九色综合| 精品一区二区av | 欧美国产精品一区 | 亚洲九九| 国产精品久久久久久久久久东京 | 久久综合久 | 免费在线看a| 国产高清一区 | 欧洲成人在线 | 蜜桃精品久久久久久久免费影院 | av网站久久| 久久九精品 | 亚洲伊人久久综合 | 丝袜久久| 国产剧情一区二区 | 91免费观看国产 | 狠狠做深爱婷婷综合一区 | 81精品国产乱码久久久久久 | 国产精品久久国产愉拍 | 欧美高清视频在线观看 | 免费国产在线视频 | 亚洲欧洲一区 | av在线毛片 | 国产午夜精品美女视频明星a级 | 久久青青 | 国产精品一区二区三区免费 | 成人免费看片 | 99精品国产一区二区三区 | 欧美精品一区二区在线观看 | 久久久久久亚洲精品 | 久久久久久久久久久精 | 亚洲免费在线观看 | 日韩精品日韩激情日韩综合 | 日本在线观看www | 日韩国产在线观看 | 午夜精品在线观看 | 手机看片亚洲 | 男女视频在线看 | 日本a在线 | 亚洲第一视频网站 | 91精品久久久久久久久久 | 国产视频一视频二 | 国产第一页在线播放 | 99热福利 | 成人在线视频观看 | 亚洲中午字幕 | 中文字幕1区 | 日本视频二区 | 亚洲日本乱码在线观看 | 欧美日韩一级在线观看 | 91一级| 免费观看一级特黄欧美大片 | 国产福利在线 | 久久成人一区 | 久久久久国产一级毛片 | 国产成人精品一区二区三区视频 | 热久久这里只有精品 | 亚洲高清免费视频 | 中文字幕一区在线观看视频 | 国产黄视频在线 | 在线观看中文视频 | 午夜免费av | 欧美视频在线播放 | 特黄特黄视频 | 国产一二三区在线观看 | 噜噜噜在线观看免费视频日本 | 国产在线观 | 欧美在线视频一区二区 | 在线观看黄免费 | 国产精品美女 | 日韩精品免费在线观看 | 久久激情网站 | 成人不卡视频 | 亚洲视频在线看 | 欧美国产高清 | 日韩欧美一区二区在线观看视频 | 国产精品一区一区 | 久久综合久久久 | 一区二区三区免费av | 亚洲成人精品 | 国产一区二区在线视频观看 | 噜噜噜噜狠狠狠7777视频 | 精品久久久久久久久久久久久久 | 黄色免费网址大全 | 国产激情综合五月久久 | 国产在线一区二区 | 精品日本久久 | 久久视频在线 | a久久| 超碰人操 | 一区在线视频 | 最新国产精品 | 狠狠视频 | 综合一区二区三区 | 亚洲欧美中文日韩在线v日本 | 亚洲一区二区三区免费在线观看 | 激情综合色综合久久综合 | 亚洲精品一二三四五区 | 亚洲免费人成在线视频观看 | 欧美精品色网 | 成人国产 | 激情网页 | 日本免费三片免费观看 | 国产精品一区久久久久 | 欧美成人中文字幕 | 久久综合一区 | 奇米一区二区 | 1204国产成人精品视频 | 一区亚洲 | 日韩成人三级 | 国产精品18hdxxxⅹ在线 | 精品久久久久久亚洲精品 | 一区二区三区高清 | 一区二区三区免费av | 日韩视频精品在线观看 | 日韩中文字幕视频 | 日本 欧美 三级 高清 视频 | 精品一区二区三区不卡 | 亚洲国产免费 | 久久久久久91亚洲精品中文字幕 | 日日夜夜摸| 久久人爽| 国产一区 | 亚洲欧美日韩天堂 | 国产高清在线观看 | 成人一区视频 | 免费av一区二区三区 | 国产精品三级视频 | 亚洲成人久久久 | 欧美日韩在线视频一区 | 欧美黑人一级爽快片淫片高清 | 国产一区二区av | 91在线视频观看 | 最近免费中文字幕大全免费版视频 | 在线观看亚洲免费 | 国产一级免费网站 | 欧洲精品一区 | 99视频精品 | 国产婷婷在线视频 | 色婷婷av久久久久久久 | 中文字幕三区 | 久久99国产精品久久99果冻传媒 | 日一日干一干 | 四虎影院网 | 噜噜噜天天躁狠狠躁夜夜精品 | 中文字幕在线视频第一页 | av基地网 | 中文字幕av亚洲精品一部二部 | 欧美成人精品一区二区男人看 | 欧美亚洲视频在线观看 | 在线国产专区 | segui88久久综合9999 | www视频在线观看 | 欧美精品亚洲精品 | 亚洲国产视频精品 | 四虎影院网| 毛片入口 | 天堂av一区 | 中文久久 | 中文字幕在线观看 | 一二三区字幕免费观看av | 一本大道综合伊人精品热热 | 国产精品不卡视频 | 亚洲三级在线播放 | 免费观看一级特黄欧美大片 | 毛片99 | 欧美精品在线观看 | 久久91视频 | 成人免费在线视频观看 | 国产一区二区免费电影 | 国产精品一二三区 | 欧美久久成人 | 久久精品一| 欧美日本在线观看 | 国产目拍亚洲精品99久久精品 | 久久精品国产99国产 | 欧美精品一二三区 | 久久久久久av | 欧美成人精品激情在线观看 | 国产免费一区二区三区 | 国产欧美精品一区 | 国产精品久久久久久吹潮 | 中国一级大毛片 | 国产精品美女久久久久aⅴ国产馆 | 91看片网 | 国产一级片一区二区三区 | 免费观看日韩av | 电影91久久久 | 成人精品视频一区二区三区 | 久久人人爽人人爽 | 亚洲一区 中文字幕 | 国产精品久久久久久久久久久久久久 | 一区二区在线影院 | 天天拍天天操 | 99精品视频一区二区三区 | 亚洲午夜av | 欧美日韩中文 | 91亚洲国产精品 | 欧洲一区二区三区 | 国产高清精品一区 | 国产精品99久久久久久久vr | 国产精品欧美一区二区三区不卡 | 99精品网| av毛片| 亚洲高清免费 | a免费网站 | a毛片国产 | 在线一区视频 | 亚洲国产精品一区二区久久,亚洲午夜 | 久久国产精品久久久久久电车 | 露娜同人18av黄漫网站 | 特级黄一级播放 | 日本黄色片免费 | 欧美日韩一区二区不卡 | 欧美精品福利 | 久久久久国产精品免费免费搜索 | 色吊丝2288sds中文字幕 | 日韩欧美在线播放视频 | 在线观看毛片视频 | 17c一起操 | 看毛片的网站 | 精品国产青草久久久久福利 | 亚洲精品在 | 品久久久久久久久久96高清 | 亚洲成人日韩 | 国产精品久久久久久久久久久久久久 | 一区二区免费 | 精品国产三级 | 国产一区二区视频在线观看 | 日韩精品一二三区 | 午夜精品一区二区三区在线视频 | 成av在线| 91久色 | 欧美国产综合 | 国产一区二区三区免费视频 | 日韩福利在线观看 | 免费特级黄毛片 | 中文字幕在线免费观看 | 国产精品一区三区 | 波多野结衣一区二区三区高清 | 国产精品视频久久久 | 中文字幕av一区二区三区 | 亚洲视频免费在线观看 | 亚洲男人的天堂在线播放 | 在线观看www| 亚洲精品一区二区网址 | 日韩在线一区二区三区 | 99福利视频 | 久久不卡 | 一级片欧美 | 一色桃子av一区二区免费 | 国变精品美女久久久久av爽 | 国产日韩欧美高清 | 中文字幕7777 | 91中文在线观看 | 国产在线观看一区 | 国产激情一区二区三区 | a级黄色毛片免费观看 | 大胆裸体gogo毛片免费看 | 欧美成亚洲 | 精品久久久久久久久久久 | 亚洲欧美成人影院 | 黄色一级毛片 | 91高清视频| 亚洲免费观看 | 亚洲精品日韩综合观看成人91 | 欧美激情a∨在线视频播放 成人免费共享视频 | 欧美日韩亚洲一区 | 综合中文字幕 | 在线a电影 | 亚洲一级视频在线 | 日本在线观看视频网站 | 午夜爽爽爽 | 国产精品毛片一区二区在线看 | 国产亚洲视频在线 | 99免费视频 | 日韩一区二区不卡 | 99精品免费观看 | 91网站在线看 | 久久久精品456亚洲影院 | 日韩成人精品在线 | 伊人激情综合网 | 国产精品久久久久毛片软件 | 国产一区在线不卡 | 91视频日韩|