亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發(fā) > Java > 正文

深入了解MyBatis參數(shù)

2024-07-14 08:43:21
字體:
來源:轉載
供稿:網(wǎng)友

深入了解MyBatis參數(shù)

相信很多人可能都遇到過下面這些異常:

  • "Parameter 'xxx' not found. Available parameters are [...]"
  • "Could not get property 'xxx' from xxxClass. Cause:
  • "The expression 'xxx' evaluated to a null value."
  • "Error evaluating expression 'xxx'. Return value (xxxxx) was not iterable."

不只是上面提到的這幾個,我認為有很多的錯誤都產(chǎn)生在和參數(shù)有關的地方。

想要避免參數(shù)引起的錯誤,我們需要深入了解參數(shù)。

想了解參數(shù),我們首先看MyBatis處理參數(shù)和使用參數(shù)的全部過程。

本篇由于為了便于理解和深入,使用了大量的源碼,因此篇幅較長,需要一定的耐心看完,本文一定會對你起到很大的幫助。

參數(shù)處理過程

處理接口形式的入?yún)?/strong>

在使用MyBatis時,有兩種使用方法。一種是使用的接口形式,另一種是通過SqlSession調用命名空間。這兩種方式在傳遞參數(shù)時是不一樣的,命名空間的方式更直接,但是多個參數(shù)時需要我們自己創(chuàng)建Map作為入?yún)ⅰO啾榷?,使用接口形式更簡單?/p>

接口形式的參數(shù)是由MyBatis自己處理的。如果使用接口調用,入?yún)⑿枰?jīng)過額外的步驟處理入?yún)?,之后就和命名空間方式一樣了。

在MapperMethod.java會首先經(jīng)過下面方法來轉換參數(shù):

public Object convertArgsToSqlCommandParam(Object[] args) { final int paramCount = params.size(); if (args == null || paramCount == 0) {  return null; } else if (!hasNamedParameters && paramCount == 1) {  return args[params.keySet().iterator().next()]; } else {  final Map<String, Object> param = new ParamMap<Object>();  int i = 0;  for (Map.Entry<Integer, String> entry : params.entrySet()) {   param.put(entry.getValue(), args[entry.getKey()]);   // issue #71, add param names as param1, param2...but ensure backward compatibility   final String genericParamName = "param" + String.valueOf(i + 1);   if (!param.containsKey(genericParamName)) {    param.put(genericParamName, args[entry.getKey()]);   }   i++;  }  return param; }}

在這里有個很關鍵的params,這個參數(shù)類型為Map<Integer, String>,他會根據(jù)接口方法按順序記錄下接口參數(shù)的定義的名字,如果使用@Param指定了名字,就會記錄這個名字,如果沒有記錄,那么就會使用它的序號作為名字。

例如有如下接口:

List<User> select(@Param('sex')String sex,Integer age);

那么他對應的params如下:

{  0:'sex',  1:'1'}

繼續(xù)看上面的convertArgsToSqlCommandParam方法,這里簡要說明3種情況:

  • 入?yún)閚ull或沒有時,參數(shù)轉換為null
  • 沒有使用@Param注解并且只有一個參數(shù)時,返回這一個參數(shù)
  • 使用了@Param注解或有多個參數(shù)時,將參數(shù)轉換為Map1類型,并且還根據(jù)參數(shù)順序存儲了key為param1,param2的參數(shù)。

注意:從第3種情況來看,建議各位有多個入?yún)⒌臅r候通過@Param指定參數(shù)名,方便后面(動態(tài)sql)的使用。

經(jīng)過上面方法的處理后,在MapperMethod中會繼續(xù)往下調用命名空間方式的方法:

Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.<E>selectList(command.getName(), param);

從這之后開始按照統(tǒng)一的方式繼續(xù)處理入?yún)ⅰ?/p>

處理集合

不管是selectOne還是selectMap方法,歸根結底都是通過selectList進行查詢的,不管是delete還是insert方法,都是通過update方法操作的。在selectList和update中所有參數(shù)的都進行了統(tǒng)一的處理。

在DefaultSqlSession.java中的wrapCollection方法:

