소스 :
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)
댓글 남기기