Spring Data JPA入門

[TOC]

SpringData JPA是spring基于ORM框架、JPA規(guī)范的基礎(chǔ)上封裝的一套JPA應(yīng)用框架诈乒,可以使開發(fā)者使用極簡(jiǎn)的代碼實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的訪問(wèn)和操作淑趾。它提供了包括增刪改查等在內(nèi)的基本功能,且易于擴(kuò)展岖圈。

springdata jpa鹦牛、jpa和hibernate三者關(guān)系

通俗來(lái)講springdata jpa是對(duì)jpa規(guī)范的一層封裝搞糕,hibernate實(shí)現(xiàn)了jpa規(guī)范。

java代碼----->springdata jpa ------>jpa規(guī)范------>hibernate------>jdbc ----->mysql數(shù)據(jù)庫(kù)

graph LR
A[java代碼] -->B(spring data jpa)
B --> |jpa規(guī)范| C(hibernate)
C -->|jdbc| D(mysql數(shù)據(jù)庫(kù))

我們使用java代碼調(diào)用springdata jpa的api曼追,springdata jpa封裝了jpa規(guī)范窍仰,并且內(nèi)部使用的是hibernate實(shí)現(xiàn),hibernate封裝了jdbc進(jìn)行數(shù)據(jù)庫(kù)操作礼殊。

入門案例

1驹吮、創(chuàng)建工程,導(dǎo)入依賴

    compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.3.Final'
    compile group: 'org.hibernate', name: 'hibernate-c3p0', version: '5.4.3.Final'
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.16'
    compile group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.1.9.RELEASE'
    testCompile group: 'org.springframework', name: 'spring-test', version: '5.1.8.RELEASE'

2晶伦、編寫spring配置文件

  • 配置spring相關(guān)

  • 數(shù)據(jù)源信息

  • jpa的實(shí)現(xiàn)方式

  • 配置要用到的實(shí)體類

  • 配置jpa實(shí)現(xiàn)方的配置信息

  • 配置事務(wù)管理器

  • 聲明式事務(wù)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--spring-->
    <!--配置spring的注解掃描-->
    <context:component-scan base-package="com.lxf"/>


    <!--spring data jpa-->
    <!--整合spring data jpa-->
    <jpa:repositories base-package="com.lxf.dao" entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager" />

    <!--創(chuàng)建實(shí)體管理器工廠,交給spring管理-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置數(shù)據(jù)源-->
        <property name="dataSource" ref="dataSource"/>
        <!--配置要掃描的包碟狞,實(shí)體所在包-->
        <property name="packagesToScan" value="com.lxf.entity"/>
        <!--配置jpa的實(shí)現(xiàn)方-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
        </property>

        <!--jpa的實(shí)現(xiàn)方的配置-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--數(shù)據(jù)庫(kù)類型-->
                <property name="database" value="MYSQL"/>
                <!--控制臺(tái)顯示sql語(yǔ)句-->
                <property name="showSql" value="true"/>
                <!--是否自動(dòng)創(chuàng)建數(shù)據(jù)庫(kù)表-->
                <property name="generateDdl" value="true"/>
                <!--數(shù)據(jù)庫(kù)方言-->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
            </bean>
        </property>

        <!--jpa方言:高級(jí)特性-->

    </bean>

    <!--數(shù)據(jù)源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/study?serverTimezone=GMT"/>
        <property name="user" value="root"/>
        <property name="password" value="crystal1024"/>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
    </bean>

    <!--配置事務(wù)管理器-->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>

    <!--聲明式事務(wù)-->
    
</beans>

3、創(chuàng)建實(shí)體類婚陪,編寫實(shí)體類和數(shù)據(jù)庫(kù)表關(guān)系映射

參考JPA規(guī)范族沃。

4、編寫dao層接口

  • 需要繼承兩個(gè)接口

    • JpaRepository:封裝了增刪改查分頁(yè)排序等基本操作近忙,具體可以看JpaRepository的父類
    graph TB
    A[Repository] -->B(CrudRepository)
    B --> C(PagingAndSortingRepository)
    C -->D(JpaRepository)
    
    • JpaSpecificationExecutor:封裝了標(biāo)準(zhǔn)查詢
  • 提供相應(yīng)的泛型

    • JpaRepository
      • 操作的實(shí)體類型
      • 實(shí)體中主鍵類型
    • JpaSpecificationExecutor
      • 操作的實(shí)體類型
public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor<User> {
}
  • 會(huì)通過(guò)動(dòng)態(tài)代理自動(dòng)生成相應(yīng)方法

