使Mybatis開發(fā)變得更加輕松的增強工具 — Ourbatis

一刺桃、Mybatis的不足之處

Mybatis是一款優(yōu)秀的及其靈活的持久層框架,通過XML配置并映射到Mapper接口為Service層提供基礎數(shù)據(jù)操作入口镣屹。

這么優(yōu)秀的框架竟然還有不足之處?

俗話說人無完人喳挑,因為Mybatis實在是太靈活了味赃,靈活到每個Mapper接口都需要定制對應的XML掀抹,所以就會引發(fā)一些問題。

問題一:配置文件繁多

假如一個系統(tǒng)中DB中涉及100張表心俗,我們就需要寫100個Mapper接口傲武,還沒完,最可怕的是另凌,我們要為這100個Mapper接口定制與之對應的100套XML谱轨。而每個Mapper都必不可少的需要增刪改查功能戒幔,我們就要寫100遍增刪改查吠谢,作為高貴的Java開發(fā)工程師,這是不能容忍的诗茎,于是Mybatis Generator誕生了工坊,然而又會引發(fā)另一個問題!

問題二:維護困難

我們使用Mybatis Generator解決了問題一敢订,再多的文件生成就是了王污,簡單粗暴,貌似解決了所有的問題楚午,Mybatis完美了昭齐!

不要高興的太早,在系統(tǒng)剛剛建立起來時矾柜,我們使用Mybatis Generator生成了一堆XML阱驾,在開發(fā)過程中,產(chǎn)品忽然提了一個新的需求怪蔑,項目經(jīng)理根據(jù)這個需求在某張表中增加或變動了一個字段里覆,這時,我猜你的操作是這樣:

  • 1缆瓣、找到對應表的XML
  • 2喧枷、將該XML中自定義的一段標簽復制出來,保存在本地
  • 3弓坞、使用Mybatis Generator重新生成該表的XML
  • 4隧甚、將之覆蓋當前的XML
  • 5、將自定義的一段標簽再粘貼進新的XML中

在這個過程中渡冻,如果我們在第2步時漏復制了一段標簽戚扳,等整個操作完成之后,又別是一番滋味在心頭~

問題三:編寫XML困難

假如肝不錯菩帝,問題二也是小CASE咖城,那么問題又來了茬腿,我們?nèi)绾卧诜遍L的XML中去編寫和修改我們的XML呢。

當我們打開要編輯的XML宜雀,映入眼簾的就是1000多行的XML切平,其中900行都是通用的增刪改查操作,要新增一個標簽辐董,我們需要拉至文件底部編寫新的數(shù)據(jù)操作悴品,要更新一個標簽,我們需要通過Ctrl + F尋找目標標簽再進行修改简烘。

如何避免這些問題呢苔严?

如何讓Mybatis增強通用性又不失靈活呢?

二孤澎、使用Ourbatis輔助Mybatis

Ourbatis是一款Mybatis開發(fā)增強工具届氢,小巧簡潔,項目地址:

特性:

  • 1覆旭、簡潔方便退子,可以讓Mybatis無XML化開發(fā)。
  • 2型将、優(yōu)雅解耦寂祥,通用和自定義的SQL標簽完全隔離,讓維護更加輕松七兜。
  • 3丸凭、無侵入性,Mybatis和Ourbatis可同時使用腕铸,配置簡潔惜犀。
  • 4、靈活可控恬惯,通用模板可自定義及擴展向拆。
  • 5、部署快捷酪耳,只需要一個依賴浓恳,兩個配置,即可直接運行碗暗。
  • 6颈将、多數(shù)據(jù)源,在多數(shù)據(jù)源環(huán)境下也可以照常使用言疗。

關于Ourbatis使用的一個小Demo

環(huán)境:

  • Spring Boot 2.0.5.RELEASE
  • Ourbatis 1.0.5
  • JAVA 8
  • Mysql

Spring Boot 2.0.5.RELEASE版本為例晴圾,在可以正常使用Mybatis的項目中,pom.xml添加如下依賴:

   <dependency>
           <groupId>com.smallnico</groupId>
           <artifactId>ourbatis-spring-boot-starter</artifactId>
           <version>1.0.5</version>
   </dependency>

在配置文件中增加一下配置:

ourbatis.domain-locations=實體類所在包名

接下來噪奄,Mapper接口只需要繼承SimpleMapper即可:

import org.nico.ourbatis.domain.User;
public interface UserMapper extends SimpleMapper<User, Integer>{
}

