一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務(wù)器之家 - 編程語(yǔ)言 - JAVA教程 - Java實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池簡(jiǎn)易教程

Java實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池簡(jiǎn)易教程

2020-03-23 12:46PerfectCoder JAVA教程

這篇文章主要為大家介紹了Java實(shí)現(xiàn)數(shù)據(jù)庫(kù)連接池簡(jiǎn)易教程,感興趣的小伙伴們可以參考一下

一、引言

  池化技術(shù)在Java中應(yīng)用的很廣泛,簡(jiǎn)而論之,使用對(duì)象池存儲(chǔ)某個(gè)實(shí)例數(shù)受限制的實(shí)例,開發(fā)者從對(duì)象池中獲取實(shí)例,使用完之后再換回對(duì)象池,從而在一定程度上減少了系統(tǒng)頻繁創(chuàng)建對(duì)象銷毀對(duì)象的開銷。Java線程池和數(shù)據(jù)庫(kù)連接池就是典型的應(yīng)用,但并非所有的對(duì)象都適合拿來池化,對(duì)于創(chuàng)建開銷比較小的對(duì)象拿來池化反而會(huì)影響性能,因?yàn)榫S護(hù)對(duì)象池也需要一定的資源開銷,對(duì)于創(chuàng)建開銷較大,又頻繁創(chuàng)建使用的對(duì)象,采用池化技術(shù)會(huì)極大提高性能。

  業(yè)界有很多成熟的數(shù)據(jù)庫(kù)連接池,比如C3P0,DBCP,Proxool以及阿里的Druid。很多以及開源,在GitHub可以找到源碼,開發(fā)者可以根據(jù)自己的需求結(jié)合各種連接池的特點(diǎn)和性能進(jìn)行選擇。本文僅是為了了解學(xué)習(xí)池化技術(shù),實(shí)現(xiàn)的一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)連接池,如有錯(cuò)誤,還望批評(píng)指正。

二、設(shè)計(jì)

主要類和接口

.ConnectionParam - 數(shù)據(jù)庫(kù)連接池參數(shù)類,負(fù)責(zé)配置數(shù)據(jù)庫(kù)連接以及連接池相關(guān)參數(shù)。使用Builder實(shí)現(xiàn)。

    driver url user password - 連接數(shù)據(jù)庫(kù)所需

    minConnection - 最小連接數(shù)

    maxConnection - 最大連接數(shù)

    minIdle - 最小空閑連接數(shù)

    maxWait - 最長(zhǎng)等待時(shí)間  

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final String driver;
 
private final String url;
 
private final String user;
 
private final String password;
 
private final int minConnection;
 
private final int maxConnection;
 
private final int minIdle;
 
private final long maxWait;

.ConnectionPool - 數(shù)據(jù)庫(kù)連接池

    ConnectionPool構(gòu)造方法聲明為保護(hù),禁止外部創(chuàng)建,交由ConnectionPoolFactory統(tǒng)一管理。

    ConnectionPool實(shí)現(xiàn)DataSource接口,重新getConnection()方法。

    ConnectionPool持有兩個(gè)容器 - 一個(gè)Queue存儲(chǔ)空閑的Connection,另一個(gè)Vector(考慮到同步)存儲(chǔ)正在使用的Connection。

    當(dāng)開發(fā)者使用數(shù)據(jù)庫(kù)連接時(shí),從Queue中獲取,沒有則返回空;使用完成close連接時(shí),則放回Vector。

    ConnectionPool提供了一個(gè)簡(jiǎn)單的基于minIdle和maxConnection的動(dòng)態(tài)擴(kuò)容機(jī)制。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
private static final int INITIAL_SIZE = 5;
 
private static final String CLOSE_METHOD = "close";
 
private static Logger logger;
 
private int size;
 
private ConnectionParam connectionParam;
 
private ArrayBlockingQueue<Connection> idleConnectionQueue;
 
private Vector<Connection> busyConnectionVector;

