fastmybatis開發(fā)文檔

簡(jiǎn)介

fastmybatis是一個(gè)mybatis開發(fā)框架,其宗旨為:簡(jiǎn)單、快速、有效。

  • 零配置快速上手
  • 無需編寫xml文件即可完成CRUD操作
  • 支持mysql,sqlserver,oracle,postgresql,sqlite
  • 支持自定義sql,sql語(yǔ)句可寫在注解中或xml中
  • 支持與spring-boot集成,依賴starter即可
  • 輕量級(jí),無侵入性,是官方mybatis的一種擴(kuò)展

快速開始(springboot)

  • 新建一個(gè)springboot項(xiàng)目
  • pom.xml添加fastmybatis-spring-boot-starter
<dependency>
    <groupId>net.oschina.durcframework</groupId>
    <artifactId>fastmybatis-spring-boot-starter</artifactId>
    <version>最新版本(見changelog.md)</version>
</dependency>
  • 假設(shè)數(shù)據(jù)庫(kù)有張t_user表,添加對(duì)應(yīng)的實(shí)體類TUser.java和MapperTUserMapper.java(可用fastmybatis-generator來生成)
  • application.propertis中配置數(shù)據(jù)庫(kù)連接
  • 編寫測(cè)試用例
@Autowired
TUserMapper mapper;
    
// 根據(jù)主鍵查詢
@Test
public void testGetById() {
    TUser user = mapper.getById(3);
    System.out.println(user);
}

查詢

本小節(jié)主要講解fastmybatis的查詢功能。fastmybatis提供豐富的查詢方式,滿足日常查詢所需。

分頁(yè)查詢

方式1

前端傳遞兩個(gè)分頁(yè)參數(shù)pageIndex,pageSize

    // http://localhost:8080/page1?pageIndex=1&pageSize=10
    @GetMapping("page1")
    public List<TUser> page1(int pageIndex,int pageSize) {
        Query query = new Query();
        query.page(pageIndex, pageSize);
        List<TUser> list = mapper.list(query);
        return list;
    }

方式2

PageParam里面封裝了pageIndex,pageSize參數(shù)

    // http://localhost:8080/page2?pageIndex=1&pageSize=10
    @GetMapping("page2")
    public List<TUser> page2(PageParam param) {
        Query query = param.toQuery();
        List<TUser> list = mapper.list(query);
        return list;
    }

返回結(jié)果集和總記錄數(shù)

方式1和方式2只能查詢結(jié)果集,通常我們查詢還需返回記錄總數(shù)并返回給前端,fastmybatis的處理方式如下:

// http://localhost:8080/page3?pageIndex=1&pageSize=10
    @GetMapping("page3")
    public Map<String,Object> page3(PageParam param) {
        Query query = param.toQuery();
        List<TUser> list = mapper.list(query);
        long total = mapper.getCount(query);
        
        Map<String,Object> result = new HashMap<String, Object>();
        result.put("list", list);
        result.put("total", total);
        
        return result;
    }

fastmybatis提供一種更簡(jiǎn)潔的方式來處理:

// http://localhost:8080/page4?pageIndex=1&pageSize=10
    @GetMapping("page4")
    public PageInfo<TUser> page4(PageParam param) {
        PageInfo<TUser> pageInfo = MapperUtil.query(mapper, query);
        return result;
    }

PageInfo里面包含了List,total信息,還包含了一些額外信息,完整數(shù)據(jù)如下:

{
    "currentPageIndex": 1, // 當(dāng)前頁(yè)
    "firstPageIndex": 1, // 首頁(yè)
    "lastPageIndex": 2, // 尾頁(yè)
    "list": [     // 結(jié)果集
        {},
        {}
    ],
    "nextPageIndex": 2, // 下一頁(yè)
    "pageCount": 2, // 總頁(yè)數(shù)
    "pageIndex": 1, // 當(dāng)前頁(yè)
    "pageSize": 10, // 每頁(yè)記錄數(shù)
    "prePageIndex": 1, // 上一頁(yè)
    "start": 0,
    "total": 20 // 總記錄數(shù)
}