至此死姚,一個使用Ourbatis的簡單應用已經(jīng)部署起來了人乓,之后,你就可以使用一些Ourbatis默認的通用操作方法:

    public T selectById(K key);
    
    public T selectEntity(T condition);
    
    public List<T> selectList(T condition);
    
    public long selectCount(Object condition);
    
    public List<T> selectPage(Page<Object> page);
    
    default PageResult<T> selectPageResult(Page<Object> page){
        long total = selectCount(page.getEntity());
        List<T> results = null;
        if(total > 0) {
            results = selectPage(page);
        }
        return new PageResult<>(total, results);
    }
    
    public K selectId(T condition);
    
    public List<K> selectIds(T condition);
    
    public int insert(T entity);
    
    public int insertSelective(T entity);
    
    public int insertBatch(List<T> list);
    
    public int update(T entity);
    
    public int updateSelective(T entity);
    
    public int updateBatch(List<T> list);
    
    public int delete(T condition);
    
    public int deleteById(K key);
    
    public int deleteBatch(List<K> list);

Mapper自定義方法

在很多場景中都毒,我們使用以上的自帶的通用方法遠遠不能滿足我們的需求色罚,我們往往需要額外擴展新的Mapper方法、XML標簽账劲,我們使用了Ourbatis之后該如何實現(xiàn)呢戳护?

首先看一下我們的需求,在上述Demo中瀑焦,我們在UserMapper中增加一個方法selectNameById

import org.nico.ourbatis.domain.User;
public interface UserMapper extends SimpleMapper<User, Integer>{
    public String selectNameById(Integer userId);
}

和Mybatis一樣腌且,需要在resources資源目錄下新建一個文件夾ourbatis-mappers,然后在其中新建一個XML文件榛瓮,命名規(guī)則為:

DomainClassSimpleName + Mapper.xml

其中DomainClassSimpleName就是我們實體類的類名铺董,這里是為User,那么新建的XML名為UserMapper.xml榆芦。

src/main/resources
 - ourbatis-mappers
   - UserMapper.xml

之后柄粹,打開UserMapper.xml喘鸟,開始編寫Mapper中selectNameById方法對應的標簽:

<select id="selectNameById" resultType="java.lang.String">
    select name from user where id = #{userId}
</select>

注意匆绣,整個文件中只需要寫標簽就行了,其他的什么都不需要什黑,這是為什么呢崎淳?深入之后你就會明白,這里先不多說愕把!

接下來拣凹,就沒有接下來了,可以直接使用selectNameById方法了恨豁。

深入了解Ourbatis

ourbatis 流程圖

當服務啟動的時候嚣镜,Ourbatis首先會掃描ourbatis.domain-locations配置包下的所有實體類,將之映射為與之對應的表結構數(shù)據(jù):

ourbatis Mapping

然后通過ourbatis.xml的渲染橘蜜,生成一個又一個的XML文件菊匿,最后將之重新Build到Mybatis容器中!

整個過程分為兩個核心點:

  • 1计福、映射實體類為元數(shù)據(jù)
  • 2跌捆、使用ourbatis.xml渲染元數(shù)據(jù)為XML文件

我會一一介紹之~

映射實體類為元數(shù)據(jù)

在映射時,我們要根據(jù)自己數(shù)據(jù)庫字段命名的風格去調整映射規(guī)則象颖,就需要在第1個核心點中去做處理佩厚,Ourbatis使用包裝器來完成:

public interface Wrapper<T> {
    public String wrapping(T value);
}

對于需要映射的字段,如表名表字段名说订,它們都將會經(jīng)過一個包裝器鏈條的處理之后再投入到ourbatis.xml中做渲染抄瓦,這樣就使得我們可以自定義包裝器出更換映射的字段格式潮瓶,具體方式可以參考官方Wiki:Wrapper包裝器

使用ourbatis.xml渲染元數(shù)據(jù)為XML文件

而在于第2個核心點中,Ourbatis通過自定義標簽做模板渲染钙姊,我們可以先看一下官方默認的ourbatis.xml內(nèi)部構造:

<?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="@{mapperClassName}">
    <resultMap id="BaseResultMap" type="@{domainClassName}">
        <ourbatis:foreach list="primaryColumns" var="elem">
            <id column="@{elem.jdbcName}" property="@{elem.javaName}" />
        </ourbatis:foreach>
        <ourbatis:foreach list="normalColumns" var="elem">
            <result column="@{elem.jdbcName}" property="@{elem.javaName}" />
        </ourbatis:foreach>
    </resultMap>

    <sql id="Base_Column_List">
        <ourbatis:foreach list="allColumns" var="elem"
            split=",">
            `@{elem.jdbcName}`
        </ourbatis:foreach>
    </sql>

    <select id="selectById" parameterType="java.lang.Object"
        resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from @{tableName}
        where 1 = 1
        <ourbatis:foreach list="primaryColumns" var="elem">
            and `@{elem.jdbcName}` = #{@{elem.javaName}}
        </ourbatis:foreach>
    </select>

    <select id="selectEntity" parameterType="@{domainClassName}"
        resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from @{tableName}
        where 1 = 1
        <ourbatis:foreach list="allColumns" var="elem">
            <if test="@{elem.javaName} != null">
                and `@{elem.jdbcName}` = #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
        limit 1
    </select>

    <select id="selectCount" parameterType="@{domainClassName}"
        resultType="long">
        select count(0)
        from @{tableName}
        where 1 = 1
        <ourbatis:foreach list="allColumns" var="elem">
            <if test="@{elem.javaName} != null">
                and `@{elem.jdbcName}` = #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
        limit 1
    </select>

    <select id="selectPage"
        parameterType="org.nico.ourbatis.entity.Page"
        resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from @{tableName}
        where 1 = 1
        <if test="entity != null">
            <ourbatis:foreach list="allColumns" var="elem">
                <if test="entity.@{elem.javaName} != null">
                    and `@{elem.jdbcName}` = #{entity.@{elem.javaName}}
                </if>
            </ourbatis:foreach>
        </if>
        <if test="orderBy != null">
            order by ${orderBy}
        </if>
        <if test="start != null and end != null">
            limit ${start},${end}
        </if>
    </select>

    <select id="selectList" parameterType="@{domainClassName}"
        resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List" />
        from @{tableName}
        where 1 = 1
        <ourbatis:foreach list="allColumns" var="elem">
            <if test="@{elem.javaName} != null">
                and `@{elem.jdbcName}` = #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
    </select>

    <select id="selectId" parameterType="@{domainClassName}"
        resultType="java.lang.Object">
        select
        <ourbatis:foreach list="primaryColumns" var="elem"
            split=",">
            `@{elem.jdbcName}`
        </ourbatis:foreach>
        from @{tableName}
        where 1 = 1
        <ourbatis:foreach list="allColumns" var="elem">
            <if test="@{elem.javaName} != null">
                and `@{elem.jdbcName}` = #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
        limit 1
    </select>

    <select id="selectIds" parameterType="@{domainClassName}"
        resultType="java.lang.Object">
        select
        <ourbatis:foreach list="primaryColumns" var="elem"
            split=",">
            `@{elem.jdbcName}`
        </ourbatis:foreach>
        from @{tableName}
        where 1 = 1
        <ourbatis:foreach list="normalColumns" var="elem">
            <if test="@{elem.javaName} != null">
                and `@{elem.jdbcName}` = #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
    </select>

    <delete id="deleteById" parameterType="java.lang.Object">
        delete
        from @{tableName}
        where 1=1
        <ourbatis:foreach list="primaryColumns" var="elem">
            and `@{elem.jdbcName}` = #{@{elem.javaName}}
        </ourbatis:foreach>
    </delete>

    <insert id="insert" keyProperty="@{primaryColumns.0.jdbcName}"
        useGeneratedKeys="true" parameterType="@{domainClassName}">
        insert into @{tableName}
        (
        <include refid="Base_Column_List" />
        )
        values (
        <ourbatis:foreach list="allColumns" var="elem"
            split=",">
            #{@{elem.javaName}}
        </ourbatis:foreach>
        )
    </insert>

    <insert id="insertSelective"
        keyProperty="@{primaryColumns.0.jdbcName}" useGeneratedKeys="true"
        parameterType="@{domainClassName}">
        insert into @{tableName}
        (
        <ourbatis:foreach list="primaryColumns" var="elem"
            split=",">
            `@{elem.jdbcName}`
        </ourbatis:foreach>
        <ourbatis:foreach list="normalColumns" var="elem">
            <if test="@{elem.javaName} != null">
                ,`@{elem.jdbcName}`
            </if>
        </ourbatis:foreach>
        )
        values (
        <ourbatis:foreach list="primaryColumns" var="elem">
            #{@{elem.javaName}}
        </ourbatis:foreach>
        <ourbatis:foreach list="normalColumns" var="elem">
            <if test="@{elem.javaName} != null">
                , #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
        )
    </insert>

    <insert id="insertBatch"
        keyProperty="@{primaryColumns.0.jdbcName}" useGeneratedKeys="true"
        parameterType="java.util.List">
        insert into @{tableName}
        (
        <include refid="Base_Column_List" />
        )
        values
        <foreach collection="list" index="index" item="item"
            separator=",">
            (
            <ourbatis:foreach list="allColumns" var="elem"
                split=",">
                #{item.@{elem.javaName}}
            </ourbatis:foreach>
            )
        </foreach>
    </insert>

    <update id="update" parameterType="@{domainClassName}">
        update @{tableName}
        <set>
            <ourbatis:foreach list="normalColumns" var="elem"
                split=",">
                `@{elem.jdbcName}` = #{@{elem.javaName}}
            </ourbatis:foreach>
        </set>
        where 1=1
        <ourbatis:foreach list="primaryColumns" var="elem">
            and `@{elem.jdbcName}` = #{@{elem.javaName}}
        </ourbatis:foreach>
    </update>

    <update id="updateSelective" parameterType="@{domainClassName}">
        update @{tableName}
        <set>
            <ourbatis:foreach list="primaryColumns" var="elem"
                split=",">
                `@{elem.jdbcName}` = #{@{elem.javaName}}
            </ourbatis:foreach>
            <ourbatis:foreach list="normalColumns" var="elem">
                <if test="@{elem.javaName} != null">
                    ,`@{elem.jdbcName}` = #{@{elem.javaName}}
                </if>
            </ourbatis:foreach>
        </set>
        where 1=1
        <ourbatis:foreach list="primaryColumns" var="elem">
            and `@{elem.jdbcName}` = #{@{elem.javaName}}
        </ourbatis:foreach>
    </update>

    <update id="updateBatch" parameterType="java.util.List">
        <foreach collection="list" index="index" item="item"
            separator=";">
            update @{tableName}
            <set>
                <ourbatis:foreach list="normalColumns" var="elem"
                    split=",">
                    `@{elem.jdbcName}` = #{item.@{elem.javaName}}
                </ourbatis:foreach>
            </set>
            where 1=1
            <ourbatis:foreach list="primaryColumns" var="elem">
                and `@{elem.jdbcName}` = #{item.@{elem.javaName}}
            </ourbatis:foreach>
        </foreach>
    </update>

    <delete id="deleteBatch" parameterType="java.util.List">
        delete from @{tableName} where @{primaryColumns.0.jdbcName} in
        <foreach close=")" collection="list" index="index" item="item"
            open="(" separator=",">
            #{item}
        </foreach>
    </delete>

    <delete id="delete" parameterType="@{domainClassName}">
        delete from @{tableName} where 1 = 1
        <ourbatis:foreach list="allColumns" var="elem">
            <if test="@{elem.javaName} != null">
                and `@{elem.jdbcName}` = #{@{elem.javaName}}
            </if>
        </ourbatis:foreach>
    </delete>

    <ourbatis:ref path="classpath:ourbatis-mappers/@{domainSimpleClassName}Mapper.xml" />