private Object wrapCollection(final Object object) { if (object instanceof Collection) {  StrictMap<Object> map = new StrictMap<Object>();  map.put("collection", object);  if (object instanceof List) {   map.put("list", object);  }  return map;    } else if (object != null && object.getClass().isArray()) {  StrictMap<Object> map = new StrictMap<Object>();  map.put("array", object);  return map; } return object;}

這里特別需要注意的一個地方是map.put("collection", object),這個設計是為了支持Set類型,需要等到MyBatis 3.3.0版本才能使用。

wrapCollection處理的是只有一個參數(shù)時,集合和數(shù)組的類型轉換成Map2類型,并且有默認的Key,從這里你能大概看到為什么<foreach>中默認情況下寫的array和list(Map類型沒有默認值map)。

參數(shù)的使用

參數(shù)的使用分為兩部分:

  • 第一種就是常見#{username}或者${username}。
  • 第二種就是在動態(tài)SQL中作為條件,例如<if test="username!=null and username !=''">。

下面對這兩種進行詳細講解,為了方便理解,先講解第二種情況。

在動態(tài)SQL條件中使用參數(shù)

關于動態(tài)SQL的基礎內容可以查看官方文檔。

動態(tài)SQL為什么會處理參數(shù)呢?

主要是因為動態(tài)SQL中的<if>,<bind>,<foreache>都會用到表達式,表達式中會用到屬性名,屬性名對應的屬性值如何獲取呢?獲取方式就在這關鍵的一步。不知道多少人遇到Could not get property xxx from xxxClass或: Parameter ‘xxx' not found. Available parameters are[…],都是不懂這里引起的。

在DynamicContext.java中,從構造方法看起:

public DynamicContext(Configuration configuration, Object parameterObject) { if (parameterObject != null && !(parameterObject instanceof Map)) {  MetaObject metaObject = configuration.newMetaObject(parameterObject);  bindings = new ContextMap(metaObject); } else {  bindings = new ContextMap(null); } bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());}

這里的Object parameterObject就是我們經(jīng)過前面兩步處理后的參數(shù)。這個參數(shù)經(jīng)過前面兩步處理后,到這里的時候,他只有下面三種情況:

  • null,如果沒有入?yún)⒒蛘呷雲(yún)⑹莕ull,到這里也是null。
  • Map類型,除了null之外,前面兩步主要是封裝成Map類型。
  • 數(shù)組、集合和Map以外的Object類型,可以是基本類型或者實體類。

看上面構造方法,如果參數(shù)是1,2情況時,執(zhí)行代碼bindings = new ContextMap(null);參數(shù)是3情況時執(zhí)行if中的代碼。我們看看ContextMap類,這是一個內部靜態(tài)類,代碼如下:

static class ContextMap extends HashMap<String, Object> { private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) {  this.parameterMetaObject = parameterMetaObject; } public Object get(Object key) {  String strKey = (String) key;  if (super.containsKey(strKey)) {   return super.get(strKey);  }  if (parameterMetaObject != null) {   // issue #61 do not modify the context when reading   return parameterMetaObject.getValue(strKey);  }  return null; }}

我們先繼續(xù)看DynamicContext的構造方法,在if/else之后還有兩行:

bindings.put(PARAMETER_OBJECT_KEY, parameterObject);bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());

其中兩個Key分別為:

public static final String PARAMETER_OBJECT_KEY = "_parameter";public static final String DATABASE_ID_KEY = "_databaseId";

也就是說1,2兩種情況的時候,參數(shù)值只存在于"_parameter"的鍵值中。3情況的時候,參數(shù)值存在于"_parameter"的鍵值中,也存在于bindings本身。

當動態(tài)SQL取值的時候會通過OGNL從bindings中獲取值。MyBatis在OGNL中注冊了ContextMap:

static { OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());}

當從ContextMap取值的時候,會執(zhí)行ContextAccessor中的如下方法:

@Overridepublic Object getProperty(Map context, Object target, Object name)  throws OgnlException { Map map = (Map) target; Object result = map.get(name); if (map.containsKey(name) || result != null) {  return result; } Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) {  return ((Map)parameterObject).get(name); } return null;}

參數(shù)中的target就是ContextMap類型的,所以可以直接強轉為Map類型。 

參數(shù)中的name就是我們寫在動態(tài)SQL中的屬性名。

下面舉例說明這三種情況:

null的時候: 