根據(jù)參數(shù)字段查詢

查詢姓名為張三的用戶

// http://localhost:8080/sch?username=張三
    @GetMapping("sch")
    public List<TUser> sch(String username) {
        Query query = new Query();
        query.eq("username", username);
        List<TUser> list = mapper.list(query);
        return list;
    }

查詢姓名為張三并且擁有的錢大于100塊

// http://localhost:8080/sch2?username=張三
    @GetMapping("sch2")
    public List<TUser> sch2(String username) {
        Query query = new Query();
        query.eq("username", username).gt("money", 100);
        List<TUser> list = mapper.list(query);
        return list;
    }

查詢姓名為張三并帶分頁(yè)

// http://localhost:8080/sch3?username=張三&pageIndex=1&pageSize=5
    @GetMapping("sch3")
    public List<TUser> sch3(String username,PageParam param) {
        Query query = param.toQuery();
        query.eq("username", username);
        List<TUser> list = mapper.list(query);
        return list;
    }

查詢錢最多的前三名

// http://localhost:8080/sch4
    @GetMapping("sch4")
    public List<TUser> sch4() {
        Query query = new Query();
        query.orderby("money", Sort.DESC) // 按金額降序
            .page(1, 3);
        List<TUser> list = mapper.list(query);
        return list;
    }

將參數(shù)放在對(duì)象中查詢

// http://localhost:8080/sch5?username=張三
    @GetMapping("sch5")
    public List<TUser> sch5(UserParam userParam) {
        Query query = userParam.toQuery();
        query.eq("username", userParam.getUsername());
        List<TUser> list = mapper.list(query);
        return list;
    }

UserParam繼承PageSortParam類,表示支持分頁(yè)和排序查詢

使用普通bean查詢

假設(shè)有個(gè)User類如下

public class User {
    private Integer id;
    private String userName;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

我們將這個(gè)類作為查詢參數(shù),那么在springmvc中可以這樣寫:

@GetMapping(path="findUserBean.do")
public List<User> findUser(User user) {
    Query query = Query.build(user);
    List<User> list = dao.find(query);
    return list;
}

Query query = Query.build(user);這句是將User中的屬性轉(zhuǎn)換成對(duì)應(yīng)條件,假設(shè)userName的值為"jim",那么會(huì)封裝成一個(gè)條件where user_name='jim'

瀏覽器輸入鏈接:http://localhost:8080/fastmybatis-springmvc/findUserBean.do?userName=jim
后臺(tái)將會(huì)執(zhí)行如下SQL:

SELECT id,user_name FROM user t WHERE t.user_name = ?

?的值為jim

@Condition注解

@Condition注解用來強(qiáng)化查詢,有了這個(gè)注解可以生成各種查詢條件。

@Condition注解有三個(gè)屬性:

  • joint:表達(dá)式之間的連接符,AND|OR,默認(rèn)AND
  • column:數(shù)據(jù)庫(kù)字段名,可選
  • operator:連接符枚舉,存放了等于、大于、小于等連接符

如果要查詢id大于2的用戶只需在get方法上加上一個(gè)@Condition注解即可:

@Condition(operator=Operator.gt)
public Integer getId() {
    return this.id;
}

這樣,當(dāng)id有值時(shí),會(huì)封裝成一個(gè)where id>2的條件

  • 需要注意的是,如果不指定column屬性,系統(tǒng)會(huì)默認(rèn)取get方法中屬性名,然后轉(zhuǎn)換成數(shù)據(jù)庫(kù)字段名。如果需要指定數(shù)據(jù)庫(kù)字段名的話,可以使用@Condition的column屬性。

public Integer get++UserName++() {
return this.userName;
}

這種情況下會(huì)取下劃線部分字段,然后轉(zhuǎn)換成數(shù)據(jù)庫(kù)字段名。

@Condition(column="username") // 顯示指定字段名
public Integer getUserName() {
    return this.userName;
}

使用@Condition可以生產(chǎn)更加靈活的條件查詢,比如需要查詢?nèi)掌跒?017-12-1~2017-12-10日的記錄,我們可以這樣寫:

@Condition(column="add_date",operator=Operator.ge)
public Date getStartDate() {
    return this.startDate;
}

@Condition(column="add_date",operator=Operator.lt)
public Date getEndDate() {
    return this.endDate;
}

轉(zhuǎn)換成SQL語(yǔ)句:

t.add_date>='2017-12-1' AND t.add_date<'2017-12-10'

IN查詢

假設(shè)前端頁(yè)面?zhèn)鱽矶鄠€(gè)值比如checkbox勾選多個(gè)id=[1,2],那么我們?cè)赨ser類里面可以用Integer[]或List<Integer>來接收.

private Integer[] idArr;

public void setIdArr(Integer[] idArr) {this.idArr = idArr;}

@Condition(column="id")
public Integer[] getIdArr() {return this.idArr;}

這樣會(huì)生成where id IN(1,2)條件。

排序查詢

// 根據(jù)添加時(shí)間倒序

Query query = new Query();
query.orderby("create_time",Sort.DESC);
dao.find(query);

多表關(guān)聯(lián)查詢

多表關(guān)聯(lián)查詢使用的地方很多,比如需要關(guān)聯(lián)第二張表,獲取第二張表的幾個(gè)字段,然后返回給前端。

fastmybatis的用法如下:

假如我們需要關(guān)聯(lián)第二張表user_info,篩選出user_info中的城市為杭州的數(shù)據(jù)。

Query query = new Query()
        // 左連接查詢,主表的alias默認(rèn)為t
        .join("LEFT JOIN user_info t2 ON t.id = t2.user_id").page(1, 5)
        .eq("t2.city","杭州");

List<TUser> list = mapper.list(query);

System.out.println("==============");
for (TUser user : list) {
    System.out.println(user.getId() + " " + user.getUsername());
}
System.out.println("==============");

多表關(guān)聯(lián)返回指定字段

有時(shí)候不需要全部字段,需要取表1中的幾個(gè)字段,然后取表2中的幾個(gè)字段,fastmybatis實(shí)現(xiàn)方式如下:

Query query = new Query();
// 左連接查詢,主表的alias默認(rèn)為t
query.join("LEFT JOIN user_info t2 ON t.id = t2.user_id");
// 指定返回字段
List<String> column = Arrays.asList("t2.user_id as userId", "t.username", "t2.city");
// 查詢結(jié)果返回到map中
List<Map<String, Object>> mapList = mapper.listMap(column, query);
// 再將map轉(zhuǎn)換成實(shí)體bean
List<UserInfoVo> list = MyBeanUtil.mapListToObjList(mapList, UserInfoVo.class);

執(zhí)行的SQL語(yǔ)句對(duì)應(yīng)如下:

SELECT t2.user_id as userId , t.username , t2.city
FROM `t_user` t 
LEFT JOIN user_info t2 ON t.id = t2.user_id

使用@Select查詢

@Select注解是mybatis官方提供的一個(gè)功能,fastmybatis可以理解為是官方的一種擴(kuò)展,因此同樣支持此功能。
在Mapper中添加如下代碼:

@Select("select * from t_user where id=#{id}")
TUser selectById(@Param("id") int id);

編寫測(cè)試用例

@Test
public void testSelectById() {
    TUser user = dao.selectById(3);

    System.out.println(user.getUsername());
}

對(duì)于簡(jiǎn)單的SQL,可以用這種方式實(shí)現(xiàn)。除了@Select之外,還有@Update,@Insert,@Delete,這里就不多做演示了。

Query類詳解

Query是一個(gè)查詢參數(shù)類,配合Mapper一起使用。

參數(shù)介紹