.ConnectionPoolFactory - 連接池管理類

  ConnectionPoolFactory持有一個(gè)靜態(tài)ConcurrentHashMap用來存儲(chǔ)連接池對(duì)象。

  ConnectionPoolFactory允許創(chuàng)建多個(gè)不同配置不同數(shù)據(jù)庫(kù)的連接池。

  開發(fā)者首次需要使用特定的名稱注冊(cè)(綁定)連接池,以后每次從指定的連接池獲取Connection。

  如果連接池不再使用,開發(fā)者可以注銷(解綁)連接池。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static Map<String, ConnectionPool> poolMap = new ConcurrentHashMap<>();
 
public static Connection getConnection(String poolName) throws SQLException {
 nameCheck(poolName);
 ConnectionPool connectionPool = poolMap.get(poolName);
 return connectionPool.getConnection();
}
 
public static void registerConnectionPool(String name, ConnectionParam connectionParam) {
 registerCheck(name);
 poolMap.put(name, new ConnectionPool(connectionParam));
}
 
// Let GC
public static void unRegisterConnectionPool(String name) {
 nameCheck(name);
 final ConnectionPool connectionPool = poolMap.get(name);
 poolMap.remove(name);
 new Thread(new Runnable() {
  @Override
  public void run() {
   connectionPool.clear();
  }
 }).start();
}

核心代碼

   數(shù)據(jù)庫(kù)連接池核心代碼在于getConnection()方法,通常,開發(fā)者處理完數(shù)據(jù)庫(kù)操作后,都會(huì)調(diào)用close()方法,Connection此時(shí)應(yīng)該被關(guān)閉并釋放資源。而在數(shù)據(jù)庫(kù)連接池中,用戶調(diào)用close()方法,不應(yīng)直接關(guān)閉Connection,而是要放回池中,重復(fù)使用,這里就用到Java動(dòng)態(tài)代理機(jī)制,getConnection返回的并不是“真正”的Connection,而是自定義的代理類(此處使用匿名類),當(dāng)用戶調(diào)用close()方法時(shí),進(jìn)行攔截,放回池中。有關(guān)動(dòng)態(tài)代理,可以參看另一篇博客《Java動(dòng)態(tài)代理簡(jiǎn)單應(yīng)用》

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public Connection getConnection() throws SQLException {
 try {
  final Connection connection = idleConnectionQueue.poll(connectionParam.getMaxWait(), TimeUnit.MILLISECONDS);
  if (connection == null) {
   logger.info(emptyMsg());
   ensureCapacity();
   return null;
  }
  busyConnectionVector.add(connection);
  return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(),
    new Class[]{Connection.class}, new InvocationHandler() {
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      if (!method.getName().equals(CLOSE_METHOD)) {
       return method.invoke(connection, args);
      } else {
       idleConnectionQueue.offer(connection);
       busyConnectionVector.remove(connection);
       return null;
      }
     }
    });
 } catch (InterruptedException e) {
  e.printStackTrace();
 }
 return null;
}

二、使用

  首先用戶構(gòu)建數(shù)據(jù)庫(kù)連接池參數(shù)(ConnectionParam),包括driver、url、user、password必須項(xiàng),可以自定義minConnection、maxConnection等可選項(xiàng),如果不設(shè)置,則使用系統(tǒng)默認(rèn)值,這是使用Builder構(gòu)建含有大量屬性的好處,其中包括必須屬性和可選屬性。然后向ConnectionPoolFactory使用特定的名稱注冊(cè)連接池,最后通過調(diào)用ConnectionPoolFactory靜態(tài)工廠方法獲取Connection。   

?
1
2
3
4
5
6
7
8
String driver = "com.mysql.jdbc.Driver";
 String url = "jdbc:mysql://localhost:3306/test";
 String user = "root";
 String password = "root";
 
 ConnectionParam connectionParam = new ConnectionParam.ConnectionParamBuilder(driver, url, user, password).build();
 ConnectionPoolFactory.registerConnectionPool("test", connectionParam);
 Connection connection = ConnectionPoolFactory.getConnection("test");

三、代碼

.ParamConfiguration

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package database.config;
 
import java.io.Serializable;
 
/**
 * DataBase Connection Parameters
 * Created by Michael Wong on 2016/1/18.
 */
public class ParamConfiguration implements Serializable {
 
 public static final int MIN_CONNECTION = 5;
 
 public static final int MAX_CONNECTION = 50;
 
 public static final int MIN_IDLE = 5;
 
