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

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

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Mybatis Generator Plugin悲觀鎖實(shí)現(xiàn)示例

Mybatis Generator Plugin悲觀鎖實(shí)現(xiàn)示例

2022-01-20 12:11raledong Java教程

本文將從悲觀鎖為例,讓你快速了解如何實(shí)現(xiàn)Mybatis Generator Plugin。文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言

Mybatis Generator插件可以快速的實(shí)現(xiàn)基礎(chǔ)的數(shù)據(jù)庫(kù)CRUD操作,它同時(shí)支持JAVA語(yǔ)言和Kotlin語(yǔ)言,將程序員從重復(fù)的Mapper和Dao層代碼編寫中釋放出來(lái)。Mybatis Generator可以自動(dòng)生成大部分的SQL代碼,如update,updateSelectively,insert,insertSelectively,select語(yǔ)句等。但是,當(dāng)程序中需要SQL不在自動(dòng)生成的SQL范圍內(nèi)時(shí),就需要使用自定義Mapper來(lái)實(shí)現(xiàn),即手動(dòng)編寫DAO層和Mapper文件(這里有一個(gè)小坑,當(dāng)數(shù)據(jù)庫(kù)實(shí)體增加字段時(shí),對(duì)應(yīng)的自定義Mapper也要及時(shí)手動(dòng)更新)。拋開(kāi)復(fù)雜的定制化SQL如join,group by等,其實(shí)還是有一些比較常用的SQL在基礎(chǔ)的Mybatis Generator工具中沒(méi)有自動(dòng)生成,比如分頁(yè)能力,悲觀鎖,樂(lè)觀鎖等,而Mybatis Generator也為這些訴求提供了Plugin的能力。通過(guò)自定義實(shí)現(xiàn)Plugin可以改變Mybatis Generator在生成Mapper和Dao文件時(shí)的行為。本文將從悲觀鎖為例,讓你快速了解如何實(shí)現(xiàn)Mybatis Generator Plugin。

實(shí)現(xiàn)背景:

  • 數(shù)據(jù)庫(kù):MYSQL
  • mybatis generator runtime:MyBatis3

實(shí)現(xiàn)Mybatis悲觀鎖

當(dāng)業(yè)務(wù)出現(xiàn)需要保證強(qiáng)一致的場(chǎng)景時(shí),可以通過(guò)在事務(wù)中對(duì)數(shù)據(jù)行上悲觀鎖后再進(jìn)行操作來(lái)實(shí)現(xiàn),這就是經(jīng)典的”一鎖二判三更新“。在交易或是支付系統(tǒng)中,這種訴求非常普遍。Mysql提供了Select...For Update語(yǔ)句來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)行上悲觀鎖。本文將不對(duì)Select...For Update進(jìn)行詳細(xì)的介紹,有興趣的同學(xué)可以查看其它文章深入了解。

Mybatis Generator Plugin為這種具有通用性的SQL提供了很好的支持。通過(guò)繼承org.mybatis.generator.api.PluginAdapter類即可自定義SQL生成邏輯并在在配置文件中使用。PluginAdapter是Plugin接口的實(shí)現(xiàn)類,提供了Plugin的默認(rèn)實(shí)現(xiàn),本文將介紹其中比較重要的幾個(gè)方法:

?
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
public interface Plugin {
    /**
    * 將Mybatis Generator配置文件中的上下文信息傳遞到Plugin實(shí)現(xiàn)類中
    * 這些信息包括數(shù)據(jù)庫(kù)鏈接,類型映射配置等
    */
    void setContext(Context context);
 
    /**
    * 配置文件中的所有properties標(biāo)簽
    **/
    void setProperties(Properties properties);
 
    /**
    * 校驗(yàn)該P(yáng)lugin是否執(zhí)行,如果返回false,則該插件不會(huì)執(zhí)行
    **/
    boolean validate(List<String> warnings);
 