</mapper>

可以看出來黎棠,ourbatis.xml內(nèi)容類似于原生的Mybatis的XML,不同的是晒他,有兩個陌生的標簽:

  • ourbatis:foreach 對元數(shù)據(jù)中的列表進行循環(huán)渲染
  • ourbatis:ref 引入外界文件內(nèi)容

這是Ourbatis中獨有的標簽璧南,Ourbatis也提供著對應的入口讓我們?nèi)プ远x標簽:

Class: org.nico.ourbatis.Ourbatis
Field: 
public static final Map<String, AssistAdapter> ASSIST_ADAPTERS = new HashMap<String, AssistAdapter>(){
        private static final long serialVersionUID = 1L;
        {
            put("ourbatis:foreach", new ForeachAdapter());
            put("ourbatis:ref", new RefAdapter());
        }
    };

我們可以修改org.nico.ourbatis.Ourbatis類中的靜態(tài)參數(shù)ASSIST_ADAPTERS去刪除、更新和添加自定義標簽立镶,需要實現(xiàn)一個標簽適配器壁袄,我們可以看一下最簡單的RefAdapter適配器的實現(xiàn):

public class RefAdapter extends AssistAdapter{
    @Override
    public String adapter(Map<String, Object> datas, NoelRender render, Document document) {
        String path = render.rending(datas, document.getParameter("path"), "domainSimpleClassName");
        String result =  StreamUtils.convertToString(path.replaceAll("classpath:", ""));
        return result == null ? "" : result.trim();
    }
}

Ourbatis中只定義了上述兩個自定義標簽已足夠滿足需求,通過foreach標簽媚媒,將元數(shù)據(jù)中的集合遍歷渲染嗜逻,通過ref標簽引入外界資源,也就是我們之前所說的對Mapper接口中方法的擴展缭召!

<ourbatis:ref path="classpath:ourbatis-mappers/@{domainSimpleClassName}Mapper.xml" />