 public static final long MAX_WAIT = 30000;
 
 private ParamConfiguration() {}
 
}

.Builder

?
1
2
3
4
5
6
7
8
9
10
11
package database;
 
/**
 * Builder
 * Created by Michael Wong on 2016/1/18.
 */
public interface Builder<T> {
 
 T build();
 
}

.ConnectionParam

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package database;
 
import database.config.ParamConfiguration;
 
/**
 * DataBase Connection Parameters
 * Created by Michael Wong on 2016/1/18.
 */
public class ConnectionParam {
 
 private final String driver;
 
 private final String url;
 
 private final String user;
 
 private final String password;
 
 private final int minConnection;
 
 private final int maxConnection;
 
 private final int minIdle;
 
 private final long maxWait;
 
 private ConnectionParam(ConnectionParamBuilder builder) {
  this.driver = builder.driver;
  this.url = builder.url;
  this.user = builder.user;
  this.password = builder.password;
  this.minConnection = builder.minConnection;
  this.maxConnection = builder.maxConnection;
  this.minIdle = builder.minIdle;
  this.maxWait = builder.maxWait;
 }
 
 public String getDriver() {
  return this.driver;
 }
 
 public String getUrl() {
  return this.url;
 }
 
 public String getUser() {
  return this.user;
 }
 
 public String getPassword() {
  return this.password;
 }
 
 public int getMinConnection() {
  return this.minConnection;
 }
 
 public int getMaxConnection() {
  return this.maxConnection;
 }
 
 public int getMinIdle() {
  return this.minIdle;
 }
 
 public long getMaxWait() {
  return this.maxWait;
 }
 
 public static class ConnectionParamBuilder implements Builder<ConnectionParam> {
 
  // Required parameters
  private final String driver;
 
  private final String url;
 
  private final String user;
 
  private final String password;
 
  // Optional parameters - initialized to default value
  private int minConnection = ParamConfiguration.MIN_CONNECTION;
 
  private int maxConnection = ParamConfiguration.MAX_CONNECTION;
 
  private int minIdle = ParamConfiguration.MIN_IDLE;
 
  // Getting Connection wait time
  private long maxWait = ParamConfiguration.MAX_WAIT;
 
  public ConnectionParamBuilder(String driver, String url, String user, String password) {
   this.driver = driver;
   this.url = url;
   this.user = user;
   this.password = password;
  }
 
  public ConnectionParamBuilder minConnection(int minConnection) {
   this.minConnection = minConnection;
   return this;
  }
 
  public ConnectionParamBuilder maxConnection(int maxConnection) {
   this.maxConnection = maxConnection;
   return this;
  }
 
  public ConnectionParamBuilder minIdle(int minIdle) {
   this.minIdle = minIdle;
   return this;
  }
 
  public ConnectionParamBuilder maxWait(int maxWait) {
   this.maxWait = maxWait;
   return this;
  }
 
  @Override
  public ConnectionParam build() {
   return new ConnectionParam(this);
  }
 
 }
 
}

.ConnectionPool

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package database.factory;
 
import database.ConnectionParam;
 
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
 
/**
 * Connection Pool
 * Created by Michael Wong on 2016/1/18.
 */
public class ConnectionPool implements DataSource {
 
 private static final int INITIAL_SIZE = 5;
 
 private static final String CLOSE_METHOD = "close";
 
 private static Logger logger;
 
 private int size;
 
 private ConnectionParam connectionParam;
 
 private ArrayBlockingQueue<Connection> idleConnectionQueue;
 
 private Vector<Connection> busyConnectionVector;
 
 protected ConnectionPool(ConnectionParam connectionParam) {
  this.connectionParam = connectionParam;
  int maxConnection = connectionParam.getMaxConnection();
  idleConnectionQueue = new ArrayBlockingQueue<>(maxConnection);
  busyConnectionVector = new Vector<>();
  logger = Logger.getLogger(this.getClass().getName());
  initConnection();
 }
 
 private void initConnection() {
  int minConnection = connectionParam.getMinConnection();
  int initialSize = INITIAL_SIZE < minConnection ? minConnection : INITIAL_SIZE;
  try {
   Class.forName(connectionParam.getDriver());
   for (int i = 0; i < initialSize + connectionParam.getMinConnection(); i++) {
    idleConnectionQueue.put(newConnection());
    size++;
   }
  } catch (Exception e) {
   throw new ExceptionInInitializerError(e);
  }
 }
 