5竭业、測(cè)試

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring_data_jpa.xml")
public class Test {
    @Autowired
    private UserDao userDao;

    @org.junit.Test
    public void textSave(){
        User user = new User();
        user.setName("小紅");
        user.setAge(22);
        User userResult = userDao.save(user);
        System.out.println(userResult);
    }
}

User{id=4, name='小紅', age=22, sex=null, address='null', phone='null'}

操作數(shù)據(jù)庫(kù)

調(diào)用spring data jpa的api

插入/更新

  • save方法:傳入的實(shí)體對(duì)象有主鍵則更新智润,沒有主鍵則插入及舍。
    @org.junit.Test
    public void testSave(){
        User user = new User();
        user.setName("小紅");
        user.setAge(22);
        User userResult = userDao.save(user);
        System.out.println(userResult);
    }

刪除

  • delete系列方法
    @org.junit.Test
    public void testDelete(){
        userDao.deleteById(2);
    }

查詢

  • count:統(tǒng)計(jì)

  • exists系列方法:數(shù)據(jù)庫(kù)中是否存在

  • find系列方法:立即加載

  • getOne:延遲加載,返回的是一個(gè)動(dòng)態(tài)代理對(duì)象

    @org.junit.Test
    public void testFindOne(){
//        Optional<User> user = userDao.findById(2);
//        System.out.println(user.get());
        User user = userDao.getOne(2);
        System.out.println(user);
    }

    @org.junit.Test
    public void testApi(){
        long count = userDao.count();
        boolean b = userDao.existsById(2);
    }

語(yǔ)句操作

除了調(diào)用spring data jpa內(nèi)置的api窟绷,我們也可以在dao接口中定義我們自己的方法锯玛,通過(guò)@Query聲明jpql或sql語(yǔ)句。

  • @Query
    • value:數(shù)據(jù)庫(kù)操作語(yǔ)句
    • nativeQuery:是否是原生查詢,默認(rèn)false攘残,即默認(rèn)使用jpql查詢
  • @Modifying:聲明當(dāng)前是一個(gè)更新操作拙友,需要修改數(shù)據(jù)庫(kù)數(shù)據(jù)。
    • 只能用于void或int/Integer的返回類型
    • 因?yàn)樾枰薷臄?shù)據(jù)庫(kù)數(shù)據(jù)歼郭,未防止修改失敗造成未知后果遗契,需要搭配事務(wù)管理來(lái)是使用
  • @Transactional:添加事務(wù)管理支持
    • 一般需要設(shè)置rollbackFor或者noRollbackFor,來(lái)表示什么情況下進(jìn)行事務(wù)回滾
  • @Rollback:是否可以回滾病曾,默認(rèn)true

jpql查詢

public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {

    @Query(value = "from User where name = :name and age = :age")
    public User findUserByName(@Param("name") String userName,@Param("age") int age);
}

jpql更新

@Query(value = "update User set name = :name where id = :id")
@Modifying
public Integer updateNameById(@Param("id") int id,@Param("name") String userName);


    @org.junit.Test
    @Transactional(rollbackFor = Exception.class)
    //@Rollback(value = false)//如果設(shè)置為fasle牍蜂,即使發(fā)生異常也不會(huì)回滾
    public void testJpql(){
        User user = userDao.findUserByName("lili",18);
        System.out.println(user);

        userDao.updateNameById(user.getId(),"lili_2");
    }

原生sql語(yǔ)句查詢

public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
    @Query(value = "select * from user where name = :name and age = :age",nativeQuery = true)
    public User findUserByName(@Param("name") String userName,@Param("age") int age);
}

約定規(guī)則查詢

spring data jpa制定了一些約定,如果按照這些約定來(lái)定義方法名泰涂,則會(huì)自動(dòng)解析出sql語(yǔ)句鲫竞。

findBy + 屬性名 + 查詢方式 + (And|Or) + 屬性名 + 查詢方式...