    /**
    * 當(dāng)DAO文件完成生成后會(huì)觸發(fā)該方法,可以通過(guò)實(shí)現(xiàn)該方法在DAO文件中新增方法或?qū)傩?/code>
    **/
    boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
            IntrospectedTable introspectedTable);
 
    /**
    * 當(dāng)SQL XML 文件生成后會(huì)調(diào)用該方法,可以通過(guò)實(shí)現(xiàn)該方法在MAPPER XML文件中新增XML定義
    **/
    boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable);
}

這里結(jié)合Mybatis Generator的配置文件和生成的DAO(也稱為Client文件)和Mapper XML文件可以更好的理解。Mybatis Generator配置文件樣例如下,其中包含了主要的一些配置信息,如用于描述數(shù)據(jù)庫(kù)鏈接的<jdbcConnection>標(biāo)簽,用于定義數(shù)據(jù)庫(kù)和Java類型轉(zhuǎn)換的<javaTypeResolver>標(biāo)簽等。

?
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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
 
<generatorConfiguration>
  <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
 
  <context id="DB2Tables" targetRuntime="MyBatis3">
    <jdbcConnection driverClass="COM.ibm.db2.jdbc.app.DB2Driver"
        connectionURL="jdbc:db2:TEST"
        userId="db2admin"
        password="db2admin">
    </jdbcConnection>
 
    <javaTypeResolver >
      <property name="forceBigDecimals" value="false" />
    </javaTypeResolver>
 
    <javaModelGenerator targetPackage="test.model" targetProject="\MBGTestProject\src">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>
 
    <sqlMapGenerator targetPackage="test.xml"  targetProject="\MBGTestProject\src">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>
 
    <javaClientGenerator type="XMLMAPPER" targetPackage="test.dao"  targetProject="\MBGTestProject\src">
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>
 
    <property name="printLog" value="true"/>
 
    <table schema="DB2ADMIN" tableName="ALLTYPES" domainObjectName="Customer" >
      <property name="useActualColumnNames" value="true"/>
      <generatedKey column="ID" sqlStatement="DB2" identity="true" />
      <columnOverride column="DATE_FIELD" property="startDate" />
      <ignoreColumn column="FRED" />
      <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
    </table>
 
  </context>
</generatorConfiguration>

這些都被映射成Context對(duì)象,并通過(guò)setContext(Context context)方法傳遞到具體的Plugin實(shí)現(xiàn)中:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Context extends PropertyHolder{
 
    /**
    * <context>標(biāo)簽的id屬性
    */
    private String id;
 
    /**
    * jdbc鏈接信息,對(duì)應(yīng)<jdbcConnection>標(biāo)簽中的信息
    */
    private JDBCConnectionConfiguration jdbcConnectionConfiguration;
 
    /**
    * 類型映射配置,對(duì)應(yīng)<javaTypeResolver>
    */
    private JavaTypeResolverConfiguration javaTypeResolverConfiguration;
 
    /**
    * ...其它標(biāo)簽對(duì)應(yīng)的配置信息
    */
}

setProperties則將context下的<properties>標(biāo)簽收集起來(lái)并映射成Properties類,它實(shí)際上是一個(gè)Map容器,正如Properties類本身就繼承了Hashtable。以上文中的配置文件為例,可以通過(guò)properties.get("printLog")獲得值"true"。

validate方法則代表了這個(gè)Plugin是否執(zhí)行,它通常進(jìn)行一些非常基礎(chǔ)的校驗(yàn),比如是否兼容對(duì)應(yīng)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)或者是Mybatis版本:

?
1
2
3
4
5
6
7
8
public boolean validate(List<String> warnings) {
    if (StringUtility.stringHasValue(this.getContext().getTargetRuntime()) && !"MyBatis3".equalsIgnoreCase(this.getContext().getTargetRuntime())) {
        logger.warn("itfsw:插件" + this.getClass().getTypeName() + "要求運(yùn)行targetRuntime必須為MyBatis3!");
        return false;
    } else {
        return true;
    }
}

如果validate方法返回false,則無(wú)論什么場(chǎng)景下都不會(huì)運(yùn)行這個(gè)Plugin。