Query里面封裝了一系列查詢參數(shù),主要分為以下幾類:

  • 分頁(yè)參數(shù):設(shè)置分頁(yè)
  • 排序參數(shù):設(shè)置排序字段
  • 條件參數(shù):設(shè)置查詢條件
  • 字段參數(shù):可返回指定字段

下面逐個(gè)講解每個(gè)參數(shù)的用法。

分頁(yè)參數(shù)

一般來說分頁(yè)的使用比較簡(jiǎn)單,通常是兩個(gè)參數(shù),
pageIndex:當(dāng)前頁(yè)索引,pageSize:每頁(yè)幾條數(shù)據(jù)。
Query類使用page(pageIdnex, pageSize)方法來設(shè)置。
假如我們要查詢第二頁(yè),每頁(yè)10條數(shù)據(jù),代碼可以這樣寫:

Query query = new Query();
query.page(2, 10);
List<User> list = dao.find(query);

如果要實(shí)現(xiàn)不規(guī)則分頁(yè),可以這樣寫:

Query query = new Query();
query.limit(3, 5) // 對(duì)應(yīng)mysql:limit 3,5

排序參數(shù)

orderby(String sortname, Sort sort)

其中sortname為數(shù)據(jù)庫(kù)字段,非javaBean屬性

  • orderby(String sortname, Sort sort)則可以指定排序方式,Sort為排序方式枚舉
    假如要按照添加時(shí)間倒序,可以這樣寫:
Query query = new Query();
query.orderby("create_time",Sort.DESC);
mapper.list(query);

添加多個(gè)排序字段可以在后面追加:

query.orderby("create_time",Sort.DESC).orderby("id",Sort.ASC);

條件參數(shù)

條件參數(shù)是用的最多一個(gè),因?yàn)樵诓樵冎型枰尤敫鞣N條件。
fastmybatis在條件查詢上面做了一些封裝,這里不做太多講解,只講下基本的用法,以后會(huì)單獨(dú)開一篇文章來介紹。感興趣的同學(xué)可以自行查看源碼,也不難理解。

條件參數(shù)使用非常簡(jiǎn)單,Query對(duì)象封裝一系列常用條件查詢。

  • 等值查詢eq(String columnName, Object value),columnName為數(shù)據(jù)庫(kù)字段名,value為查詢的值
    假設(shè)我們要查詢姓名為張三的用戶,可以這樣寫:
Query query = new Query();
query.eq("username","張三");
List<User> list = mapper.list(query);

通過方法名即可知道eq表示等于'=',同理lt表示小于<,gt表示大于>

查詢方式 說明
eq 等于=
gt 大于>
lt 小于<
ge 大于等于>=
le 小于等于<=
notEq 不等于<>
like 模糊查詢
in in()查詢
notIn not in()查詢
isNull NULL值查詢
notNull IS NOT NULL
notEmpty 字段不為空,非NULL且有內(nèi)容
isEmpty 字段為NULL或者為''

如果上述方法還不能滿足查詢需求的話,我們可以使用自定sql的方式來編寫查詢條件,方法為:

Query query = new Query();
query.sql(" username='Jim' OR username='Tom'");

注意:sql()方法不會(huì)處理sql注入問題,因此盡量少用。

自定義SQL

方式1

直接寫在Mapper.java中

public interface TUserMapper extends CrudMapper<TUser, Integer> {

    // 自定義sql,官方自帶,不需要寫xml
    /**
     * 修改用戶名
     * @param id
     * @param username
     * @return 返回影響行數(shù)
     */
    @Update("update t_user set username = #{username} where id = #{id}")
    int updateById(@Param("id") int id, @Param("username") String username);
 
}

簡(jiǎn)單SQL可采用這種形式。

方式2

fastmybatis提供的Mapper已經(jīng)滿足大部分的操作需求,但是有些復(fù)雜的sql語(yǔ)句還是需要寫在xml文件中。fastmybatis同樣支持將sql語(yǔ)句寫在xml中,具體配置如下:

  • 在application.properties添加一句,指定xml文件存放路徑