查詢方式 方法命名 sql where字句
And findByNameAndPwd where name= ? and pwd =?
Or findByNameOrSex where name= ? or sex=?
Is,Equals findById,findByIdEquals where id= ?
Between findByIdBetween where id between ? and ?
LessThan findByIdLessThan where id < ?
LessThanEquals findByIdLessThanEquals where id <= ?
GreaterThan findByIdGreaterThan where id > ?
GreaterThanEquals findByIdGreaterThanEquals where id > = ?
After findByIdAfter where id > ?
Before findByIdBefore where id < ?
IsNull findByNameIsNull where name is null
isNotNull,NotNull findByNameNotNull where name is not null
Like findByNameLike where name like ?
NotLike findByNameNotLike where name not like ?
StartingWith findByNameStartingWith where name like '?%'
EndingWith findByNameEndingWith where name like '%?'
Containing findByNameContaining where name like '%?%'
OrderBy findByIdOrderByXDesc where id=? order by x desc
Not findByNameNot where name <> ?
In findByIdIn(Collection<?> c) where id in (?)
NotIn findByIdNotIn(Collection<?> c) where id not in (?)
True findByAaaTue where aaa = true
False findByAaaFalse where aaa = false
IgnoreCase findByNameIgnoreCase where UPPER(name)=UPPER(?)

簡(jiǎn)單挑幾個(gè)示例:

public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
    public User findByName(String name);

    public User findByNameLike(String name);

    public User findByNameLikeAndAge(String name, int age);

    public List<User> findByIdBetween(int idMin, int idMax);
}



    @org.junit.Test
    public void testName(){
        User user1 = userDao.findByName("tom");
        System.out.println(user1);

        User user2 = userDao.findByNameLike("t%");
        System.out.println(user2);

        User user3 = userDao.findByNameLikeAndAge("tom",18);
        System.out.println(user3);

        List<User> users = userDao.findByIdBetween(1, 3);
        users.forEach(new Consumer<User>() {
            @Override
            public void accept(User user) {
                System.out.println(user);
            }
        });
    }

Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.name=?
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.name like ? escape ?
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where (user0_.name like ? escape ?) and user0_.age=?
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}
Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.age as age3_0_, user0_.name as name4_0_, user0_.phone as phone5_0_, user0_.sex as sex6_0_ from user user0_ where user0_.id between ? and ?
User{id=2, name='lili2', age=18, sex=1, address='null', phone='null'}
User{id=3, name='tom', age=18, sex=1, address='null', phone='null'}

標(biāo)準(zhǔn)查詢(Specification)

我們上面提到過(guò),springdata jpa的dao層一般繼承2個(gè)接口JpaRepository和JpaSpecificationExecutor逼蒙。JpaRepository封裝了crud从绘、統(tǒng)計(jì)、排序是牢、分頁(yè)的常見操作僵井,而JpaSpecificationExecutor基于JPA的criteria查詢封裝了另一種查詢方式,我們之前一直在使用JpaRepositoru中的方法驳棱,下面來(lái)看下JpaSpecificationExecutor接口驹沿,它里面只提供了5個(gè)方法:

public interface JpaSpecificationExecutor<T> {
    //查詢一個(gè)
    Optional<T> findOne(@Nullable Specification<T> spec);
    //查詢?nèi)?    List<T> findAll(@Nullable Specification<T> spec);
    //查詢?nèi)? 提供分頁(yè)功能
    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
    //查詢?nèi)浚峁┡判蚬δ?    List<T> findAll(@Nullable Specification<T> spec, Sort sort);
    //統(tǒng)計(jì)
    long count(@Nullable Specification<T> spec);
}

可以看到蹈胡,這5個(gè)方法有個(gè)共同點(diǎn)渊季,接收一個(gè)Specification參數(shù)。

Specification

Specification是對(duì)JPA規(guī)范中Root罚渐、CriteriaQuery却汉、CriteriaBuilder的一層封裝,用于構(gòu)建過(guò)濾條件荷并。實(shí)例化Specification需要實(shí)現(xiàn)它的toPerdicate方法:

//參數(shù)含義在我的另一文JPA規(guī)范中有介紹合砂,簡(jiǎn)單說(shuō)來(lái)Root用于獲得查詢屬性,CriteriaBuilder用于構(gòu)建過(guò)濾條件源织,CriteriaQuery用于指定最終查詢語(yǔ)句翩伪,這里一般不會(huì)使用,默認(rèn)為where語(yǔ)句谈息。
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

注意這里創(chuàng)建出來(lái)的是where查詢語(yǔ)句缘屹。

來(lái)個(gè)簡(jiǎn)單示例僻孝,查詢表中年齡大于等于18的所有河南人:

    @Test
    public void test(){
        Specification<User> specification = new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                //分別構(gòu)造各個(gè)單屬性的過(guò)濾條件
                Predicate namePredicate = criteriaBuilder.like(root.get("address"), "河南%");
                Predicate agePredicate = criteriaBuilder.ge(root.get("age"), 18);//大于等于

                //組合成最終的過(guò)濾條件
                Predicate predicate = criteriaBuilder.and(namePredicate, agePredicate);
                return predicate;
            }
        };
        
        //查詢
        List<User> users = userDao.findAll(specification);
        users.forEach(new Consumer<User>() {
            @Override
            public void accept(User user) {
                System.out.println(user);
            }
        });
    }

