소스 :
https://github.com/braverokmc79/hateoas-rest-api-account/tree/main
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>net.macaronics.account</groupId> <artifactId>AccoutAPI</artifactId> <version>0.0.1-SNAPSHOT</version> <name>AccoutAPI</name> <description>Develop REST APis with HATOAS</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
1. application.properties
spring.jpa.hibernate.ddl-auto=create #spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true #logging.level.org.hibernate.SQL=debug logging.level.org.hibernate.type.descriptor.sql=trace
2.Account
import org.springframework.hateoas.RepresentationModel; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @Entity @Table(name="accounts") @Getter @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder public class Account extends RepresentationModel<Account>{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @Column(length =20, nullable=false, unique=true) private String accountNumber; private float balance; public Account(String accountNumber, float balance) { this.accountNumber = accountNumber; this.balance = balance; } }
3.AccountNotFoundException
package com.example.demo; public class AccountNotFoundException extends Exception { }
4.AccountRepository
package com.example.demo; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; public interface AccountRepository extends JpaRepository<Account, Integer> { @Query("UPDATE Account a SET a.balance= a.balance + :amount WHERE a.id =:id") @Modifying public void updateBalance(float amount, Integer id); }
5.AccountService
package com.example.demo; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional public class AccountService { private AccountRepository repo; public AccountService(AccountRepository repo) { this.repo=repo; } public List<Account> listAll(){ return repo.findAll(); } public Account get(Integer id) { return repo.findById(id).get(); } public Account save(Account account) { return repo.save(account); } /** 입금 */ public Account deposit(float amount, Integer id) { repo.updateBalance(-amount, id); return repo.findById(id).get(); } /** 출금 */ public Account withdraw(float amount, Integer id) { repo.updateBalance(-amount, id); return repo.findById(id).get(); } public void delete(Integer id) throws AccountNotFoundException { if(!repo.existsById(id)) { throw new AccountNotFoundException(); } repo.deleteById(id); } }
6.Amount
import lombok.Getter; import lombok.Setter; @Getter @Setter public class Amount { private float amout; }
7.DatabaseLoader
package com.example.demo; import org.springframework.boot.CommandLineRunner; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class DatabaseLoader { private AccountRepository repo; public DatabaseLoader(AccountRepository repo) { this.repo=repo; } @Bean public CommandLineRunner initDatabase() { return args ->{ Account account1=new Account("1234567891", 100); Account account2=new Account("1002003009", 50); Account account3=new Account("1223565893", 1000); //repo.saveAll(List.of(account1, account2, account3)); repo.save(account1); repo.save(account2); repo.save(account3); System.out.println("Sample database initialized"); }; } }
8.AccountModelAssember
package com.example.demo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.server.RepresentationModelAssembler; @Configuration public class AccountModelAssember implements RepresentationModelAssembler<Account, EntityModel<Account>> { @Override public EntityModel<Account> toModel(Account account) { EntityModel<Account> accountModel=EntityModel.of(account); accountModel.add(linkTo(methodOn(AccountApi.class).getOne(account.getId())).withSelfRel()); accountModel.add(linkTo(methodOn(AccountApi.class).deposit(account.getId(), null)).withRel("deposits")); accountModel.add(linkTo(methodOn(AccountApi.class).withdraw(account.getId(), null)).withRel("withdrawals")); accountModel.add(linkTo(methodOn(AccountApi.class).listAll()).withRel(IanaLinkRelations.COLLECTION)); return accountModel; } }
9.AccountApi
package com.example.demo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; import java.util.List; import java.util.NoSuchElementException; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.MediaTypes; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; //@RequestMapping(value = "/api/events", produces = MediaTypes.HAL_JSON_VALUE) @RestController @RequestMapping(value="/api/accounts", produces=MediaTypes.HAL_JSON_VALUE) //@RequestMapping(value="/api/accounts") public class AccountApi { private AccountService service; private AccountModelAssember assember; public AccountApi(AccountService service, AccountModelAssember assember) { this.service=service; this.assember=assember; } @GetMapping public ResponseEntity<?> listAll(){ List<Account> listAccounts =service.listAll(); if(listAccounts.isEmpty()) { return ResponseEntity.noContent().build(); } /** List<EntityModel<Account>> accountModel =listAccounts.stream().map(assember::toModel).collect(Collectors.toList()); CollectionModel<EntityModel<Account>> collectionModel=CollectionModel.of(accountModel); //컬렉션에 링크 추가 collectionModel.add(linkTo(methodOn(AccountApi.class).listAll()).withSelfRel()); assember 에 내장된 toCollectionModel 을 사용 ==> */ CollectionModel<EntityModel<Account>> collectionModel=assember.toCollectionModel(listAccounts); return ResponseEntity.status(HttpStatus.OK).body(collectionModel); } //return new ResponseEntity<>(collectionModel, HttpStatus.OK); /** * ====>출력 { "_embedded": { "accountList": [ { "id": 1, "accountNumber": "1234567891", "balance": 100.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/1" }, "collection": { "href": "http://localhost:8080/api/accounts" } } }, { "id": 2, "accountNumber": "1002003009", "balance": 50.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/2" }, "collection": { "href": "http://localhost:8080/api/accounts" } } }, { "id": 3, "accountNumber": "1223565893", "balance": 1000.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/3" }, "collection": { "href": "http://localhost:8080/api/accounts" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/api/accounts" } } } */ @GetMapping("/{id}") public ResponseEntity<?> getOne(@PathVariable("id") Integer id) { try { Account account=service.get(id); EntityModel<Account> accountModel=assember.toModel(account); return ResponseEntity.status(HttpStatus.OK).body(accountModel); }catch (NoSuchElementException ex) { return ResponseEntity.notFound().build(); } } /** * ===>출력 * * { "id": 1, "accountNumber": "1234567891", "balance": 100.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/1" }, "collection": { "href": "http://localhost:8080/api/accounts" } } } */ /** @PostMapping("/{id}") public ResponseEntity<?> createEvent(@PathVariable("id") Integer id){ Account account =service.get(id); URI createdUri = linkTo(AccountApi.class).slash(id).toUri(); System.out.println(" createUri : " + createdUri); return ResponseEntity.created(createdUri).body(account); } */ @PostMapping public ResponseEntity<?> add(@RequestBody Account account){ Account savedAccount =service.save(account); EntityModel<Account> accountModel=assember.toModel(account); return ResponseEntity.created(linkTo(methodOn(AccountApi.class).getOne(savedAccount.getId())).toUri()).body(accountModel); } /**==> 출력 * { "id": 8, "accountNumber": "987654321", "balance": 2950.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/8" }, "collection": { "href": "http://localhost:8080/api/accounts" } } } */ @PutMapping public ResponseEntity<?> replace(@RequestBody Account account){ Account updatedAccount =service.save(account); EntityModel<Account> accountModel=assember.toModel(updatedAccount); return ResponseEntity.status(HttpStatus.OK).body(accountModel); } /** * ==> * * { "id":1, "accountNumber":"1234567891", "balance" : 999 } 출력=> { "id": 1, "accountNumber": "1234567891", "balance": 999.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/1" }, "collection": { "href": "http://localhost:8080/api/accounts" } } } */ @PatchMapping("/{id}/deposit") public ResponseEntity<?> deposit(@PathVariable("id") Integer id, @RequestBody Amount amount){ Account updatedAccount=service.deposit(amount.getAmout(), id); EntityModel<Account> accountModel=assember.toModel(updatedAccount); return ResponseEntity.status(HttpStatus.OK).body(accountModel); } @PatchMapping("/{id}/withdraw") public ResponseEntity<?> withdraw(@PathVariable("id") Integer id, @RequestBody Amount amount){ Account updatedAccount=service.withdraw(amount.getAmout(), id); EntityModel<Account> accountModel=assember.toModel(updatedAccount); return ResponseEntity.status(HttpStatus.OK).body(accountModel); } /** * { "id": 3, "accountNumber": "1223565893", "balance": 1000.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/3" }, "deposits": { "href": "http://localhost:8080/api/accounts/3/deposit" }, "withdrawals": { "href": "http://localhost:8080/api/accounts/3/withdraw" }, "collection": { "href": "http://localhost:8080/api/accounts" } } } * */ @DeleteMapping("/{id}") public ResponseEntity<?> delete(@PathVariable("id") Integer id){ try { service.delete(id); return ResponseEntity.noContent().build(); } catch (Exception e) { return ResponseEntity.notFound().build(); } } }
Code REST API for Create Operation
Request URI: /api/accounts
Add new account information
HTTP request method: POST
Request body: JSON document represents account information
Response status code:
201 Created for successful creation
Response body: JSON document of newly added account
@PostMapping public ResponseEntity<?> add(@RequestBody Account account){ Account savedAccount =service.save(account); savedAccount.add(linkTo(methodOn(AccountApi.class).getOne(account.getId())).withSelfRel()); savedAccount.add(linkTo(methodOn(AccountApi.class).listAll()).withRel(IanaLinkRelations.COLLECTION)); return ResponseEntity.created(linkTo(methodOn(AccountApi.class).getOne(savedAccount.getId())).toUri()).body(savedAccount); } /**==> 출력 * { "id": 8, "accountNumber": "987654321", "balance": 2950.0, "_links": { "self": { "href": "http://localhost:8080/api/accounts/8" }, "collection": { "href": "http://localhost:8080/api/accounts" } } } */
댓글 ( 4)
댓글 남기기