日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

Mybatis #foreach中相同的變量名導(dǎo)致值覆蓋的問題解決

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

使用Mybatis中執(zhí)行如下查詢:

單元測試

@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>

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

Mybatis #foreach中相同的變量名導(dǎo)致值覆蓋的問題解決

Mybatis #foreach中相同的變量名導(dǎo)致值覆蓋的問題解決

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

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

入口,跟著build方法走

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

來到SqlSessionFactoryBuilder#build方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //獲取XMLConfigBuilder,在XMLConfigBuilder的構(gòu)造方法中,會創(chuàng)建XPathParser對象 //在創(chuàng)建XPathParser對象時,會將mybatis-config.xml文件轉(zhuǎn)換成Document對象 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //調(diào)用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); //這里同上文創(chuàng)建XMLConfigBuilder對象一樣,在內(nèi)部構(gòu)造時,也將xml文件轉(zhuǎn)換為了一個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類,負(fù)責(zé)解析SQL語句所在XML中的內(nèi)容

//parse方法public void parse() { if (!configuration.isResourceLoaded(resource)) { //解析mapper標(biāo)簽 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) { //創(chuàng)建XMLStatementBuilder對象 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //解析 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } }}

XMLStatementBuilder負(fù)責(zé)解析單個select|insert|update|delete節(jié)點(diǎn)

public void parseStatementNode() { String id = context.getStringAttribute('id'); String databaseId = context.getStringAttribute('databaseId'); //判斷databaseId是否匹配,將namespace+’.’+id拼接,判斷是否已經(jīng)存在此id if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute('fetchSize'); Integer timeout = context.getIntAttribute('timeout'); String parameterMap = context.getStringAttribute('parameterMap'); //獲取參數(shù)類型 String parameterType = context.getStringAttribute('parameterType'); //獲取參數(shù)類型的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為默認(rèn)的XMLLanguageDriver,在new Configuration時設(shè)置 //若sql中包含元素節(jié)點(diǎn)或$,則返回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); //創(chuàng)建執(zhí)行器,默認(rèn)是SimpleExecutor //如果在配置文件中開啟了緩存(默認(rèn)開啟),則是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對象

三、執(zhí)行SQL

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

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

執(zhí)行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); //執(zhí)行操作:select|insert|update|delete return mapperMethod.execute(sqlSession, args);}

執(zhí)行操作時,根據(jù)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方法中,就可以看到執(zhí)行查詢的操作,由于這里沒有進(jìn)行分頁查詢,因此走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 { //根據(jù)key(namespace+'.'+id)來獲取MappedStatement對象 //MappedStatement對象中封裝了解析好的SQL信息 MappedStatement ms = configuration.getMappedStatement(statement); //通過CachingExecutor#query執(zhí)行查詢 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為可執(zhí)行的SQL BoundSql boundSql = ms.getBoundSql(parameter); //獲取緩存的key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //執(zhí)行查詢 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中包含元素節(jié)點(diǎn),因此是DynamicSqlSource。由此來到DynamicSqlSource#getBoundSql。rootSqlNode.apply(context);這段代碼便是在執(zhí)行SQL解析。

@Overridepublic BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); //執(zhí)行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;}

打上斷點(diǎn),跟著解析流程,來到解析foreach標(biāo)簽的代碼,F(xiàn)orEachSqlNode#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); } //當(dāng)foreach中使用#號時,會將變量替換為占位符(類似__frch_width_0)(StaticTextSqlNode) //當(dāng)使用$符號時,會將值直接拼接到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) {//在參數(shù)映射中綁定item屬性值與集合值的關(guān)系//第一次:(width:1)//第二次:(width:2)//第三次:(width:3)context.bind(item, o);//在參數(shù)映射中綁定處理后的item屬性值與集合值的關(guān)系//第一次:(__frch_width_0:1)//第二次:(__frch_width_1:2)//第三次:(__frch_width_2:3)context.bind(itemizeItem(item, i), o); } }

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

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

@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標(biāo)簽解析完成后,從bindings中移除item context.getBindings().remove(item); context.getBindings().remove(index); return true;}

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

