CSS調整

2017年3月23日

Spring Data Restで@Projection

まず、Spring Data RestでサクッとRestできるようにします

DB

適当クエリでテーブル2つとデータを作成
create table memo_title (
  id serial primary key,
  title varchar(255) not null,
  comment varchar(255)
);

create table memo_value (
  id serial primary key,
  value varchar(255) not null,
  title_id int not null
);

insert into memo_title(id, title, comment) values(1, 'TESTタイトル', 'メモのコメントですよ');
insert into memo_value(id, value, title_id) values(1, 'ほげ', 1);
insert into memo_value(id, value, title_id) values(2, 'ふが', 1);

memo_value が memo_title の id を持ってて親子関係がある感じです。
(面倒なのでFKとか無しで)



続いてAPのコード

MemoTitle.kt

@Entity
@Table(name="memo_title")
class MemoTitle {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0

    @Column(nullable = false)
    lateinit var title: String

    @Column
    lateinit var comment: String
}

@Repository
interface MemoTitleRepository: PagingAndSortingRepository<MemoTitle, Long>

MemoValue.kt

@Entity
@Table(name="memo_value")
class MemoValue {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0

    @Column(nullable = false)
    lateinit var value: String

    @Column(nullable = false)
    var title_id: Long = 0
}

@Repository
interface MemoValueRepository : PagingAndSortingRepository<MemoValue, Long>

こういう時に kotlin だと関連したコードが1ファイルに書けるってだけでウレシイ。。。



この時点で実行すると

MemoTitle

 
curl http://localhost:9000/api/memoTitles

{
  "_embedded" : {
    "memoTitles" : [ {
      "title" : "TESTタイトル",
      "comment": "メモのコメントですよ",
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoTitles/1"
        },
        "memoTitle" : {
          "href" : "http://localhost:9000/api/memoTitles/1"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoTitles"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoTitles"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

MemoValue

curl http://localhost:9000/api/memoValues

{
  "_embedded" : {
    "memoValues" : [ {
      "id" : 1,
      "value" : "ほげ",
      "title_id" : 1,
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/1/title"
        }
      }
    }, {
      "id" : 2,
      "value" : "ふが",
      "title_id" : 1,
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/2/title"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoValues"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoValues"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 0
  }
}


MemoValueをGETしたときにtitleも欲しかったりしたとします
MemoValueにMemoTitleとのアソシエーションを追加し、Projectionを指定します

MemoValue.kt

@Entity
@Table(name="memo_value")
class MemoValue {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = 0

    @Column(nullable = false)
    lateinit var value: String

    @Column(nullable = false)
    var title_id: Long = 0

    @ManyToOne(targetEntity = MemoTitle::class, fetch = FetchType.EAGER)
    @JoinColumn(name = "title_id", insertable = false, updatable = false, nullable = false)
    lateinit var title: MemoTitle
}

@Projection(types = arrayOf(MemoValue::class))
interface MemoValueProjection {
    var id: Long
    var value: String
    var title: MemoTitle
}

@RepositoryRestResource(excerptProjection = MemoValueProjection::class)
interface MemoValueRepository : PagingAndSortingRepository<MemoValue, Long>

excerptProjectionとするとリポジトリの戻りが全てそのprojectionになります
ここで指定せずに、GETリクエストに?projection=Projection名とか指定することもできます


これで実行すると

MemoValue

curl http://localhost:9000/api/memoValues

{
  "_embedded" : {
    "memoValues" : [ {
      "id" : 1,
      "value" : "ほげ",
      "title" : {
        "title" : "TESTタイトル",
        "comment": "メモのコメントですよ"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/1{?projection}",
          "templated" : true
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/1/title"
        }
      }
    }, {
      "id" : 2,
      "value" : "ふが",
      "title" : {
        "title" : "TESTタイトル",
        "comment": "メモのコメントですよ"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/2{?projection}",
          "templated" : true
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/2/title"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoValues"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoValues"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 0
  }
}

無事にアソシエーション先の情報も取れました



続いて、title.commentは要らないんすよ、とかなったとします
その場合はMemoTitleもProjectionを指定します

MemoTitle.kt

@Projection(types = arrayOf(MemoTitle::class))
interface MemoTitleProjection {
    var title: MemoTitle
}

MemoValue.kt

@Projection(types = arrayOf(MemoValue::class))
interface MemoValueProjection {
    var id: Long
    var value: String
    var title: MemoTitleProjection
}

これで実行すると

MemoValue

curl http://localhost:9000/api/memoValues

{
  "_embedded" : {
    "memoValues" : [ {
      "id" : 1,
      "value" : "ほげ",
      "title" : {
        "title" : "TESTタイトル"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/1"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/1{?projection}",
          "templated" : true
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/1/title"
        }
      }
    }, {
      "id" : 2,
      "value" : "ふが",
      "title" : {
        "title" : "TESTタイトル"
      },
      "_links" : {
        "self" : {
          "href" : "http://localhost:9000/api/memoValues/2"
        },
        "memoValue" : {
          "href" : "http://localhost:9000/api/memoValues/2{?projection}",
          "templated" : true
        },
        "title" : {
          "href" : "http://localhost:9000/api/memoValues/2/title"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:9000/api/memoValues"
    },
    "profile" : {
      "href" : "http://localhost:9000/api/profile/memoValues"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 0
  }
}

title.commentが削られています



これだけの記述で必要な情報のGETに加えてPOSTやらPUTやらPATCHでのINSERTやDELETEやUPDATEなんかもルーティングされてるとかステキですね

いじょ