主頁 > 軟體設計 > 教小師妹快速入門Mybatis,看這篇就夠了

教小師妹快速入門Mybatis,看這篇就夠了

2020-12-11 12:11:29 軟體設計

關注“Java后端技術全堆疊”

回復“面試”獲取全套面試資料

本文主要內容:

傳統JDBC

傳統JDBC編碼格式

public class DataBaseUtil {
    public static final String URL = "jdbc:mysql://localhost:3306/mblog";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception {
        
        Class.forName("com.mysql.jdbc.Driver");
        //2. 
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
        //3.
        Statement stmt = conn.createStatement();
        //4.
        ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1");
        //如果有資料,rs.next()回傳true
        while(rs.next()){
            System.out.println("name: "+rs.getString("name")+" 年齡:"+rs.getInt("age"));
        }
    }
}

上面代碼中知識為了展示JDBC整個程序(例外和資源是簡單粗暴的處理了,我們關注的點不在這兩個),

大致可以分為六個步驟:

  1. 加載驅動程式

  2. 獲得資料庫連接

  3. 創建一個Statement物件

  4. 操作資料庫,實作增刪改查

  5. 獲取結果集

  6. 關閉資源

從使用層面來說,采用原生態的JDBC在專案中使用起來成本還是很高的,如果我們的專案中的業務相對比較復雜,資料庫表也相對較多,各種操作資料庫的增刪改查的方法也會隨之多起來,那么這樣的代碼重復次數會非常之多,

傳統JDBC的問題

  • 創建資料庫的連接存在大量的硬編碼,

  • 執行statement時存在硬編碼.

  • 頻繁的開啟和關閉資料庫連接,會嚴重影響資料庫的性能,浪費資料庫的資源.

  • 存在大量的重復性編碼

為了解決以上問題,就誕生了各種各樣替換JDBC的產品,即就是ORM框架,

什么是ORM?

全稱為Object Relational Mapping,物件-映射-關系型資料庫,物件關系映射(,簡稱ORM,或O/RM,或O/R mapping),用于實作面向物件編程語言里不同型別系統的資料之間的轉換,簡單的說,ORM是通過使用描述物件和資料庫之間映射的元資料,將程式中的物件與關系資料庫相互映射,

ORM提供了實作持久化層的另一種模式,它采用映射元資料來描述物件關系的映射,使得ORM中間件能在任何一個應用的業務邏輯層和資料庫層之間充當橋梁,

我們的專案中是這樣的:

比如說:Apache DbUtils、Spring JDBC、 Hibernate、Ibatis(Mybatis的前生)、Spring Data Jpa等等,

目前最為流行的MybatisSpring Data Jpa,本系列我們先講解MybatisJpa后面再講,

ORM的優缺點

優點1.提高了開發效率,由于ORM可以自動對Entity物件與資料庫中的Table進行欄位與屬性的映射,所以我們實際可能已經不需要一個專用的、龐大的資料訪問層,2.ORM提供了對資料庫的映射,不用sql直接編碼,能夠像操作物件一樣從資料庫獲取資料,

缺點犧牲程式的執行效率和會固定思維模式,降低了開發的靈活性,

從系統結構上來看,采用ORM的系統一般都是多層系統,系統的層次多了,效率就會降低,ORM是一種完全的面向物件的做法,而面向物件的做法也會對性能產生一定的影響,在我們開發系統時,一般都有性能問題,性能問題主要產生在演算法不正確和與資料庫不正確的使用上,ORM所生成的代碼一般不太可能寫出很高效的演算法,在資料庫應用上更有可能會被誤用,主要體現在對持久物件的提取和和資料的加工處理上,如果用上了ORM,程式員很有可能將全部的資料提取到記憶體物件中,然后再進行過濾和加工處理,這樣就容易產生性能問題,在對物件做持久化時,ORM一般會持久化所有的屬性,有時,這是不希望的,但ORM是一種工具,工具確實能解決一些重復,簡單的勞動,這是不可否認的,但我們不能指望工具能一勞永逸的解決所有問題,有些問題還是需要特殊處理的,但需要特殊處理的部分對絕大多數的系統,應該是很少的,

MyBatis 是什么?

如果在面試的時候被問到,只要你說出下面三種即可,

MyBatis 是一款優秀的持久層框架,它支持自定義 SQL、存盤程序以及高級映射,

MyBatis 免除了幾乎所有的 JDBC 代碼以及設定引數和獲取結果集的作業,

MyBatis 可以通過簡單的 XML 或注解來配置和映射原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄,

來自官網,

MyBatis的優點和缺點

優點:

(1)基于SQL陳述句編程,相當靈活,不會對應用程式或者資料庫的現有設計造成任何影響,SQL寫在XML里,解除sql與程式代碼的耦合,便于統一管理;提供XML標簽,支持撰寫動態SQL陳述句,并可重用,

(2)與JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗余的代碼,不需要手動開關連接;

(3)很好的與各種資料庫兼容(因為MyBatis使用JDBC來連接資料庫,所以只要JDBC支持的資料庫MyBatis都支持),

(4)能夠與Spring很好的集成;

(5)提供映射標簽,支持物件與資料庫的ORM欄位關系映射;提供物件關系映射標簽,支持物件關系組件維護,

缺點

(1)SQL陳述句的撰寫作業量較大,尤其當欄位多、關聯表多時,對開發人員撰寫SQL陳述句的功底有一定要求,