標(biāo)簽: Mybatis 數(shù)據(jù)庫
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
久久av导航| 日韩综合小视频| 国产精品入口久久| 日本成人中文字幕在线视频| 亚久久调教视频| 亚洲精品系列| 日韩av午夜在线观看| 日韩av一区二| 国产精品久久国产愉拍| 精品美女在线视频| 日韩国产激情| 欧美中文一区二区| 亚洲一区欧美激情| 亚洲精品激情| 97成人超碰| 国产欧美啪啪| 极品av在线| 狠狠色狠狠色综合日日tαg| 日韩视频在线一区二区三区| 丝袜美腿亚洲一区二区图片| 亚洲精品三级| 国产精品亚洲欧美一级在线| 精品一区91| 亚洲国产福利| 亚洲精品午夜av福利久久蜜桃| 伊人久久婷婷| 日本亚洲三级在线| 久久精品人人| 亚洲先锋成人| 日韩精品一区二区三区中文| 国产精品欧美在线观看| 国产中文欧美日韩在线| 久久久蜜桃一区二区人| 99视频精品免费观看| 日韩精品a在线观看91| 麻豆精品蜜桃视频网站| 日韩精品欧美激情一区二区| 亚洲主播在线| 国产精品美女在线观看直播| 欧美亚洲日本精品| 中文一区一区三区免费在线观 | 欧美日韩尤物久久| 首页国产欧美日韩丝袜| 久久超级碰碰| 精品在线91| 国产精品亚洲综合久久| 99久久视频| 亚洲欧美久久精品| 久久久国产精品网站| 久久婷婷av| 日韩av一二三| 亚洲一本视频| 国产麻豆精品久久| 久久精品观看| 青草综合视频| 99tv成人| 国产精品男女| 欧美日韩国产精品一区二区亚洲| 国产乱子精品一区二区在线观看| 一区二区三区四区在线看| 欧美影院视频| 亚洲韩日在线| 国产精品久久久一区二区| 日本在线精品| 91亚洲精品在看在线观看高清| 久久精品欧美一区| 国产精品免费不| 免费中文字幕日韩欧美| 国产一区二区三区亚洲综合| 日韩中文字幕91| 麻豆mv在线观看| 日本欧美在线| 精品在线91| 国精品产品一区| 日韩精品免费观看视频| 久久影视一区| 久久三级中文| 亚洲日本三级| sm捆绑调教国产免费网站在线观看| 亚州欧美在线| 欧美日韩国产免费观看| 国产精品高颜值在线观看| 日本免费新一区视频| 欧美日韩国产精品一区二区亚洲| 91亚洲国产| 国产精品jk白丝蜜臀av小说| 日韩专区在线视频| 免费毛片在线不卡| 国产自产自拍视频在线观看| 久久精品97| 一区二区日韩免费看| 欧美成人日韩| 水蜜桃精品av一区二区| 国产免费播放一区二区| 免费观看在线综合| 91成人精品| 日本久久成人网| 三上亚洲一区二区| 麻豆成人av在线| 国产欧美日韩综合一区在线播放| 久久午夜精品| 欧美日韩精品免费观看视频完整| 不卡福利视频| 国产成人精品999在线观看| 91精品福利观看| 免费在线看一区| 日韩视频一区| 国产综合激情| 日韩三区免费| 在线天堂中文资源最新版| 国产精品色婷婷在线观看| 欧美欧美黄在线二区| 婷婷亚洲精品| 一区二区三区网站| 日韩中文字幕麻豆| 日韩中文字幕91| 免费成人在线观看| 手机精品视频在线观看| aa国产精品| 亚洲主播在线| 爽爽淫人综合网网站| 快she精品国产999| 免费观看在线综合色| av不卡在线看| 麻豆9191精品国产| 日韩在线卡一卡二| 亚洲精品美女91| 日韩视频一二区| 91欧美极品| 欧美国产专区| 精品久久一区| 国产v日韩v欧美v| 啪啪国产精品| 狠狠爱成人网| 亚洲视频二区| 欧美精品福利| 精品国产一区二区三区噜噜噜| 精品国产三区在线| 国产成人免费精品| 日韩欧美自拍| 免费视频亚洲| 免费在线看一区| 日韩一区精品| 国产精品亚洲二区| 欧美xxxx中国| 久久高清精品| 丝袜亚洲精品中文字幕一区| 亚洲欧洲av| 国产精品2区| 中文在线а√天堂| 免费av一区| 在线一区二区三区视频| 欧美日本一区| 国产一区三区在线播放| 99久久久久久中文字幕一区| 国产精品普通话对白| 日本va欧美va精品发布| 精品国产欧美| 女人天堂亚洲aⅴ在线观看| 蜜桃一区二区三区在线观看| 97久久亚洲| 日本欧美国产| 日韩午夜精品| 日韩激情一区二区| 精品一区二区三区亚洲| 亚洲a在线视频| 亚洲人成毛片在线播放女女| 免费在线日韩av| 久久精选视频| 日韩av一区二区三区四区| 久久久久久网| 蘑菇福利视频一区播放| 久久国产尿小便嘘嘘| 狠狠躁少妇一区二区三区| 在线午夜精品| 欧美激情一区| 狠狠久久婷婷| 国产精品毛片视频| 免费国产自久久久久三四区久久| 日韩精品成人在线观看| 捆绑调教日本一区二区三区| 视频一区视频二区中文字幕| 欧美激情综合| 黄色日韩精品| 美女视频一区在线观看| 国产综合欧美| 国产精品一区高清| 午夜久久美女| 久久精品国产99国产| 亚洲尤物在线| 高潮久久久久久久久久久久久久| 欧美中文日韩| 91欧美在线| 日韩精品国产精品| 激情五月综合| 成人亚洲一区| 91精品视频一区二区| 尤物在线精品| 国语对白精品一区二区| 日韩中文字幕|