不管name是什么(name="_databaseId"除外,可能會有值),此時Object result = map.get(name);得到的result=null。 
在Object parameterObject = map.get(PARAMETER_OBJECT_KEY);中parameterObject=null,因此最后返回的結果是null。 
在這種情況下,不管寫什么樣的屬性,值都會是null,并且不管屬性是否存在,都不會出錯。

Map類型: 

此時Object result = map.get(name);一般也不會有值,因為參數(shù)值只存在于"_parameter"的鍵值中。 
然后到Object parameterObject = map.get(PARAMETER_OBJECT_KEY);,此時獲取到我們的參數(shù)值。 
在從參數(shù)值((Map)parameterObject).get(name)根據(jù)name來獲取屬性值。 
在這一步的時候,如果name屬性不存在,就會報錯:

throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());

name屬性是什么呢,有什么可選值呢?這就是處理接口形式的入?yún)⒑吞幚砑咸幚砗笏鶕碛械腒ey。 
如果你遇到過類似異常,相信看到這兒就明白原因了。

數(shù)組、集合和Map以外的Object類型: 

這種類型經(jīng)過了下面的處理:

MetaObject metaObject = configuration.newMetaObject(parameterObject);bindings = new ContextMap(metaObject);

MetaObject是MyBatis的一個反射類,可以很方便的通過getValue方法獲取對象的各種屬性(支持集合數(shù)組和Map,可以多級屬性點.訪問,如user.username,user.roles[1].rolename)。 現(xiàn)在分析這種情況。 

首先通過name獲取屬性時Object result = map.get(name);,根據(jù)上面ContextMap類中的get方法:

public Object get(Object key) {String strKey = (String) key;if (super.containsKey(strKey)) { return super.get(strKey);}if (parameterMetaObject != null) { return parameterMetaObject.getValue(strKey);}return null;}

可以看到這里會優(yōu)先從Map中取該屬性的值,如果不存在,那么一定會執(zhí)行到下面這行代碼:

return parameterMetaObject.getValue(strKey)

如果name剛好是對象的一個屬性值,那么通過MetaObject反射可以獲取該屬性值。如果該對象不包含name屬性的值,就會報錯:

throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ".  Cause: " + t.toString(), t);

理解這三種情況后,使用動態(tài)SQL應該不會有參數(shù)名方面的問題了。

在SQL語句中使用參數(shù)

SQL中的兩種形式#{username}或者${username},雖然看著差不多,但是實際處理過程差別很大,而且很容易出現(xiàn)莫名其妙的錯誤。

${username}的使用方式為OGNL方式獲取值,和上面的動態(tài)SQL一樣,這里先說這種情況。

${propertyName}參數(shù)

在TextSqlNode.java中有一個內部的靜態(tài)類BindingTokenParser,現(xiàn)在只看其中的handleToken方法:

@Overridepublic String handleToken(String content) { Object parameter = context.getBindings().get("_parameter"); if (parameter == null) {  context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {  context.getBindings().put("value", parameter); } Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" checkInjection(srtValue); return srtValue;}

從put("value"這個地方可以看出來,MyBatis會創(chuàng)建一個默認為"value"的值,也就是說,在xml中的SQL中可以直接使用${value},從else if可以看出來,只有是簡單類型的時候,才會有值。

關于這點,舉個簡單例子,如果接口為List<User> selectOrderby(String column),如果xml內容為:

<select id="selectOrderby" resultType="User">select * from user order by ${value}</select>

這種情況下,雖然沒有指定一個value屬性,但是MyBatis會自動把參數(shù)column賦值進去。

再往下的代碼:

Object value = OgnlCache.getValue(content, context.getBindings());String srtValue = (value == null ? "" : String.valueOf(value));

這里和動態(tài)SQL就一樣了,通過OGNL方式來獲取值。

看到這里使用OGNL這種方式時,你有沒有別的想法?

特殊用法:你是否在SQL查詢中使用過某些固定的碼值?一旦碼值改變的時候需要改動很多地方,但是你又不想把碼值作為參數(shù)傳進來,怎么解決呢?你可能已經(jīng)明白了。 

就是通過OGNL的方式,例如有如下一個碼值類:

package com.abel533.mybatis;public interface Code{  public static final String ENABLE = "1";  public static final String DISABLE = "0";}

如果在xml,可以這么使用:

<select id="selectUser" resultType="User">  select * from user where enable = ${@com.abel533.mybatis.Code@ENABLE}</select>

除了碼值之外,你可以使用OGNL支持的各種方法,如調用靜態(tài)方法。

#{propertyName}參數(shù)

這種方式比較簡單,復雜屬性的時候使用的MyBatis的MetaObject。

在DefaultParameterHandler.java中:

public void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> 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();    }    typeHandler.setParameter(ps, i + 1, value, jdbcType);   }  } }}