(2)SQL陳述句依賴于資料庫,導致資料庫移植性差,不能隨意更換資料庫,

Mybatis環境搭建及簡單實體

創建一張資料庫表

創建一張m_user表使用MySQL資料庫,

CREATE TABLE `m_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

添加依賴

<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
            <scope>runtime</scope>
        </dependency>
</dependencies>

專案結構如下:

創建一個mybatis-config.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mblog?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>

物體類User

ublic class User {
    private Integer id;
    private String name;
    private Integer age;
    //set get
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

創建UserMapper.java

import com.tian.mybatis.entity.User;

public interface UserMapper {
    User selectUserById(Integer id);
}

創建UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tian.mybatis.mapper.UserMapper">
    <select id="selectUserById" resultType="com.tian.mybatis.entity.User">
        select * from m_user where id = #{id}
    </select>
</mapper>

創建一個測驗類

import com.tian.mybatis.entity.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MybatisApplication {

 public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        SqlSession sqlSession =null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
            //工廠模式
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //sql操作會話
            sqlSession = sqlSessionFactory.openSession();
            //獲取資料并決議成User物件
            User user = sqlSession.selectOne("com.tian.mybatis.mapper.UserMapper.selectUserById", 1);
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            sqlSession.close();
        }
    }

}

輸出結果:

User{id=1, name='tian', age=22}

整體步驟:

另外一種啟動方式

import com.tian.mybatis.entity.User;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class MybatisApplication {

    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //決議xml檔案
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
        //構建一個SqlSessionFactory工廠類
        SqlSessionFactory sqlSessionFactory = build(parser.parse());
        //創建一個SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //獲取資料并決議成User物件
        User user = sqlSession.selectOne("com.tian.mybatis.mapper.UserMapper.selectUserById", 1);
        System.out.println(user);
    }

    public static SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
}

輸出和上面一樣,還可以直接使用Java代碼而不用mybatis-config.xml

//創建資料源
DataSource dataSource = getDataSource();

TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

小總結

從上面這個案例中我們大致能猜到,Mybatis中最主要幾個組件:

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

Mapper

SqlSessionFactoryBuilder這個類可以被初始、使用和丟棄,如果你已經創建好了一個 SqlSessionFactory后就不用再保留它,因此 ,SqlSessionFactoryBuilder 的最好作用域是方法體內,比如說定義一個方法變數,

你可以重復使用SqlSessionFactoryBuilder 生成多個 SqlSessionFactory 實體,但是最好不要強行保留,因為 XML 的決議資源要用來做其它更重要的事,

SqlSessionFactory一旦創建,SqlSessionFactory就會在整個應用程序中始終存在,所以沒有理由去銷毀和再創建它,一個 應用運行中也不建議多次創建 SqlSessionFactory,如果真的那樣做,會顯得很拙劣,

因此 SqlSessionFactory最好的作用域是 Application,可以有多種方法實作,最簡單的方法是單例模式或者是靜態單例模式,然而這既不是廣泛贊成和好用的,反而,使用 Google Guice 或 Spring 來進行依賴反射會更好,這些框架允 許你生成管理器來管理 SqlSessionFactory的單例生命周期

SqlSession每個執行緒都有自己的 SqlSession 實體,SqlSession 實體是不能被共享,也是不是執行緒安全的,因此最好 使用 Request 作用域或者方法體作用域,

不要使用類的靜態變數來參考一個 SqlSession 實體,甚至不要使用類的一個實體變更來參考,永遠不要在一個被管理域中參考 SqlSession,比如說在 Servlet 中的HttpSession 中,如果你正在使用 WEB 框架,應該讓 SqlSession 跟隨 HTTP 請求的相似作用域,

也就是說,在收到一個 HTTP 請求過后,打開 SqlSession,等回傳一個回應以后,立馬關掉這個 SqlSession,關閉 SqlSession 是非常重要的,你必須要確保 SqlSession 在 finally 方法體中正常關閉,

SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}

使用這種模式來貫穿你的所有代碼,以確保所有資料庫資源都被完全關閉,

Mapper

Mapper 是一種你創建的用于系結映射陳述句的介面,Mapper 介面的實體是用 SqlSession 來獲得的,同樣 , 從技術上來說,最廣泛的 Mapper 實體作用域像 SqlSession 一樣,使用請求作用域,確切地說,在方法 被呼叫的時候呼叫 Mapper 實體,然后使用后,就自動銷毀掉,不需要使用明確的注銷,當一個請求執 行正確無誤的時候,像 SqlSession 一樣,你可以輕而易舉地操控這一切,保持簡單性,保持 Mapper 在 方法體作用域內,

 //獲取資料并決議成User物件
 User user = sqlSession.selectOne("com.tian.mybatis.mapper.UserMapper.selectUserById", 1);
 System.out.println(user);

這里映射涉及到四個主體:

  1. 物體類User,

  2. 介面UaerMapper

  3. xml組態檔UserMapper

  4. 資料庫表m_user

Mybatis的五部曲:

總結

回顧JDBC的demo,JDBC中的問題,創建一個Mybatis示例,總結出重要的四個組件,以及每個組件的作用,

推薦閱讀

《億級流量網站架構核心技術》.pdf

《演算法的樂趣》.pdf

面試官:Object有些什么方法?教你如何吊打他

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/233121.html

標籤:其他

上一篇:MySQL架構備份之雙機熱備——MySql 主從復制、主主復制

下一篇:OpenStack的探索之路——基礎篇

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more