日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

mybatis源码阅读(二):mybatis初始化上

發布時間:2023/12/3 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 mybatis源码阅读(二):mybatis初始化上 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉載自??mybatis源碼閱讀(二):mybatis初始化上

1.初始化入口

//Mybatis 通過SqlSessionFactory獲取SqlSession, 然后才能通過SqlSession與數據庫進行交互 private static SqlSessionFactory getSessionFactory() {SqlSessionFactory sessionFactory = null;String resource = "configuration.xml";try {sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));} catch (IOException e) {e.printStackTrace();}return sessionFactory; }

那么,我們就先從SqlSessionFactoryBuilder入手, 咱們先看看源碼是怎么實現的

SqlSessionFactoryBuilder

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 讀取配置文件XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//解析配置得到Configuration對象,創建DefaultSqlSessionFactory對象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.}} }

XMLConfigBuilder

XMLConfigBuilder是BaseBuilder的眾多子類之一,核心字段如下

//表示是否已經解析過了 private boolean parsed; //用于解析配置文件的對象 private final XPathParser parser; //配置文件中表示<environment>的名稱 默認讀取default屬性 private String environment; // 負責和創建Reflector對象 private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

XMLConfigBuilder.parse()方法是解析mybatis-config.xml配置文件的如。它調用parseConfiguration()方法實現整個解析過程。具體實現如下:

/*** 解析配置文件的入口* @return*/ public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration; }/*** 對配置文件每個節點具體的解析過程* configuration節點為根節點。* 在configuration節點之下,我們可以配置11 個子節點,* 分別為:properties、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、* environments、databaseIdProvider、typeHandlers、mappers。* @param root 根節點*/ private void parseConfiguration(XNode root) {try {// 解析properties節點propertiesElement(root.evalNode("properties"));//解析settings節點Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);//設置vfsImpl字段//解析typeAliases節點typeAliasesElement(root.evalNode("typeAliases"));//解析plugins節點pluginElement(root.evalNode("plugins"));//解析objectFactory節點objectFactoryElement(root.evalNode("objectFactory"));//解析objectWrapperFactory節點objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));//解析reflectorFactory節點reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);//解析environments節點environmentsElement(root.evalNode("environments"));//解析databaseIdProvider節點databaseIdProviderElement(root.evalNode("databaseIdProvider"));//解析typeHandlers節點typeHandlerElement(root.evalNode("typeHandlers"));//解析mappers節點mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);} }

propertiesElement()方法會解析配置文件中的properties節點并形成Java.util.Properties對象,之后將改對象設置到XpathParse和Configguration的variables字段中,占位符就是用Properties中的信息替換的,具體實現如下:

/*** 解析properties的具體方法* @param context* @throws Exception*/ private void propertiesElement(XNode context) throws Exception {if (context != null) {// 將子節點的 name 以及value屬性set進properties對象// 這兒可以注意一下順序,xml配置優先, 外部指定properties配置其次Properties defaults = context.getChildrenAsProperties();// 獲取properties節點上 resource屬性的值String resource = context.getStringAttribute("resource");// 獲取properties節點上 url屬性的值, resource和url不能同時配置String url = context.getStringAttribute("url");if (resource != null && url != null) {//url 和resource不能同時配置throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");}// 把解析出的properties文件set進Properties對象if (resource != null) {defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}// 將configuration對象中已配置的Properties屬性與剛剛解析的融合// configuration這個對象會裝載所解析mybatis配置文件的所有節點元素,以后也會頻頻提到這個對象Properties vars = configuration.getVariables();if (vars != null) {defaults.putAll(vars);}// 把裝有解析配置propertis對象set進解析器, 因為后面可能會用到parser.setVariables(defaults);// set進configuration對象configuration.setVariables(defaults);} }

settings節點下的配飾是mybatis的全局性配置,修改的是configuration對象的屬性,具體說明參考官方文檔

/*** settings標簽就是設置configuration對象的各種屬性,* 具體屬性說明可以參考mybatis官方文檔* @param props* @throws Exception*/ private void settingsElement(Properties props) throws Exception {configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));@SuppressWarnings("unchecked")Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));configuration.setDefaultEnumTypeHandler(typeHandler);configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));configuration.setLogPrefix(props.getProperty("logPrefix"));@SuppressWarnings("unchecked")Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));configuration.setLogImpl(logImpl);configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }

environments元素節點主要配置數據庫事物,數據源。可以配置多個environment子節點,假如我們系統的開發環境和正式環境所用的數據庫不一樣(這是肯定的), 那么可以設置兩個environment, 兩個id分別對應開發環境(development)和正式環境(final),那么通過配置environments的default屬性就能選擇對應的environment了, 例如,我將environments的deault屬性的值配置為development, 那么就會選擇dev的environment。具體實現如下

/*** 解析enviroments元素節點的方法* @param context* @throws Exception*/ private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {//獲取 <environments default="development"> 中的default值environment = context.getStringAttribute("default");}// 循環environments的子節點for (XNode child : context.getChildren()) {// 獲取 <environment id="development"> z中的idString id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {//根據由environments的default屬性去選擇對應的enviroment// 事物 mybatis有兩種:JDBC 和 MANAGED, 配置為JDBC則直接使用JDBC的事務,配置為MANAGED則是將事務托管給容器// <transactionManager type="JDBC"/>TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));//enviroment節點下面就是dataSource節點了,解析dataSource節點DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));DataSource dataSource = dsFactory.getDataSource();Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);// 將dataSource設置進configuration對象configuration.setEnvironment(environmentBuilder.build());}}} }

typeAliases節點主要用來設置別名,其實這是挺好用的一個功能, 通過配置別名,我們不用再指定完整的包名

/*** 解析typeAliases 節點* <typeAliases>* <!--<package name="com.lpf.entity"></package>-->* <typeAlias alias="UserEntity" type="com.lpf.entity.User"/>* </typeAliases>* @param parent*/ private void typeAliasesElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {//如果子節點是package, 那么就獲取package節點的name屬性, mybatis會掃描指定的packageif ("package".equals(child.getName())) {String typeAliasPackage = child.getStringAttribute("name");//TypeAliasRegistry 負責管理別名, 這兒就是通過TypeAliasRegistry 進行別名注冊configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);} else {//如果子節點是typeAlias節點,那么就獲取alias屬性和type的屬性值String alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class<?> clazz = Resources.classForName(type);if (alias == null) {typeAliasRegistry.registerAlias(clazz);} else {typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);}}}} }

具體的別名注冊類

public class TypeAliasRegistry {// 別名通過一個HashMap來實現, key為別名, value就是別名對應的類型(class對象)private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();/*** mybatis默認為我們注冊的別名*/public TypeAliasRegistry() {registerAlias("string", String.class);registerAlias("byte", Byte.class);registerAlias("long", Long.class);registerAlias("short", Short.class);registerAlias("int", Integer.class);registerAlias("integer", Integer.class);registerAlias("double", Double.class);registerAlias("float", Float.class);registerAlias("boolean", Boolean.class);registerAlias("byte[]", Byte[].class);registerAlias("long[]", Long[].class);registerAlias("short[]", Short[].class);registerAlias("int[]", Integer[].class);registerAlias("integer[]", Integer[].class);registerAlias("double[]", Double[].class);registerAlias("float[]", Float[].class);registerAlias("boolean[]", Boolean[].class);registerAlias("_byte", byte.class);registerAlias("_long", long.class);registerAlias("_short", short.class);registerAlias("_int", int.class);registerAlias("_integer", int.class);registerAlias("_double", double.class);registerAlias("_float", float.class);registerAlias("_boolean", boolean.class);registerAlias("_byte[]", byte[].class);registerAlias("_long[]", long[].class);registerAlias("_short[]", short[].class);registerAlias("_int[]", int[].class);registerAlias("_integer[]", int[].class);registerAlias("_double[]", double[].class);registerAlias("_float[]", float[].class);registerAlias("_boolean[]", boolean[].class);registerAlias("date", Date.class);registerAlias("decimal", BigDecimal.class);registerAlias("bigdecimal", BigDecimal.class);registerAlias("biginteger", BigInteger.class);registerAlias("object", Object.class);registerAlias("date[]", Date[].class);registerAlias("decimal[]", BigDecimal[].class);registerAlias("bigdecimal[]", BigDecimal[].class);registerAlias("biginteger[]", BigInteger[].class);registerAlias("object[]", Object[].class);registerAlias("map", Map.class);registerAlias("hashmap", HashMap.class);registerAlias("list", List.class);registerAlias("arraylist", ArrayList.class);registerAlias("collection", Collection.class);registerAlias("iterator", Iterator.class);registerAlias("ResultSet", ResultSet.class);}/*** 處理別名, 直接從保存有別名的hashMap中取出即可*/@SuppressWarnings("unchecked")// throws class cast exception as well if types cannot be assignedpublic <T> Class<T> resolveAlias(String string) {try {if (string == null) {return null;}// issue #748String key = string.toLowerCase(Locale.ENGLISH);Class<T> value;if (TYPE_ALIASES.containsKey(key)) {value = (Class<T>) TYPE_ALIASES.get(key);} else {value = (Class<T>) Resources.classForName(string);}return value;} catch (ClassNotFoundException e) {throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);}}/*** 配置文件中配置為package的時候,掃描包下的Javabean ,然后自動注冊別名* 默認會使用 Bean 的首字母小寫的非限定類名來作為它的別名* 也可在javabean 加上注解@Alias 來自定義別名, 例如: @Alias(user)*/public void registerAliases(String packageName){registerAliases(packageName, Object.class);}public void registerAliases(String packageName, Class<?> superType){ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();resolverUtil.find(new ResolverUtil.IsA(superType), packageName);Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();for(Class<?> type : typeSet){// Ignore inner classes and interfaces (including package-info.java)// Skip also inner classes. See issue #6if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {registerAlias(type);}}}public void registerAlias(Class<?> type) {String alias = type.getSimpleName();Alias aliasAnnotation = type.getAnnotation(Alias.class);if (aliasAnnotation != null) {alias = aliasAnnotation.value();} registerAlias(alias, type);}//向hashMap中注冊別名public void registerAlias(String alias, Class<?> value) {if (alias == null) {throw new TypeException("The parameter alias cannot be null");}// issue #748String key = alias.toLowerCase(Locale.ENGLISH);if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");}TYPE_ALIASES.put(key, value);}public void registerAlias(String alias, String value) {try {registerAlias(alias, Resources.classForName(value));} catch (ClassNotFoundException e) {throw new TypeException("Error registering type alias "+alias+" for "+value+". Cause: " + e, e);}}/*** 獲取保存別名的HashMap, Configuration對象持有對TypeAliasRegistry的引用,* 因此,如果需要,我們可以通過Configuration對象獲取*/public Map<String, Class<?>> getTypeAliases() {return Collections.unmodifiableMap(TYPE_ALIASES);}}

typeHandlers節點的解析和typeAlianses節點的解析類似

/*** 解析typeHandlers節點* 無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,* 還是從結果集中取出一個值時,都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。* Mybatis默認為我們實現了許多TypeHandler, 當我們沒有配置指定TypeHandler時,* Mybatis會根據參數或者返回結果的不同,默認為我們選擇合適的TypeHandler處理。* @param parent* @throws Exception*/ private void typeHandlerElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {//子節點為package時,獲取其name屬性的值,然后自動掃描package下的自定義typeHandlerif ("package".equals(child.getName())) {String typeHandlerPackage = child.getStringAttribute("name");typeHandlerRegistry.register(typeHandlerPackage);} else {//子節點為typeHandler時, 可以指定javaType屬性, 也可以指定jdbcType, 也可兩者都指定//javaType 是指定java類型//jdbcType 是指定jdbc類型(數據庫類型: 如varchar)String javaTypeName = child.getStringAttribute("javaType");String jdbcTypeName = child.getStringAttribute("jdbcType");//handler就是我們配置的typeHandlerString handlerTypeName = child.getStringAttribute("handler");Class<?> javaTypeClass = resolveClass(javaTypeName);//JdbcType是一個枚舉類型,resolveJdbcType方法是在獲取枚舉類型的值JdbcType jdbcType = resolveJdbcType(jdbcTypeName);Class<?> typeHandlerClass = resolveClass(handlerTypeName);//注冊typeHandler, typeHandler通過TypeHandlerRegistry這個類管理if (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);}} else {typeHandlerRegistry.register(typeHandlerClass);}}}} }

插件是mybatis提供的擴展機制,用戶可以通過添加自定義插件在SQL語句執行的過程中某一環節進行攔截,mybatis中的自定義插件只需實現Interceptor接口,并通過注解指定攔截的方法簽名,這個后面具體介紹。

/*** 解析plugins標簽* mybatis中的plugin其實就是個interceptor,* 它可以攔截Executor 、ParameterHandler 、ResultSetHandler 、StatementHandler 的部分方法,處理我們自己的邏輯。* @param parent* @throws Exception*/ private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();// 我們在定義一個interceptor的時候,需要去實現InterceptorInterceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);// 向configuration對象中注冊攔截器configuration.addInterceptor(interceptorInstance);}} }

mybatis初始化時,出了加載mybatis-config.xml的全局配置文件,還會加載全部的映射配置文件,即mappers節點配置的mapper.

/*** 解析mapper文件,mapper可以理解為dao的實現* @param parent* @throws Exception*/ private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {//如果mappers節點的子節點是package, 那么就掃描package下的文件, 注入進configurationif ("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");//resource, url, class 三選一if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);//mapper映射文件都是通過XMLMapperBuilder解析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.");}}}} }

mybatis初始化過程中對mybatis-config.xml配置文件的解析過程到這吧,下一個就叫啥mapper配置文件的解析過程。

總結

以上是生活随笔為你收集整理的mybatis源码阅读(二):mybatis初始化上的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 欧洲视频一区二区 | 97精品人妻一区二区三区在线 | 欧美手机在线视频 | 手机av免费观看 | 亚洲 欧洲 日韩 | 免费福利在线视频 | 夜夜嗨av一区二区三区四区 | 亚洲精品一区二三区不卡 | 精品成人在线 | 久久夜夜操 | 亚洲欧洲自拍 | 伊人97| 亚洲影院在线观看 | 亚洲福利视频一区二区 | 超碰8| 亚洲夜夜爱 | 怡红院男人天堂 | 天天射夜夜操 | 欧美猛操 | 午夜精品一区二区在线观看 | 精品视频在线观看免费 | 性无码专区无码 | 久久久久久久福利 | 国产免费a视频 | 韩日一区 | 无码人妻精品一区二区三区蜜桃91 | 婷婷色一区二区三区 | 色视频在线观看免费 | 三级第一页 | 99热在线观看 | 亚洲精品伦理 | 一级视频毛片 | 日本一区二区三区四区视频 | 欧美视频一区 | 亚洲成人久久久久 | 精品欧美色视频网站在线观看 | 激情五月视频 | 国产在线一级 | 亚洲爱爱视频 | 人妖干美女 | 国产乱人乱偷精品视频a人人澡 | 天堂影视在线观看 | 尤物自拍| 中文毛片无遮挡高潮免费 | xxxx毛片 | 欧美精品国产 | 一区二区三区久久久久 | 91蜜桃视频 | 粗喘呻吟撞击猛烈疯狂 | 欧美精品国产一区二区 | 91a视频 | 日韩免费不卡视频 | 91激情视频在线 | 少妇太紧太爽又黄又硬又爽 | 久久激情免费视频 | 国产精品成久久久久三级 | 日韩综合网站 | 色屁屁www| 亚洲欧美在线不卡 | 久久国产视频网 | jizz久久| 在线看黄色网址 | 播放灌醉水嫩大学生国内精品 | 狠狠做深爱婷婷久久综合一区 | 熟妇人妻久久中文字幕 | 国产在线观看a | 99在线免费观看视频 | 亚洲色图第一页 | 国产无精乱码一区二区三区 | 午夜精品在线观看 | 日本精品视频在线观看 | 国产在线网址 | 女人免费视频 | 成人在线免费视频播放 | 欧亚一区二区三区 | 三级黄色生活片 | 日日摸日日干 | 日韩一级影院 | 岛国精品一区二区三区 | 另类色综合 | 99午夜视频 | 欧美 日本 国产 | 日韩午夜在线播放 | 天堂在线精品 | 少妇一晚三次一区二区三区 | 日韩 欧美 自拍 | 中国性xxx| 亚洲hhh | 绯色av一区| 国产一区二区在 | 亚洲春色另类 | 婷婷五月综合缴情在线视频 | 十大黄台在线观看 | 欧美亚洲综合另类 | 国产伦精品一区二区三区免费视频 | 中文字幕制服诱惑 | 日韩黄视频| 性xxxx欧美老肥妇牲乱 | 毛片最新网址 |