上面這段代碼就是從參數(shù)中取#{propertyName}值的方法,這段代碼的主要邏輯就是if/else判斷的地方,單獨拿出來分析:

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);}
  • 首先看第一個if,當使用<foreach>的時候,MyBatis會自動生成額外的動態(tài)參數(shù),如果propertyName是動態(tài)參數(shù),就會從動態(tài)參數(shù)中取值。
  • 第二個if,如果參數(shù)是null,不管屬性名是什么,都會返回null。
  • 第三個if,如果參數(shù)是一個簡單類型,或者是一個注冊了typeHandler的對象類型,就會直接使用該參數(shù)作為返回值,和屬性名無關。
  • 最后一個else,這種情況下是復雜對象或者Map類型,通過反射方便的取值。

下面我們說明上面四種情況下的參數(shù)名注意事項。

動態(tài)參數(shù),這里的參數(shù)名和值都由MyBatis動態(tài)生成的,因此我們沒法直接接觸,也不需要管這兒的命名。但是我們可以了解一下這兒的命名規(guī)則,當以后錯誤信息看到的時候,我們可以確定出錯的地方。 
在ForEachSqlNode.java中:

private static String itemizeItem(String item, int i) {return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString();}

其中ITEM_PRFIX為public static final String ITEM_PREFIX = "__frch_";。 
如果在<foreach>中的collection="userList" item="user",那么對userList循環(huán)產(chǎn)生的動態(tài)參數(shù)名就是:

__frch_user_0,__frch_user_1,__frch_user_2…

如果訪問動態(tài)參數(shù)的屬性,如user.username會被處理成__frch_user_0.username,這種參數(shù)值的處理過程在更早之前解析SQL的時候就已經(jīng)獲取了對應的參數(shù)值。具體內容看下面有關<foreach>的詳細內容。

參數(shù)為null,由于這里的判斷和參數(shù)名無關,因此入?yún)ull的時候,在xml中寫的#{name}不管name寫什么,都不會出錯,值都是null。

可以直接使用typeHandler處理的類型。最常見的就是基本類型,例如有這樣一個接口方法User selectById(@Param("id")Integer id),在xml中使用id的時候,我們可以隨便使用屬性名,不管用什么樣的屬性名,值都是id。

復雜對象或者Map類型一般都是我們需要注意的地方,這種情況下,就必須保證入?yún)@些屬性,如果沒有就會報錯。這一點和可以參考上面有關MetaObject的地方。

<foreach>詳解

所有動態(tài)SQL類型中,<foreach>似乎是遇到問題最多的一個。

例如有下面的方法:

<insert id="insertUserList"> INSERT INTO user(username,password) VALUES <foreach collection="userList" item="user" separator=",">  (#{user.username},#{user.password}) </foreach></insert>

對應的接口:

int insertUserList(@Param("userList")List<User> list);

我們通過foreach源碼,看看MyBatis如何處理上面這個例子。

在ForEachSqlNode.java中的apply方法中的前兩行:

Map<String, Object> bindings = context.getBindings();final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);

這里的bindings參數(shù)熟悉嗎?上面提到過很多。經(jīng)過一系列的參數(shù)處理后,這兒的bindings如下:

{ "_parameter":{  "param1":list,  "userList":list }, "_databaseId":null,}

collectionExpression就是collection="userList"的值userList。

我們看看evaluator.evaluateIterable如何處理這個參數(shù),在ExpressionEvaluator.java中的evaluateIterable方法:

public Iterable<?> evaluateIterable(String expression, Object parameterObject) {  Object value = OgnlCache.getValue(expression, parameterObject);  if (value == null) {   throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");  }  if (value instanceof Iterable) {   return (Iterable<?>) value;  }  if (value.getClass().isArray()) {    int size = Array.getLength(value);    List<Object> answer = new ArrayList<Object>();    for (int i = 0; i < size; i++) {      Object o = Array.get(value, i);      answer.add(o);    }    return answer;  }  if (value instanceof Map) {   return ((Map) value).entrySet();  }  throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable.");}

首先通過看第一行代碼:

Object value = OgnlCache.getValue(expression, parameterObject);

這里通過OGNL獲取到了userList的值。獲取userList值的時候可能出現(xiàn)異常,具體可以參考上面動態(tài)SQL部分的內容。

userList的值分四種情況。

  • value == null,這種情況直接拋出異常BuilderException。
  • value instanceof Iterable,實現(xiàn)Iterable接口的直接返回,如Collection的所有子類,通常是List。
  • value.getClass().isArray()數(shù)組的情況,這種情況會轉換為List返回。
  • value instanceof Map如果是Map,通過((Map) value).entrySet()返回一個Set類型的參數(shù)。

通過上面處理后,返回的值,是一個Iterable類型的值,這個值可以使用for (Object o : iterable)這種形式循環(huán)。

在ForEachSqlNode中對iterable循環(huán)的時候,有一段需要關注的代碼:

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);}

如果是通過((Map) value).entrySet()返回的Set,那么循環(huán)取得的子元素都是Map.Entry類型,這個時候會將mapEntry.getKey()存儲到index中,mapEntry.getValue()存儲到item中。

如果是List,那么會將序號i存到index中,mapEntry.getValue()存儲到item中。

<foreach>常見錯誤補充

當collection="userList"的值userList中的User是一個繼承自Map的類型時,你需要保證<foreach>循環(huán)中用到的所有對象的屬性必須存在,Map類型存在的問題通常是,如果某個值是null,一般是不存在相應的key,這種情況會導致<foreach>出錯,會報找不到__frch_user_x參數(shù)。所以這種情況下,就是值是null,你也需要map.put(key,null)。

最后

這篇文章很長,寫這篇文章耗費的時間也很長,超過10小時,寫到半夜兩點都沒寫完。