其中的path就是當前項目classpath路徑的相對路徑栈顷,而@{domainSimpleClassName}就代表著實體類的類名,更多的系統(tǒng)參數(shù)可以參考Wiki:元數(shù)據(jù)映射

通過這種模板渲染的機制嵌巷,Ourbatis是相當靈活的萄凤,我們不僅可以通過引入外部文件進行擴展,當我們需要添加或修改通用方法時搪哪,我們可以可以自定義ourbatis.xml的內(nèi)容靡努,如何做到呢?復制一份將之放在資源目錄下就可以了晓折!

看到這里惑朦,相信大家已經(jīng)知道Ourbatis的基本原理已經(jīng)使用方式,我就再次不多說了漓概,更多細節(jié)可以去官方Wiki中閱讀:Ourbtis Wiki

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末漾月,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子胃珍,更是在濱河造成了極大的恐慌梁肿,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,423評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件堂鲜,死亡現(xiàn)場離奇詭異栈雳,居然都是意外死亡,警方通過查閱死者的電腦和手機缔莲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,147評論 2 385
  • 文/潘曉璐 我一進店門哥纫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事蛀骇⊙崦耄” “怎么了?”我有些...
    開封第一講書人閱讀 157,019評論 0 348
  • 文/不壞的土叔 我叫張陵擅憔,是天一觀的道長鸵闪。 經(jīng)常有香客問我,道長暑诸,這世上最難降的妖魔是什么蚌讼? 我笑而不...
    開封第一講書人閱讀 56,443評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮个榕,結果婚禮上篡石,老公的妹妹穿的比我還像新娘。我一直安慰自己西采,他們只是感情好凰萨,可當我...
    茶點故事閱讀 65,535評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著械馆,像睡著了一般胖眷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霹崎,一...
    開封第一講書人閱讀 49,798評論 1 290
  • 那天珊搀,我揣著相機與錄音,去河邊找鬼仿畸。 笑死食棕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的错沽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,941評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼眶拉,長吁一口氣:“原來是場噩夢啊……” “哼千埃!你這毒婦竟也來了?” 一聲冷哼從身側響起忆植,我...
    開封第一講書人閱讀 37,704評論 0 266
  • 序言:老撾萬榮一對情侶失蹤放可,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后朝刊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耀里,經(jīng)...
    沈念sama閱讀 44,152評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,494評論 2 327
  • 正文 我和宋清朗相戀三年拾氓,在試婚紗的時候發(fā)現(xiàn)自己被綠了冯挎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,629評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡咙鞍,死狀恐怖房官,靈堂內(nèi)的尸體忽然破棺而出趾徽,到底是詐尸還是另有隱情,我是刑警寧澤翰守,帶...
    沈念sama閱讀 34,295評論 4 329
  • 正文 年R本政府宣布孵奶,位于F島的核電站,受9級特大地震影響蜡峰,放射性物質發(fā)生泄漏了袁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,901評論 3 313
  • 文/蒙蒙 一湿颅、第九天 我趴在偏房一處隱蔽的房頂上張望早像。 院中可真熱鬧,春花似錦肖爵、人聲如沸卢鹦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冀自。三九已至,卻和暖如春秒啦,著一層夾襖步出監(jiān)牢的瞬間熬粗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,978評論 1 266
  • 我被黑心中介騙來泰國打工余境, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驻呐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,333評論 2 360
  • 正文 我出身青樓芳来,卻偏偏與公主長得像含末,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子即舌,可洞房花燭夜當晚...
    茶點故事閱讀 43,499評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1 Mybatis入門 1.1 單獨使用jdbc編程問題總結 1.1.1 jdbc程序 上邊使...
    哇哈哈E閱讀 3,295評論 0 38
  • 1. 簡介 1.1 什么是 MyBatis 佣盒? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,454評論 0 4
  • 彩虹花園里開了三朵花顽聂。 第一朵:彩虹晨讀花肥惭。 第二朵是:彩虹整本書閱讀花 圖片發(fā)自簡書App圖片發(fā)自簡書App圖片...
    陜縣2001方文菊閱讀 328評論 0 5
  • 女巫念著咒語,嘰里咕嚕紊搪,變蜜葱,小王子變成了一只青蛙,小茜茜把一個塑膠人偶盡量折疊起來耀石,使它看起來像只青蛙牵囤,但只要一放...
    逆風1閱讀 445評論 0 2
  • 想了解大家旅行的狀態(tài),有什么特別的際遇,所以在微信里問了一圈奔浅。每個人的答案都很精彩馆纳。舍不得刪掉,所以字很多汹桦。 感謝...
    莫知西東閱讀 936評論 0 2