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

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

MyBatis攔截器的原理與使用

瀏覽:96日期:2023-10-19 09:07:07
目錄一、攔截對象和接口實現示例二、攔截器注冊的三種方式1.XML注冊2.配置類注冊3.注解方式三、ParameterHandler參數改寫-修改時間和修改人統一插入四、通過StatementHandler改寫SQL一、攔截對象和接口實現示例

MyBatis攔截器的作用是在于Dao到DB中間進行額外的處理。大部分情況下通過mybatis的xml配置sql都可以達到想要的DB操作效果,然而存在一些類似或者相同的查詢條件或者查詢要求,這些可以通過攔截器的實現可以提升開發效率,比如:分頁、插入和更新時間/人、數據權限、SQL監控日志等。

Mybatis支持四種對象攔截Executor、StatementHandler、PameterHandler和ResultSetHandler Executor:攔截執行器的方法。 StatementHandler:攔截Sql語法構建的處理。 ParameterHandler:攔截參數的處理。 ResultHandler:攔截結果集的處理。

public interface Executor { ResultHandler NO_RESULT_HANDLER = null; int update(MappedStatement var1, Object var2) throws SQLException; <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException; <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException; <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException; List<BatchResult> flushStatements() throws SQLException; void commit(boolean var1) throws SQLException; void rollback(boolean var1) throws SQLException; CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4); boolean isCached(MappedStatement var1, CacheKey var2); void clearLocalCache(); void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5); Transaction getTransaction(); void close(boolean var1); boolean isClosed(); void setExecutorWrapper(Executor var1);}public interface StatementHandler { Statement prepare(Connection var1, Integer var2) throws SQLException; void parameterize(Statement var1) throws SQLException; void batch(Statement var1) throws SQLException; int update(Statement var1) throws SQLException; <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException; <E> Cursor<E> queryCursor(Statement var1) throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler();}public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement var1) throws SQLException;}public interface ResultHandler<T> { void handleResult(ResultContext<? extends T> var1);}

攔截的執行順序是Executor->StatementHandler->ParameterHandler->ResultHandler

MyBatis提供的攔截器接口:

public interface Interceptor { Object intercept(Invocation var1) throws Throwable; default Object plugin(Object target) {return Plugin.wrap(target, this); } default void setProperties(Properties properties) {}}

Object intercept方法用于攔截器的實現;

Object plugin方法用于判斷執行攔截器的類型;

void setProperties方法用于獲取配置項的屬性。

攔截對象和攔截器接口的結合,自定義的攔截器類需要實現攔截器接口,并通過注解@Intercepts和參數@Signature來聲明要攔截的對象。

@Signature參數type是攔截對象,method是攔截的方法,即上面的四個類對應的方法,args是攔截方法對應的參數(方法存在重載因此需要指明參數個數和類型)

@Intercepts可以有多個@Signature,即一個攔截器實現類可以同時攔截多個對象及方法,示例如下:

Executor->intercept StatementHandler->intercept ParameterHandler->intercept ResultHandler->intercept

@Intercepts({@Signature(type = Executor.class,method = 'query',args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class SelectPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof Executor) { System.out.println('SelectPlugin');}return invocation.proceed(); } @Override public Object plugin(Object target) {if (target instanceof Executor) { return Plugin.wrap(target, this);}return target; } @Override public void setProperties(Properties properties) {}}@Intercepts({@Signature(type = StatementHandler.class, method = 'prepare', args = {Connection.class, Integer.class})})public class StatementPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof StatementHandler) { System.out.println('StatementPlugin');}return invocation.proceed(); } @Override public Object plugin(Object target) {if (target instanceof StatementHandler) { return Plugin.wrap(target, this);}return target; } @Override public void setProperties(Properties properties) {}}@Intercepts({@Signature(type = ParameterHandler.class,method = 'setParameters',args = {PreparedStatement.class})})public class ParameterPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof ParameterHandler) { System.out.println('ParameterPlugin');}return invocation.proceed(); } @Override public Object plugin(Object target) {if (target instanceof ParameterHandler) { return Plugin.wrap(target, this);}return target; } @Override public void setProperties(Properties properties) {}}@Intercepts({@Signature(type = ResultHandler.class,method = 'handleResult',args = {ResultContext.class})})public class ResultPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof ResultHandler) { System.out.println('ResultPlugin');}return invocation.proceed(); } @Override public Object plugin(Object target) {if (target instanceof ResultHandler) { return Plugin.wrap(target, this);}return target; } @Override public void setProperties(Properties properties) {}}二、攔截器注冊的三種方式