 @Override
 public Connection getConnection() throws SQLException {
  try {
   final Connection connection = idleConnectionQueue.poll(connectionParam.getMaxWait(), TimeUnit.MILLISECONDS);
   if (connection == null) {
    logger.info(emptyMsg());
    ensureCapacity();
    return null;
   }
   busyConnectionVector.add(connection);
   return (Connection) Proxy.newProxyInstance(this.getClass().getClassLoader(),
     new Class[]{Connection.class}, new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       if (!method.getName().equals(CLOSE_METHOD)) {
        return method.invoke(connection, args);
       } else {
        idleConnectionQueue.offer(connection);
        busyConnectionVector.remove(connection);
        return null;
       }
      }
     });
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  return null;
 }
 
 private Connection newConnection() throws SQLException {
  String url = connectionParam.getUrl();
  String user = connectionParam.getUser();
  String password = connectionParam.getPassword();
  return DriverManager.getConnection(url, user, password);
 }
 
 protected int size() {
  return size;
 }
 
 protected int idleConnectionQuantity() {
  return idleConnectionQueue.size();
 }
 
 protected int busyConnectionQuantity() {
  return busyConnectionVector.size();
 }
 
 private void ensureCapacity() throws SQLException {
  int minIdle = connectionParam.getMinIdle();
  int maxConnection = connectionParam.getMaxConnection();
  int newCapacity = size + minIdle;
  newCapacity = newCapacity > maxConnection ? maxConnection : newCapacity;
  int growCount = 0;
  if (size < newCapacity) {
   try {
    for (int i = 0; i < newCapacity - size; i++) {
     idleConnectionQueue.put(newConnection());
     growCount++;
    }
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
  size = size + growCount;
 }
 
 protected void clear() {
  try {
   while (size-- > 0) {
    Connection connection = idleConnectionQueue.take();
    connection.close();
   }
  } catch (InterruptedException | SQLException e) {
   e.printStackTrace();
  }
 }
 
 private String emptyMsg() {
  return "Database is busy, please wait...";
 }
 
 @Override
 public Connection getConnection(String username, String password) throws SQLException {
  return null;
 }
 
 @Override
 public PrintWriter getLogWriter() throws SQLException {
  return null;
 }
 
 @Override
 public void setLogWriter(PrintWriter out) throws SQLException {
 
 }
 
 @Override
 public void setLoginTimeout(int seconds) throws SQLException {
 
 }
 
 @Override
 public int getLoginTimeout() throws SQLException {
  return 0;
 }
 
 @Override
 public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  return null;
 }
 
 @Override
 public <T> T unwrap(Class<T> iface) throws SQLException {
  return null;
 }
 
 @Override
 public boolean isWrapperFor(Class<?> iface) throws SQLException {
  return false;
 }
 
}

 

.ConnectionPoolFactory

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package database.factory;
 
import database.ConnectionParam;
 
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * Connection Pool Factory
 * Created by Michael Wong on 2016/1/18.
 */
public class ConnectionPoolFactory {
 
 private ConnectionPoolFactory() {}
 
 private static Map<String, ConnectionPool> poolMap = new ConcurrentHashMap<>();
 
 public static Connection getConnection(String poolName) throws SQLException {
  nameCheck(poolName);
  ConnectionPool connectionPool = poolMap.get(poolName);
  return connectionPool.getConnection();
 }
 
 public static void registerConnectionPool(String name, ConnectionParam connectionParam) {
  registerCheck(name);
  poolMap.put(name, new ConnectionPool(connectionParam));
 }
 
 // Let GC
 public static void unRegisterConnectionPool(String name) {
  nameCheck(name);
  final ConnectionPool connectionPool = poolMap.get(name);
  poolMap.remove(name);
  new Thread(new Runnable() {
   @Override
   public void run() {
    connectionPool.clear();
   }
  }).start();
 }
 
 public static int size(String poolName) {
  nameCheck(poolName);
  return poolMap.get(poolName).size();
 }
 