接著是最重要的兩個(gè)方法,分別是用于在DAO中生成新的方法clientGenerated和在XML文件中生成新的SQL sqlMapDocumentGenerated。

先說(shuō)clientGenerated,這個(gè)方法共有三個(gè)參數(shù),interfaze是當(dāng)前已經(jīng)生成的客戶端Dao接口,topLevelClass是指生成的實(shí)現(xiàn)類,這個(gè)類可能為空,introspectedTable是指當(dāng)前處理的數(shù)據(jù)表,這里包含了從數(shù)據(jù)庫(kù)中獲取的關(guān)于表的各種信息,包括列名稱,列類型等。這里可以看一下introspectedTable中幾個(gè)比較重要的方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class IntrospectedTable {
    /**
    * 該方法可以獲得配置文件中該表對(duì)應(yīng)<table>標(biāo)簽下的配置信息,包括映射成的Mapper名稱,PO名稱等
    * 也可以在table標(biāo)簽下自定義<property>標(biāo)簽并通過(guò)getProperty方法獲得值
    */
    public TableConfiguration getTableConfiguration() {
        return tableConfiguration;
    }
 
    /**
    * 這個(gè)方法中定義了默認(rèn)的生成規(guī)則,可以通過(guò)calculateAllFieldsClass獲得返回類型
    */
    public Rules getRules() {
        return rules;
    }
}

悲觀鎖的clientGenerated方法如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Plugin配置,是否要生成selectForUpdate語(yǔ)句
private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
 
@Override
public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
    String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
    if (StringUtility.isTrue(implementUpdate)) {
        Method method = new Method(METHOD_NAME);
        FullyQualifiedJavaType returnType = introspectedTable.getRules().calculateAllFieldsClass();
        method.setReturnType(returnType);
        method.addParameter(new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id"));
        String docComment = "/**\n" +
                "      * 使用id對(duì)數(shù)據(jù)行上悲觀鎖\n" +
                "      */";
        method.addJavaDocLine(docComment);
        interfaze.addMethod(method);
        log.debug("(悲觀鎖插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");
    }
 
    return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
}

這里可以通過(guò)在對(duì)應(yīng)table下新增property標(biāo)簽來(lái)決定是否要為這張表生成對(duì)應(yīng)的悲觀鎖方法,配置樣例如下:

?
1
2
3
4
5
6
7
8
9
<table tableName="demo" domainObjectName="DemoPO" mapperName="DemoMapper"
              enableCountByExample="true"
              enableUpdateByExample="true"
              enableDeleteByExample="true"
              enableSelectByExample="true"
              enableInsert="true"
              selectByExampleQueryId="true">
   <property name="implementUpdateWithCAS" value="true"/>
</table>

代碼中通過(guò)mybatis提供的Method方法,定義了方法的名稱,參數(shù),返回類型等,并使用interfaze.addMethod方法將方法添加到客戶端的接口中。

再到sqlMapDocumentGenerated這個(gè)方法,這個(gè)方法中傳入了Document對(duì)象,它對(duì)應(yīng)生成的XML文件,并通過(guò)XmlElement來(lái)映射XML文件中的元素。通過(guò)document.getRootElement().addElement可以將自定義的XML元素插入到Mapper文件中。自定義XML元素就是指拼接X(jué)mlElement,XmlElement的addAttribute方法可以為XML元素設(shè)置屬性,addElement則可以為XML標(biāo)簽添加子元素。有兩種類型的子元素,分別是TextElement和XmlElement本身,TextElement則直接填充標(biāo)簽中的內(nèi)容,而XmlElement則對(duì)應(yīng)新的標(biāo)簽,如<where> <include>等。悲觀鎖的SQL生成邏輯如下:

?
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
// Plugin配置,是否要生成selectForUpdate語(yǔ)句
private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
 