前面介紹了Mybatis的攔截對象及其接口的實現方式,那么在項目中如何注冊攔截器呢?本文中給出三種注冊方式。

1.XML注冊

xml注冊是最基本的方式,是通過在Mybatis配置文件中plugins元素來進行注冊的。一個plugin對應著一個攔截器,在plugin元素可以指定property子元素,在注冊定義攔截器時把對應攔截器的所有property通過Interceptor的setProperties方法注入給攔截器。因此攔截器注冊xml方式如下:

<?xml version='1.0' encoding='UTF-8' ?><!DOCTYPE configuration PUBLIC '-//mybatis.org//DTD Config 3.0//EN' 'http://mybatis.org/dtd/mybatis-3-config.dtd'><configuration> <!-- ...... --> <plugins> <plugin interceptor='com.tiantian.mybatis.interceptor.MyInterceptor'> <property name='prop1' value='prop1'/> <property name='prop2' value='prop2'/> </plugin> </plugins> <!-- ...... --></configuration>2.配置類注冊

配置類注冊是指通過Mybatis的配置類中聲明注冊攔截器,配置類注冊也可以通過Properties類給Interceptor的setProperties方法注入參數。具體參考如下:

@Configurationpublic class MyBatisConfig { @Bean public String MyBatisInterceptor(SqlSessionFactory sqlSessionFactory) {UpdatePlugin executorInterceptor = new UpdatePlugin();Properties properties = new Properties();properties.setProperty('prop1', 'value1');// 給攔截器添加自定義參數executorInterceptor.setProperties(properties);sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor);sqlSessionFactory.getConfiguration().addInterceptor(new StatementPlugin());sqlSessionFactory.getConfiguration().addInterceptor(new ResultPlugin());sqlSessionFactory.getConfiguration().addInterceptor(new ParameterPlugin());// sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());return 'interceptor'; } // 與sqlSessionFactory.getConfiguration().addInterceptor(new SelectPlugin());效果一致 @Bean public SelectPlugin SelectInterceptor() {SelectPlugin interceptor = new SelectPlugin();Properties properties = new Properties();// 調用properties.setProperty方法給攔截器設置自定義參數interceptor.setProperties(properties);return interceptor; }}3.注解方式

通過@Component注解方式是最簡單的方式,在不需要轉遞自定義參數時可以使用,方便快捷。

@Component@Intercepts({@Signature(type = Executor.class,method = 'query',args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class SelectPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof Executor) { System.out.println('SelectPlugin');}return invocation.proceed(); } @Override public Object plugin(Object target) {if (target instanceof Executor) { return Plugin.wrap(target, this);}return target; } @Override public void setProperties(Properties properties) { }}三、ParameterHandler參數改寫-修改時間和修改人統一插入

針對具體的攔截器實現進行描述。日常編碼需求中會碰到修改時需要插入修改的時間和人員,如果要用xml的方式去寫非常麻煩,而通過攔截器的方式可以快速實現全局的插入修改時間和人員。先看代碼:

@Component@Intercepts({@Signature(type = ParameterHandler.class, method = 'setParameters', args = {PreparedStatement.class}),})public class MyBatisInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {// 參數代理if (invocation.getTarget() instanceof ParameterHandler) { System.out.println('ParameterHandler'); // 自動添加操作員信息 autoAddOperatorInfo(invocation);}return invocation.proceed(); } @Override public Object plugin(Object target) {return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } /** * 自動添加操作員信息 * * @param invocation 代理對象 * @throws Throwable 異常 */ private void autoAddOperatorInfo(Invocation invocation) throws Throwable {System.out.println('autoInsertCreatorInfo');// 獲取代理的參數對象ParameterHandlerParameterHandler ph = (ParameterHandler) invocation.getTarget();// 通過MetaObject獲取ParameterHandler的反射內容MetaObject metaObject = MetaObject.forObject(ph,SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());// 通過MetaObject反射的內容獲取MappedStatementMappedStatement mappedStatement = (MappedStatement) metaObject.getValue('mappedStatement');// 當sql類型為INSERT或UPDATE時,自動插入操作員信息if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT ||mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) { // 獲取參數對象 Object obj = ph.getParameterObject(); if (null != obj) {// 通過反射獲取參數對象的屬性Field[] fields = obj.getClass().getDeclaredFields();// 遍歷參數對象的屬性for (Field f : fields) { // 如果sql是INSERT,且存在createdAt屬性 if ('createdAt'.equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {// 設置允許訪問反射屬性f.setAccessible(true);// 如果沒有設置createdAt屬性,則自動為createdAt屬性添加當前的時間if (null == f.get(obj)) { // 設置createdAt屬性為當前時間 f.set(obj, LocalDateTime.now());} } // 如果sql是INSERT,且存在createdBy屬性 if ('createdBy'.equals(f.getName()) && mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {// 設置允許訪問反射屬性f.setAccessible(true);// 如果沒有設置createdBy屬性,則自動為createdBy屬性添加當前登錄的人員if (null == f.get(obj)) { // 設置createdBy屬性為當前登錄的人員 f.set(obj, 0);} } // sql為INSERT或UPDATE時均需要設置updatedAt屬性 if ('updatedAt'.equals(f.getName())) {f.setAccessible(true);if (null == f.get(obj)) { f.set(obj, LocalDateTime.now());} } // sql為INSERT或UPDATE時均需要設置updatedBy屬性 if ('updatedBy'.equals(f.getName())) {f.setAccessible(true);if (null == f.get(obj)) { f.set(obj, 0);} }}// 通過反射獲取ParameterHandler的parameterObject屬性Field parameterObject = ph.getClass().getDeclaredField('parameterObject');// 設置允許訪問parameterObject屬性parameterObject.setAccessible(true);// 將上面設置的新參數對象設置到ParameterHandler的parameterObject屬性parameterObject.set(ph, obj); }} }}

攔截器的接口實現參考前文,這里著重介紹autoAddOperatorInfo方法里的相關類。

1.ParameterHandler

接口源碼:

public interface ParameterHandler { Object getParameterObject(); void setParameters(PreparedStatement var1) throws SQLException; }

提供兩個方法:

getParameterObject是獲取參數對象,可能存在null,需要注意null指針。

setParameters是控制如何設置SQL參數,即sql語句中配置的java對象和jdbc類型對應的關系,例如#{id,jdbcType=INTEGER},id默認類型是javaType=class java.lang.Integer。

該接口有一個默認的實現類,源碼如下:

public class DefaultParameterHandler implements ParameterHandler { private final TypeHandlerRegistry typeHandlerRegistry; private final MappedStatement mappedStatement; private final Object parameterObject; private final BoundSql boundSql; private final Configuration configuration; public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {this.mappedStatement = mappedStatement;this.configuration = mappedStatement.getConfiguration();this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();this.parameterObject = parameterObject;this.boundSql = boundSql; } public Object getParameterObject() {return this.parameterObject; } public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity('setting parameters').object(this.mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();if (parameterMappings != null) { for(int i = 0; i < parameterMappings.size(); ++i) {ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) { String propertyName = parameterMapping.getProperty(); Object value; if (this.boundSql.hasAdditionalParameter(propertyName)) {value = this.boundSql.getAdditionalParameter(propertyName); } else if (this.parameterObject == null) {value = null; } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {value = this.parameterObject; } else {MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) {jdbcType = this.configuration.getJdbcTypeForNull(); } try {typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (SQLException | TypeException var10) {throw new TypeException('Could not set parameters for mapping: ' + parameterMapping + '. Cause: ' + var10, var10); }} }} }}

通過DefaultParameterHandler實現類我們知道通過ParameterHandler可以獲取到哪些屬性和方法,其中包括我們下面一個重要的類MappedStatement。

2.MappedStatement

MyBatis的mapper文件中的每個select/update/insert/delete標簽會被解析器解析成一個對應的MappedStatement對象,也就是一個MappedStatement對象描述一條SQL語句。MappedStatement對象屬性如下:

// mapper配置文件名 private String resource; // mybatis的全局信息,如jdbc private Configuration configuration; // 節點的id屬性加命名空間,如:com.example.mybatis.dao.UserMapper.selectByExample private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; // sql語句的類型:select、update、delete、insert private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;

在本例中通過MappedStatement對象的sqlCommandType來判斷當前的sql類型是insert、update來進行下一步的操作。

四、通過StatementHandler改寫SQL

StatementHandler是用于封裝JDBC Statement操作,負責對JDBC Statement的操作,如設置參數,并將Statement結果集轉換成List集合。

實現代碼如下:

刪除注解標記

@Target({ElementType.METHOD}) //表示注解的使用范圍@Retention(RetentionPolicy.RUNTIME) //注解的保存時間@Documented //文檔顯示public @interface DeletedAt { boolean has() default true;}

Dao層添加刪除注解,為false時不添加刪除標志

@Mapper public interface AdminProjectDao { @DeletedAt(has = false) List<AdminProjectPo> selectProjects(AdminProjectPo po); }

攔截器通過刪除注解標記判斷是否添加刪除標志

@Component@Intercepts({@Signature(type = StatementHandler.class, method = 'prepare', args = {Connection.class, Integer.class}),})public class MyBatisInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable {if (invocation.getTarget() instanceof StatementHandler) { System.out.println('StatementHandler'); checkHasDeletedAtField(invocation);}return invocation.proceed(); } @Override public Object plugin(Object target) {return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } /** * 檢查查詢是否需要添加刪除標志字段 * * @param invocation 代理對象 * @throws Throwable 異常 */ private void checkHasDeletedAtField(Invocation invocation) throws Throwable {System.out.println('checkHasDeletedAtField');StatementHandler statementHandler = (StatementHandler) invocation.getTarget();// 通過MetaObject訪問對象的屬性MetaObject metaObject = MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,new DefaultReflectorFactory());// 獲取成員變量mappedStatementMappedStatement mappedStatement = (MappedStatement) metaObject.getValue('delegate.mappedStatement');// 如果sql類型是查詢if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) { // 獲取刪除注解標志 DeletedAt annotation = null; String id = mappedStatement.getId(); String className = id.substring(0, id.lastIndexOf('.')); String methodName = id.substring(id.lastIndexOf('.') + 1); Class<?> aClass = Class.forName(className); Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) {declaredMethod.setAccessible(true);//方法名相同,并且注解是DeletedAtif (methodName.equals(declaredMethod.getName()) && declaredMethod.isAnnotationPresent(DeletedAt.class)) { annotation = declaredMethod.getAnnotation(DeletedAt.class);} } // 如果注解不存在或者注解為true(默認為true) 則為mysql語句增加刪除標志 if (annotation == null || annotation.has()) {BoundSql boundSql = statementHandler.getBoundSql();//獲取到原始sql語句String sql = boundSql.getSql();//通過反射修改sql語句Field field = boundSql.getClass().getDeclaredField('sql');field.setAccessible(true);String newSql = sql.replaceAll('9=9', '9=9 and deleted_at is null ');field.set(boundSql, newSql); }} }}

在SQL語句替換上需要能識別到要被替換的內容,因此在xml的sql語句中加入特殊標志'9=9',該標志不影響原來SQL的執行結果,不同的過濾條件可以設置不同的標志,是一個比較巧妙的替換方式。

以上就是MyBatis攔截器的原理與使用的詳細內容,更多關于MyBatis攔截器的資料請關注好吧啦網其它相關文章!

標簽: Mybatis 數據庫
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
欧美另类中文字幕| 欧美一区成人| 亚洲精品中文字幕乱码| 亚洲成人一区| 日av在线不卡| 国产精品4hu.www| 日韩免费视频| 日韩影院免费视频| 国产精品v日韩精品v欧美精品网站| 国产不卡精品| 图片区亚洲欧美小说区| 亚洲美女91| 成人在线免费观看91| 在线一区视频| 天堂va在线高清一区| 国产中文字幕一区二区三区| 91成人精品| 国产精品3区| 欧美日韩国产传媒| 国产视频一区二区在线播放| 欧美成人基地 | 午夜欧美精品久久久久久久| 亚洲久久一区| 日韩美女一区二区三区在线观看| 美日韩精品视频| 成人午夜网址| 宅男噜噜噜66国产日韩在线观看| 国产日韩1区| 精品一区免费| 欧美激情aⅴ一区二区三区| 国产综合激情| 国产精品网站在线看| 99久久亚洲精品| 国产伦精品一区二区三区在线播放| 日韩电影免费在线观看| 日韩va亚洲va欧美va久久| 久久精品在线| 国产精品草草| 日韩专区一卡二卡| 福利在线免费视频| 国产视频一区二| 亚洲电影在线一区二区三区| 国产精品1luya在线播放| 国产亚洲精品v| 中文一区一区三区高中清不卡免费| 日韩中文字幕区一区有砖一区| 日韩成人精品一区二区| 欧美精品国产白浆久久久久| 亚洲综合国产| 蜜臀久久精品| 久久中文字幕一区二区| 亚洲精品精选| 欧美日韩精品一本二本三本 | 午夜电影一区| 亚洲91视频| 天堂av在线| 麻豆中文一区二区| 中文字幕日韩亚洲| 国产伊人精品| 日韩欧美一区二区三区免费观看| 久久99蜜桃| 捆绑调教美女网站视频一区| 日韩精品一区二区三区中文在线| 国户精品久久久久久久久久久不卡 | 欧美啪啪一区| 男人的天堂久久精品| se01亚洲视频| www.九色在线| 国产成人免费精品| 国产精品久久久久av蜜臀| 亚洲精品美女| 一区二区三区四区精品视频| 99国产精品视频免费观看一公开 | 国产一在线精品一区在线观看| 国产成人精品一区二区三区视频 | 欧美综合另类| 日韩伦理福利| 精品欠久久久中文字幕加勒比| 日韩av不卡在线观看| 综合激情婷婷| 日韩精品一卡二卡三卡四卡无卡| 亚州av乱码久久精品蜜桃| 国产一区二区三区国产精品| 麻豆精品久久久| 美女精品久久| 精品国产一级| 精品亚洲二区| av资源新版天堂在线| 日韩av专区| 日韩大片在线观看| 日韩伦理一区| 亚洲成人精品| 激情五月综合网| 午夜国产精品视频免费体验区| 婷婷激情图片久久| 99在线精品免费视频九九视| 黄色成人91| 中文一区二区| 国产一区导航| 中文字幕日韩亚洲| 日韩av午夜在线观看| 日韩亚洲精品在线观看| 日韩av在线播放中文字幕| 国产欧美69| 国产精品videossex久久发布 | 国产精品美女久久久浪潮软件| 亚洲精品一区三区三区在线观看| 久久九九99| 日韩午夜在线| 日韩精品一二区| 欧美日韩一区二区国产| 国产高清精品二区| 麻豆一区二区99久久久久| 国产成人免费视频网站视频社区| 高清不卡亚洲| 欧美aa国产视频| 免费观看在线综合色| 日韩影片在线观看| 国产精品白丝一区二区三区| 久久一区视频| 开心激情综合| 伊人网在线播放| 中文一区二区| 久久国内精品视频| 日韩精品专区| 爽好久久久欧美精品| 欧美日韩一视频区二区| 成午夜精品一区二区三区软件| 神马日本精品| 在线精品一区二区| 国产精品美女午夜爽爽| 韩国三级一区| 一区二区国产精品| 九九99久久精品在免费线bt| 国产麻豆久久| 一区二区91| 蜜桃精品视频| 在线日韩中文| 日韩精彩视频在线观看| 精品国产aⅴ| 日韩午夜在线| 国产精品第十页| 亚洲高清不卡| 18国产精品| 久久人人精品| 欧美三级第一页| 欧美综合另类| 欧美一级二区| 在线日韩中文| 国产精品v一区二区三区| 久久视频精品| 日韩和欧美一区二区| av资源亚洲| 日本在线视频一区二区| 成人一区不卡| 日韩影院在线观看| a天堂资源在线| 亚洲毛片网站| 欧美精品日日操| 日本视频中文字幕一区二区三区| 国产 日韩 欧美一区| 日韩av不卡一区二区| 久久中文字幕av| 国产日韩免费| 国产精品腿扒开做爽爽爽挤奶网站| 国产亚洲欧美日韩精品一区二区三区 | 欧美成人基地| 亚洲精品无播放器在线播放| 岛国av在线网站| 日韩欧美2区| 美女网站一区| 精品一区二区三区中文字幕在线| 欧美资源在线| 成人午夜精品| 国产精品一区二区99| 国产女优一区| 日韩啪啪电影网| 国产精品一区亚洲| 石原莉奈一区二区三区在线观看| 98精品久久久久久久| 日本一区中文字幕| 欧美精品九九| 色乱码一区二区三区网站| 日韩一区二区三区精品| 美女少妇全过程你懂的久久| 精品淫伦v久久水蜜桃| 日韩激情一二三区| 鲁大师影院一区二区三区| 日韩专区精品| 精品一区二区三区视频在线播放| 午夜电影一区| 香蕉久久久久久久av网站| 99久久亚洲精品| 亚洲国产福利| 麻豆精品新av中文字幕| 日本成人一区二区| 免费在线欧美视频| 米奇777超碰欧美日韩亚洲| 欧美xxxx中国| 国产精品一区二区三区美女 |