mybatis.mapper-locations=classpath:/mybatis/mapper/*.xml
  • 在resources/mybatis/mapper目錄下新建一個(gè)xml文件TUserMapper.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="com.mayapp.mapper.TUserMapper">
    
    <select id="selectByName" parameterType="String" resultMap="baseResultMap">
        select * from t_user t where t.username = #{username} limit 1
    </select>
    
</mapper>

這個(gè)xml文件跟其它的mybatis配置文件一樣,baseResultMap沒有看到定義,但是確實(shí)存在,因?yàn)檫@個(gè)是fastmybatis提供的一個(gè)內(nèi)置resultMap。

  • 在TUseroMapper.java中添加:
TUser selectByName(@Param("username")String username);
  • 編寫單元測(cè)試用例
@Test
public void testSelectByName() {
    TUser user = dao.selectByName("張三");

    System.out.println(user.getUsername());
}
    

多文件同一個(gè)namespace

在以往的開發(fā)過程中,一個(gè)Mapper對(duì)應(yīng)一個(gè)xml文件(namespace)。如果多人同時(shí)在一個(gè)xml中寫SQL的話會(huì)造成各種沖突(雖然能夠最終被解決)。

fastmybatis打破這種常規(guī),允許不同的xml文件定義相同的namespace,程序啟動(dòng)時(shí)會(huì)自動(dòng)把他們的內(nèi)容合并到同一個(gè)文件當(dāng)中去。

  • 張三的UserMapper_zs.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.mayapp.mapper.TUserMapper">
    
    <select id="selectByName" parameterType="String" resultMap="baseResultMap">
        select * from t_user t where t.username = #{username} limit 1
    </select>
    
</mapper>
  • 李四的UserMapper_ls.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.mayapp.mapper.TUserMapper">
    
    <select id="updateUser" parameterType="String" resultMap="baseResultMap">
        update t_user set username = #{username} where id=#{id}
    </select>
    
</mapper>

最終會(huì)合并成

<?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.mayapp.mapper.TUserMapper">
    <!-- 張三部分 -->
    <select id="selectByName" parameterType="String" resultMap="baseResultMap">
        select * from t_user t where t.username = #{username} limit 1
    </select>
    
    <!-- 李四部分 -->
    <select id="updateUser" parameterType="String" resultMap="baseResultMap">
        update t_user set username = #{username} where id=#{id}
    </select>
    
</mapper>

這樣也體現(xiàn)了開閉原則,即新增一個(gè)功能只需要新增一個(gè)文件就行,不需要修改原來的文件。

如果SQL寫多了還可以把它們進(jìn)行分類,放到不同的xml中,便于管理。

注:合并動(dòng)作是在啟動(dòng)時(shí)進(jìn)行的,并不會(huì)生成一個(gè)真實(shí)的文件。

字段自動(dòng)填充

填充器設(shè)置

假設(shè)數(shù)據(jù)庫(kù)表里面有兩個(gè)時(shí)間字段gmt_create,gmt_update。

當(dāng)進(jìn)行insert操作時(shí)gmt_create,gmt_update字段需要更新。當(dāng)update時(shí),gmt_update字段需要更新。

通常的做法是通過Entity手動(dòng)設(shè)置:

User user = new User();
user.setGmtCreate(new Date());
user.setGmtUpdate(new Date());

因?yàn)楸碓O(shè)計(jì)的時(shí)候大部分都有這兩個(gè)字段,所以對(duì)每張表都進(jìn)行手動(dòng)設(shè)置的話很容易錯(cuò)加、漏加。
fastmybatis提供了兩個(gè)輔助類DateFillInsert和DateFillUpdate,用來處理添加修改時(shí)的時(shí)間字段自動(dòng)填充。配置了這兩個(gè)類之后,時(shí)間字段將會(huì)自動(dòng)設(shè)置。

配置方式如下:

EasymybatisConfig config = new EasymybatisConfig();

    config.setFills(Arrays.asList(
            new DateFillInsert()
            ,new DateFillUpdate()
            ));

在spring的xml中配置如下:

<bean id="sqlSessionFactory"
        class="com.gitee.fastmybatis.core.ext.SqlSessionFactoryBeanExt">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation">
            <value>classpath:mybatis/mybatisConfig.xml</value>
        </property>
        <property name="mapperLocations">
            <list>
                <value>classpath:mybatis/mapper/*.xml</value>
            </list>
        </property>
        
        <!-- 以下是附加屬性 -->
        
        <!-- dao所在的包名,跟MapperScannerConfigurer的basePackage一致 
            多個(gè)用;隔開
        -->
        <property name="basePackage" value="com.myapp.dao" />
        <property name="config">
            <bean class="com.gitee.fastmybatis.core.EasymybatisConfig">
                                <!-- 定義填充器 -->
                <property name="fills">
                    <list>
                                        <bean class="com.gitee.fastmybatis.core.support.DateFillInsert"/>
                                        <bean class="com.gitee.fastmybatis.core.support.DateFillUpdate"/>
                    </list>
                </property>
            </bean>
        </property>
    </bean>

springboot中可以這樣定義:

在application.properties中添加:

mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=
mybatis.fill.com.gitee.fastmybatis.core.support.DateFillUpdate=

如果要指定字段名,可以寫成:

mybatis.fill.com.gitee.fastmybatis.core.support.DateFillInsert=add_time

自定義填充器

除了使用fastmybatis默認(rèn)提供的填充之外,我們還可以自定義填充。

自定義填充類要繼承FillHandler<T>類。
<T> 表示填充字段類型,如Date,String,BigDecimal,Boolean。

實(shí)戰(zhàn)(springboot)

現(xiàn)在有個(gè)remark字段,需要在insert時(shí)初始化為“備注默認(rèn)內(nèi)容”,新建一個(gè)StringRemarkFill類如下:

public class StringRemarkFill extends FillHandler<String> {

    @Override
    public String getColumnName() {
        return "remark";
    }

    @Override
    public FillType getFillType() {
        return FillType.INSERT;
    }

    @Override
    protected Object getFillValue(String defaultValue) {
        return "備注默認(rèn)內(nèi)容";
    }

}

StringRemarkFill類中有三個(gè)重寫方法:

  • getColumnName() : 指定表字段名
  • getFillType() : 填充方式,F(xiàn)illType.INSERT:僅insert時(shí)填充; FillType.UPDATE:insert,update時(shí)填充
  • getFillValue(String defaultValue) :返回填充內(nèi)容

然后在application.properties中添加:

mybatis.fill.com.xx.StringRemarkFill=

這樣就配置完畢了,調(diào)用dao.save(user);時(shí)會(huì)自動(dòng)填充remark字段。

指定目標(biāo)類

上面說到StringRemarkFill填充器,它作用在所有實(shí)體類上,也就是說實(shí)體類如果有remark字段都會(huì)自動(dòng)填充。這樣顯然是不合理的,解決辦法是指定特定的實(shí)體類。只要重寫FillHandler類的getTargetEntityClasses()方法即可。

@Override
public Class<?>[] getTargetEntityClasses() {
    return new Class<?>[] { TUser.class };
}

這樣就表示作用在TUser類上,多個(gè)類可以追加。最終代碼如下:

public class StringRemarkFill extends FillHandler<String> {

    @Override
    public String getColumnName() {
        return "remark";
    }

    @Override
    public Class<?>[] getTargetEntityClasses() {
        return new Class<?>[] { TUser.class }; // 只作用在TUser類上
    }

    @Override
    public FillType getFillType() {
        return FillType.INSERT;
    }

    @Override
    protected Object getFillValue(String defaultValue) {
        return "備注默認(rèn)內(nèi)容"; // insert時(shí)填充的內(nèi)容
    }

}

關(guān)于自動(dòng)填充的原理是基于mybatis的TypeHandler實(shí)現(xiàn)的,這里就不多做介紹了。感興趣的同學(xué)可以查看FillHandler<T>源碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容