@Override
public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
    String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
    if (!StringUtility.isTrue(implementUpdate)) {
        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }
 
    XmlElement selectForUpdate = new XmlElement("select");
    selectForUpdate.addAttribute(new Attribute("id", METHOD_NAME));
    StringBuilder sb;
 
    String resultMapId = introspectedTable.hasBLOBColumns() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId();
    selectForUpdate.addAttribute(new Attribute("resultMap", resultMapId));
    selectForUpdate.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
    selectForUpdate.addElement(new TextElement("select"));
 
    sb = new StringBuilder();
    if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
        sb.append('\'');
        sb.append(introspectedTable.getSelectByExampleQueryId());
        sb.append("' as QUERYID,");
        selectForUpdate.addElement(new TextElement(sb.toString()));
    }
 
    XmlElement baseColumn = new XmlElement("include");
    baseColumn.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
    selectForUpdate.addElement(baseColumn);
    if (introspectedTable.hasBLOBColumns()) {
        selectForUpdate.addElement(new TextElement(","));
        XmlElement blobColumns = new XmlElement("include");
        blobColumns.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
        selectForUpdate.addElement(blobColumns);
    }
 
    sb.setLength(0);
    sb.append("from ");
    sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
    selectForUpdate.addElement(new TextElement(sb.toString()));
    TextElement whereXml = new TextElement("where id = #{id} for update");
    selectForUpdate.addElement(whereXml);
 
    document.getRootElement().addElement(selectForUpdate);
    log.debug("(悲觀鎖插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + METHOD_NAME + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "無(wú)") + "Blob類型))。");
    return super.sqlMapDocumentGenerated(document, introspectedTable);
}

完整代碼

?
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
@Slf4j
public class SelectForUpdatePlugin extends PluginAdapter {
 
    private static final String CONFIG_XML_KEY = "implementSelectForUpdate";
 
    private static final String METHOD_NAME = "selectByIdForUpdate";
 
    @Override
    public boolean validate(List<String> list) {
        return true;
    }
 
    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
        if (StringUtility.isTrue(implementUpdate)) {
            Method method = new Method(METHOD_NAME);
            FullyQualifiedJavaType returnType = introspectedTable.getRules().calculateAllFieldsClass();
            method.setReturnType(returnType);
            method.addParameter(new Parameter(new FullyQualifiedJavaType("java.lang.Long"), "id"));
            String docComment = "/**\n" +
                    "      * 使用id對(duì)數(shù)據(jù)行上悲觀鎖\n" +
                    "      */";
            method.addJavaDocLine(docComment);
            interfaze.addMethod(method);
            log.debug("(悲觀鎖插件):" + interfaze.getType().getShortName() + "增加" + METHOD_NAME + "方法。");
        }
 
        return super.clientGenerated(interfaze, topLevelClass, introspectedTable);
    }
 
    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        String implementUpdate = introspectedTable.getTableConfiguration().getProperty(CONFIG_XML_KEY);
        if (!StringUtility.isTrue(implementUpdate)) {
            return super.sqlMapDocumentGenerated(document, introspectedTable);
        }
 
        XmlElement selectForUpdate = new XmlElement("select");
        selectForUpdate.addAttribute(new Attribute("id", METHOD_NAME));
        StringBuilder sb;
 
        String resultMapId = introspectedTable.hasBLOBColumns() ? introspectedTable.getResultMapWithBLOBsId() : introspectedTable.getBaseResultMapId();
        selectForUpdate.addAttribute(new Attribute("resultMap", resultMapId));
        selectForUpdate.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
        selectForUpdate.addElement(new TextElement("select"));
 
        sb = new StringBuilder();
        if (StringUtility.stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
            sb.append('\'');
            sb.append(introspectedTable.getSelectByExampleQueryId());
            sb.append("' as QUERYID,");
            selectForUpdate.addElement(new TextElement(sb.toString()));
        }
 
        XmlElement baseColumn = new XmlElement("include");
        baseColumn.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
        selectForUpdate.addElement(baseColumn);
        if (introspectedTable.hasBLOBColumns()) {
            selectForUpdate.addElement(new TextElement(","));
            XmlElement blobColumns = new XmlElement("include");
            blobColumns.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
            selectForUpdate.addElement(blobColumns);
        }
 
        sb.setLength(0);
        sb.append("from ");
        sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
        selectForUpdate.addElement(new TextElement(sb.toString()));
        TextElement whereXml = new TextElement("where id = #{id} for update");
        selectForUpdate.addElement(whereXml);
 
        document.getRootElement().addElement(selectForUpdate);
        log.debug("(悲觀鎖插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加" + METHOD_NAME + "方法(" + (introspectedTable.hasBLOBColumns() ? "有" : "無(wú)") + "Blob類型))。");
        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }
}