 public static int getIdleConnectionQuantity(String poolName) {
  nameCheck(poolName);
  return poolMap.get(poolName).idleConnectionQuantity();
 }
 
 public static int getBusyConnectionQuantity(String poolName) {
  nameCheck(poolName);
  return poolMap.get(poolName).busyConnectionQuantity();
 }
 
 private static void registerCheck(String name) {
  if (name == null) {
   throw new IllegalArgumentException(nullName());
  }
 }
 
 private static void nameCheck(String name) {
  if (name == null) {
   throw new IllegalArgumentException(nullName());
  }
  if (!poolMap.containsKey(name)) {
   throw new IllegalArgumentException(notExists(name));
  }
 }
 
 private static String nullName() {
  return "Pool name must not be null";
 }
 
 private static String notExists(String name) {
  return "Connection pool named " + name + " does not exists";
 }
 
}

四、測(cè)試
JUnit單元測(cè)試

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package database.factory;
 
import database.ConnectionParam;
import org.junit.Test;
 
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.*;
 
/**
 * ConnectionPoolFactory Test
 * Created by Michael Wong on 2016/1/20.
 */
public class ConnectionPoolFactoryTest {
 
 @Test
 public void testGetConnection() throws SQLException {
 
  String driver = "com.mysql.jdbc.Driver";
  String url = "jdbc:mysql://localhost:3306/test";
  String user = "root";
  String password = "root";
 
  ConnectionParam connectionParam = new ConnectionParam.ConnectionParamBuilder(driver, url, user, password).build();
  ConnectionPoolFactory.registerConnectionPool("test", connectionParam);
 
  List<Connection> connectionList = new ArrayList<>();
 
  for(int i = 0; i < 12; i++) {
   connectionList.add(ConnectionPoolFactory.getConnection("test"));
  }
 
  print();
 
  close(connectionList);
 
  print();
 
  connectionList.clear();
 
  for(int i = 0; i < 12; i++) {
   connectionList.add(ConnectionPoolFactory.getConnection("test"));
  }
 
  print();
 
  close(connectionList);
 
  ConnectionPoolFactory.unRegisterConnectionPool("test");
 
 }
 
 @Test(expected = IllegalArgumentException.class)
 public void testException() {
  try {
   ConnectionPoolFactory.getConnection("test");
  } catch (SQLException e) {
   e.printStackTrace();
  }
 }
 
 private void close(List<Connection> connectionList) throws SQLException {
  for(Connection conn : connectionList) {
   if (conn != null) {
    conn.close();
   }
  }
 }
 
 private void print() {
  System.out.println("idle: " + ConnectionPoolFactory.getIdleConnectionQuantity("test"));
  System.out.println("busy: " + ConnectionPoolFactory.getBusyConnectionQuantity("test"));
  System.out.println("size: " + ConnectionPoolFactory.size("test"));
 }
 
}

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 波多野结衣中文字幕在线 | 荷兰精品女人性hd | 亚洲国产在线99视频 | 国产一区二区三区在线观看视频 | 国产高清在线精品一区 | 免费观看国产视频 | 国内久久精品 | 成人高清视频在线观看 | 亚洲系列第一页 | 精品欧美一区二区三区久久久 | 精品视频入口 | 美日毛片 | 美女厕所尿尿擦逼 | 武侠古典久久亚洲精品 | 99热国产在线观看 | 视频一区 日韩 | 免费日本在线视频 | 色猪视频 | 三级aaa黄特色 | 四虎影音在线 | 久久国产乱子伦精品免费不卡 | 国产一级真人毛爱做毛片 | 免费高清在线视频色yeye | 久久伊人电影 | xxxxxx日本处大片免费看 | 国产精品一区二区三区久久 | 丝瓜污污视频 | 国产剧情一区二区三区 | 欧美肥b | 日韩一区二三区无 | 精品国产乱码久久久人妻 | 波多野结衣在线中文 | 四虎影院大全 | 99精品国产美女福到在线不卡 | 黑人双渗透| 91国产在线观看 | 亚洲视频在线观看免费 | 激情另类国内一区二区视频 | 国产成人咱精品视频免费网站 | 成人午夜爽爽爽免费视频 | 免费欧美视频 |