這篇文章真的非常有用,如果你對Mybatis有一定的了解,這篇文章幾乎是必讀的一篇。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對VeVb武林網(wǎng)的支持。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
国产精品露脸av在线| 色妞ww精品视频7777| 午夜精品久久久久久久久久久| 999在线免费观看视频| 国产乱码在线| 菠萝菠萝蜜在线观看| 亚洲一区二区三区精品在线| 在线电影福利片| 懂色av一区二区在线播放| 在线亚洲伦理| 欧美高清一级大片| 欧美专区在线视频| 国产精品一区二区av日韩在线| 日韩精品最新在线观看| 亚洲另类一区二区| 成人在线直播| 欧美一区免费| 亚洲成人午夜影院| 二人午夜免费观看在线视频| 可以免费观看av的网站| 99热这里只有精品在线| 欧美综合激情| 欧美性受xxxx白人性爽| 日韩av中文字幕一区| 嫩草影院2018| 99re6热只有精品免费观看| 二级片在线观看| 日韩成人动漫在线观看| 在线亚洲观看| 国产日韩在线一区二区三区| 成人h猎奇视频网站| 国内揄拍国内精品久久| 白嫩白嫩国产精品| 精品久久无码中文字幕| 九九热精品视频在线观看| 不卡的一区二区| 亚洲网色网站| 91中文在线视频| 精品日韩一区二区三区免费视频| 看黄色免费网站| 一区二区三区四区不卡视频| 一级少妇精品久久久久久久| 97成人免费视频| 日本a级片免费观看| 亚洲成年人在线播放| 菠萝蜜一区二区| 性爱在线免费视频| 2019国内自拍| 欧美日韩高清一区二区| 国产xxx在线| 久久精品免费播放| 欧美尺度大的性做爰视频| 天天操天天艹| 99精品欧美一区二区三区综合在线| 在线观看视频中文字幕| 在线视频一区二区免费| 91九色porn在线资源| 亚洲一区二区三区三州| 久久草视频在线| 久久精品一区二区三区四区| 91精品国产综合久久香蕉的特点| 午夜激情成人网| 在线成人中文字幕| 成人黄色片视频| 色www亚洲国产阿娇yao| 亚洲国产精品高清| 法国空姐在线观看免费| 韩国精品福利一区二区三区| 肉肉av福利一精品导航| 中文在线最新版地址| 精品91自产拍在线观看一区| 4444亚洲人成无码网在线观看| 日韩精品视频一区二区在线观看| av成人动漫在线观看| 日本免费不卡一区二区| аⅴ资源新版在线天堂| 色综合天天综合色综合av| 久久影视电视剧免费网站| 国产精品乱码人人做人人爱| 亚洲精彩视频| 国产一级影片| 好看的日韩精品视频在线| 欧美色图片区| 日韩中文欧美在线| 欧美老女人性视频| 人善交vide欧美| 亚洲综合色区另类av| 在线观看免费视频一区二区三区| 天天综合天天综合色| 日日噜噜噜夜夜爽爽狠狠视频| 国产一区二区片| 黄色正能量网站| 国产 日韩 欧美 综合 一区| 国产精品v欧美精品v日本精品动漫| 成人激情动漫在线观看| 日本国产在线播放| 成人高清av在线| 黑吊大战白xxxxxx| 天码人妻一区二区三区在线看| 另类国产精品一区二区| xxx在线播放| 欧美性极品xxxx娇小| 国产成人自拍偷拍| 日韩精品亚洲一区二区三区免费| 国产精品www在线观看| 国产成人精品电影久久久| 91免费在线播放视频| 亚洲欧美日本国产有色| 极品白浆推特女神在线观看| 欧美日韩国产综合一区二区三区| 日韩欧美中文一区二区| 国产精品高潮呻吟久久久久| 中文字幕少妇一区二区三区| 午夜老司机在线观看| 欧美做受高潮6| 中文字幕 自拍| 一区二区成人| 国产午夜视频在线播放| 亚洲欧美日韩精品久久久| 性生交大片免费看l| 精品国产一区二区三区| av在线网站免费观看| 久久精品国产av一区二区三区| 久久资源亚洲| 亚洲免费人成在线视频观看| 国产欧美一区二区在线播放| www.涩涩涩| 欧美成人午夜精品免费| 寂寞少妇一区二区三区| 97中文在线观看| 一二三四社区欧美黄| 日本超碰在线观看| 久久只有精品| 欧美精品a∨在线观看不卡| 青青影院在线观看| 国产成人精品久久亚洲高清不卡| 亚洲一区二区三区四区中文| 草草在线视频| 三级黄色的网站| 白白色亚洲国产精品| 日本不卡免费播放| 深爱激情五月婷婷| 在线观看国产日韩| 亚洲毛片一区二区| 美女精品国产| 日本一区二区三区电影| 人妻精品久久久久中文| 欧美日韩导航| 欧美男男激情videos| 被男人吃奶添下面好舒服动态图| 国产成人亚洲综合青青| 亚洲资源一区| 欧美午夜电影在线播放| 日韩有码在线播放| 在线观看国产v片| 国产女主播福利| 国产小视频精品| jizzjizz丝袜老师| 激情中国色综合| 成人免费在线视频网站| 欧美激情一级二级三级在线视频| 97在线观看免费高清视频| jizz内谢中国亚洲jizz| 亚洲色图欧美制服丝袜另类第一页| 好男人官网在线观看| 亚洲欧美电影一区二区| 久久机这里只有精品| 欧美大片va欧美在线播放| 在线视频这里只有精品| 国产免费黄视频在线观看| 国产精品久久国产精麻豆99网站| 色一情一伦一子一伦一区| www.xxx麻豆| 在线电影国产精品| 欧美午夜一区二区三区免费大片| 国产一区二区视频免费观看| 黄色成人在线视频| 国产一区二中文字幕在线看| 男人操女人的视频在线观看欧美| 久久亚洲私人国产精品va| 成人免费在线观看视频网站| 国产 日韩 欧美 综合 一区| 国产毛片av在线| 亚洲熟女乱综合一区二区| 欧美区日韩区| 免费尤物视频| 色哟哟精品丝袜一区二区| 91香蕉视频免费在线观看| 国产午夜亚洲精品午夜鲁丝片| 星空影院最新电视剧免费观看| 国产尤物一区二区在线| a天堂在线资源| avove在线观看| 91伊人久久大香线蕉| 激情六月天婷婷| 好男人在线视频www| 欧美老肥妇做.爰bbww| 99久久久久| 亚洲成人999| 自拍视频在线观看一区二区| 国产高清中文字幕在线| 美女被啪啪一区二区| 欧美日韩国产观看视频| 午夜免费福利视频| 免费观看久久久久| 免费亚洲精品视频| 亚洲2区在线| 日本韩国欧美| 新版中文在线官网| 麻豆福利在线观看| 久久久久久久久久久久电影| 亚洲第一色视频| 成人中心免费视频| 欧美xxxx性| 性一交一乱一伧老太| 亚洲а∨天堂久久精品9966| 日韩精品视频免费在线观看| 夜夜嗨av一区二区三区网站四季av| 91看片免费| 午夜爱爱毛片xxxx视频免费看| 国产精品亚洲午夜一区二区三区| 浮生影视网在线观看免费| 天天色天天射天天综合网| 国产精品成人一区二区| 国产一区二区三区在线观看| 欧美视频完全免费看| 九九**精品视频免费播放| 波多野结衣家庭教师| 最近中文字幕在线mv视频在线| 中文人妻一区二区三区| 免费人成在线不卡| 日本网站免费观看| 国产欧亚日韩视频| 一区二区三区国产| 青青在线免费视频| 美女又黄又免费的视频| 国产精品电影观看| 夜夜嗨av一区二区三区中文字幕| 一道精品一区二区三区| 欧美精品精品一区| 亚洲精品wwww| 在线看片免费人成视久网| 久久久精彩视频| 欧美另类在线视频| 日韩在线视频免费看| 在线观看亚洲a| 欧美午夜一区| 欧美videos另类| 阳光姐妹淘韩国版| 在线观看国产小视频| 亚洲成va人在线观看| 在线日本成人| 日韩久久精品网| 手机福利小视频在线播放| av福利网址网站| 又黄又免费的网站| 亚洲啊v在线观看| 欧美视频第一区| av免费不卡国产观看| 91精品国产综合久久久久久豆腐| 日韩精品免费视频一区二区三区| 图片区小说区区亚洲影院| 亚欧洲精品在线视频| 美女视频黄a大片欧美| 红杏视频成人| 日本不卡在线| 一区二区免费| 理论片午午伦夜理片在线播放| 妺妺窝人体色www在线观看| 欧美色精品在线视频| 欧美伦理视频在线观看| 免费黄色在线视频| 国产精品影视网| 免费观看特级毛片| 午夜欧美巨大性欧美巨大| 自拍视频网站| 成人毛片老司机大片| 国产乱人伦精品一区二区在线观看| eeuss一区二区三区| 成年人视频网站免费观看| 国产清纯白嫩初高生在线观看91| 久久精品国产久精国产| 肥女人的一级毛片| 色大师av一区二区三区| 日本1区2区3区中文字幕| 免费在线超碰| 久久久久免费av| 成人激情视屏| 久久久91精品国产一区不卡| www.日韩av.com| 成人精品一区二区| 亚洲大香人伊一本线| 国产精品对白一区二区三区| 日韩一区二区三区免费观看| 亚洲天堂网站在线| 日韩精品一区二区三区电影| 美女扒开腿让男人桶爽久久软| 拍拍拍在线观看视频免费| 中国黄色片一级| 国产日产精品久久久久久婷婷| 中文乱码免费一区二区| 在线视频一区二区三| 国产精品你懂的在线欣赏| 成人久久在线| 91欧美一区二区三区| 成人精品视频一区二区三区尤物| 2022中文字幕| 成人a免费在线看| 国产精品www994| 成人欧美大片| 欧美6一10sex性hd| 色综合综合网| 国产福利一区在线| 8x8x国产| 欧美电影免费观看| 欧美成人亚洲成人日韩成人| 亚洲理论电影在线观看| 国产99久久久国产精品潘金| 欧美日韩在线中文| 亚洲天堂免费av| 四虎国产精品免费久久5151| 国产一区二区免费在线观看| 天堂久久久久va久久久久| 18黄暴禁片在线观看| 中文字幕av免费专区久久| 插菊花综合1| 欧美人与物videos另类| 天天鲁一鲁摸一摸爽一爽|