如果要添加排序和分頁(yè)蒿囤,可以使用Sort和Pageable。

  • Sort:排序
Sort sort = new Sort(Sort.Direction.DESC,"id");//排序?qū)傩钥梢栽O(shè)置多個(gè)
List<User> users = userDao.findAll(specification,sort);
  • Pageable:分頁(yè)袱蜡,是一個(gè)接口,可以通過(guò)PageRequest構(gòu)建實(shí)例互亮。
Sort sort = new Sort(Sort.Direction.DESC,"id");
//Pageable pageable = PageRequest.of(0,10);//pageIndex犁享,pageSize
Pageable pageable = PageRequest.of(0,10,sort);
Page<User> users = userDao.findAll(specification, pageable);
users.forEach(new Consumer<User>() {
   @Override
   public void accept(User user) {
      System.out.println(user);
   }
});

spring boot中的springdata jpa配置

application.yaml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/study?serverTimezone=GMT
    username: root
    password: crystal1024
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update

Demo源碼地址

https://github.com/lunxinfeng/jpa/tree/master/springdatajpa

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市豹休,隨后出現(xiàn)的幾起案子炊昆,更是在濱河造成了極大的恐慌,老刑警劉巖威根,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窑眯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡医窿,警方通過(guò)查閱死者的電腦和手機(jī)磅甩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)姥卢,“玉大人卷要,你說(shuō)我怎么就攤上這事《懒瘢” “怎么了僧叉?”我有些...
    開封第一講書人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)棺榔。 經(jīng)常有香客問(wèn)我瓶堕,道長(zhǎng),這世上最難降的妖魔是什么症歇? 我笑而不...
    開封第一講書人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任郎笆,我火速辦了婚禮,結(jié)果婚禮上忘晤,老公的妹妹穿的比我還像新娘宛蚓。我一直安慰自己,他們只是感情好设塔,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開白布凄吏。 她就那樣靜靜地躺著,像睡著了一般闰蛔。 火紅的嫁衣襯著肌膚如雪痕钢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,793評(píng)論 1 314
  • 那天序六,我揣著相機(jī)與錄音任连,去河邊找鬼。 笑死难咕,一個(gè)胖子當(dāng)著我的面吹牛课梳,可吹牛的內(nèi)容都是我干的距辆。 我是一名探鬼主播余佃,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼暮刃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了爆土?” 一聲冷哼從身側(cè)響起椭懊,我...
    開封第一講書人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎步势,沒想到半個(gè)月后氧猬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坏瘩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年盅抚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倔矾。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妄均,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哪自,到底是詐尸還是另有隱情丰包,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布壤巷,位于F島的核電站邑彪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏胧华。R本人自食惡果不足惜寄症,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望矩动。 院中可真熱鬧瘸爽,春花似錦、人聲如沸铅忿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)檀训。三九已至柑潦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間峻凫,已是汗流浹背渗鬼。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荧琼,地道東北人譬胎。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓差牛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親堰乔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偏化,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

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

  • 1、Spring Data JPA是什么镐侯? 它是Spring基于ORM框架侦讨、JPA規(guī)范封裝的一套JPA應(yīng)用框架,可...
    Jiandev閱讀 620評(píng)論 0 1
  • JPQL和命名查詢 在使用Spring Data JPA的過(guò)程中,框架通過(guò)解析方法名稱的方式生成對(duì)應(yīng)的SQL,確實(shí)...
    叩丁狼教育閱讀 7,135評(píng)論 0 5
  • Spring Data JPA 首先,讓IPersonDAO接口繼承Repository<T, ID extend...
    叩丁狼教育閱讀 503評(píng)論 0 1
  • Spring Data框架和Spring Data JPA簡(jiǎn)介 Spring Data : Spring 的一個(gè)子...
    smileNicky閱讀 9,207評(píng)論 2 6
  • 看著門口苟翻。嗯韵卤,那是離別的方向,我經(jīng)常告訴自己可能再也不會(huì)回來(lái)了崇猫。走出去是一片稻田沈条,風(fēng)吹過(guò)那時(shí),一朵恬靜的小...
    punxdog閱讀 326評(píng)論 1 6