到此這篇關(guān)于Mybatis Generator Plugin悲觀鎖實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Mybatis Generator Plugin悲觀鎖 內(nèi)容請(qǐng)搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://segmentfault.com/a/1190000040747121

延伸 · 閱讀

精彩推薦
  • Java教程xml與Java對(duì)象的轉(zhuǎn)換詳解

    xml與Java對(duì)象的轉(zhuǎn)換詳解

    這篇文章主要介紹了xml與Java對(duì)象的轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下...

    Java教程網(wǎng)2942020-09-17
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進(jìn)去或缺失數(shù)據(jù)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程升級(jí)IDEA后Lombok不能使用的解決方法

    升級(jí)IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級(jí),尋思已經(jīng)有好久沒(méi)有升過(guò)級(jí)了。升級(jí)完畢重啟之后,突然發(fā)現(xiàn)好多錯(cuò)誤,本文就來(lái)介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實(shí)現(xiàn)搶紅包功能

    Java實(shí)現(xiàn)搶紅包功能

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)搶紅包功能,采用多線程模擬多人同時(shí)搶紅包,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java8中Stream使用的一個(gè)注意事項(xiàng)

    Java8中Stream使用的一個(gè)注意事項(xiàng)

    最近在工作中發(fā)現(xiàn)了對(duì)于集合操作轉(zhuǎn)換的神器,java8新特性 stream,但在使用中遇到了一個(gè)非常重要的注意點(diǎn),所以這篇文章主要給大家介紹了關(guān)于Java8中S...

    阿杜7482021-02-04
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧...

    富貴穩(wěn)中求8032021-07-12
  • Java教程20個(gè)非常實(shí)用的Java程序代碼片段

    20個(gè)非常實(shí)用的Java程序代碼片段

    這篇文章主要為大家分享了20個(gè)非常實(shí)用的Java程序片段,對(duì)java開(kāi)發(fā)項(xiàng)目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
主站蜘蛛池模板: 村妇超级乱淫伦小说全集 | 免费网站看v片在线香蕉 | 亚洲七七久久综合桃花 | 免费在线观看a | 亚洲成人影院在线观看 | 啾咪成人漫画免费 | 国内精品视频一区二区三区八戒 | 日韩在线一区二区三区免费视频 | 变态np虐高h | 麻豆亚洲一区 | 久久精品中文字幕 | les在宿舍吃她奶 | 三级伦理在线播放 | 国产在线极品 | 深夜草莓视频 | 欧美日韩亚洲一区二区三区在线观看 | 美国玩尿眼道videos | 婷婷网址 | 精品四虎国产在免费观看 | 国产精品区牛牛影院 | 国产精品久久久久久久久齐齐 | 精品国偷自产在线 | 九九热这里只有精品视频免费 | 欧美裸妇| 小草观看免费高清视频 | 女人狂吮男人命根gif视频 | 欧美有码| 欧美福利在线观看 | 免费一级特黄特色大片在线观看 | 2020国产精品永久在线观看 | 国产免费小视频 | 国产区香蕉精品系列在线观看不卡 | 免费人成在线观看69式小视频 | 午夜爽喷水无码成人18禁三级 | 99re在线视频观看 | 青青青国产精品国产精品久久久久 | 性夜影院午夜看片 | 亚洲国产精品第一页 | 午夜爽喷水无码成人18禁三级 | 国产精品一区二区不卡的视频 | 王淑兰与铁柱全文免费阅读 |