iyOmSd/Title: Vapor

[Vapor] MySQL 연동 및 데이터 CRUD (Fluent)

냄수 2021. 3. 6. 15:37
반응형

Vapor버전 4.0을 기준으로 작성된 게시글입니다!

 

 

DB기능을 Vapor에서 Fluent라고 지칭하는것 같아요

 

MySQL인 경우

// dependencies에 추가
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0-beta")


// target에 추가
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentMySQLDriver", package: "fluent-mysql-driver")

위의 코드를 추가해주면

아래와 같이 되겠죠

dependencies: [
    // 💧 A server-side Swift web framework.
    .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
    .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"),
    .package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0-beta"),
],
targets: [
    .target(
        name: "App",
        dependencies: [
            .product(name: "Vapor", package: "vapor"),
            .product(name: "Fluent", package: "fluent"),
            .product(name: "FluentMySQLDriver", package: "fluent-mysql-driver"),
        ],

 

 

configure.swit에 디비를 사용하니까 설정을 추가해줘야해요

이제부터 디비관련 코드를 작성할때 import Fluent 빼먹지마시구요

import Fluent
import FluentMySQLDriver

// configures your application
public func configure(_ app: Application) throws {
    // uncomment to serve files from /Public folder
    // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

    // MARK: - Setting MySQL
    try setUpMySQL(app)
    
    // MARK: - Register Routes
    try routes(app)
}


private func setUpMySQL(_ app: Application) throws {
    app.databases.use(
        .mysql(
            hostname: "localhost",
            username: "root",
            password: "",
            database: "vapor",
            tlsConfiguration: .forClient(certificateVerification: .none)
    ), as: .mysql)
  }

 

이렇게 만들어두고

터미널에서

create database vapor

 

을 입력하면 데이터베이스가 만들어진걸 확인할 수 있죠

 

이때 만약

ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

이 에러가 보인다면

mysql 서버가 실행되지 않은상태인거에요

 

터미널에서

mysql.server start

를 실행해주고 다시 확인해보세용

 

확인을 어떻게하냐구요?

다운받아주세요~ 

(터미널에서 접속해서 명령어로 하셔도무방합니다~~)

 

.

.

.

데이터베이스를 만들었으니이제 테이블을 만들어야해요

이제부터 Model이라는 개념이 나와요

Model의 역할은 Database에서 Table이자 데이터라고 비유할 수 있어요

 

모델은 기본적으로 Codable을 채택하구있구요

모델을 만들때 빈 생성자를 꼭 만들어줘야해요

vapor에서 사용한다고 하네요

final class Galaxy: Model, Content {
    // Name of the table or collection.
    static let schema = "galaxies"

    // Unique identifier for this Galaxy.
    @ID(key: .id)
    var id: UUID?

    // The Galaxy's name.
    @Field(key: "name")
    var name: String

    // Creates a new, empty Galaxy.
    init() { }

    // Creates a new Galaxy with all properties set.
    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

Model을 채택하면

schema변수를 설정해주고

 

@ID를 이용해서

해당값으로 모델의 인스턴스를 고유하게 식별할 수 있도록 할 수있어요

 

@Field는

키값을 필요로 하고 값을 저장할 수 있는 데이터베이스의 필드 역할을 지정해주는 프로퍼티레퍼에요

이 키값이 db에서의 키값이 되고

변수값은 키에 대한 값으로 저장될거에요

 

Create

데이터를 생성하는 코드는 간단해요

해당 모델을 만들어서

.create(on: db) 를 통해 생성할 수 있어요

let galaxy = Galaxy(name: "Earth")
galaxy.create(on: database)

 

서버 요청에서 디비를 접근하려면 아래와같아요

database를 req.db로 접근할 수 있구요

EventLoopFuture은 아직은 잘 몰라두되요 이렇게하는구나 우선 넘어가세요

app.get("galaxies") { req -> EventLoopFuture<Galaxy> in
    let galaxy = try req.query.decode(Galaxy.self)
    return galaxy.create(on: req.db)
        .map { galaxy }

}

/* 응답
{
  "id": "B6CA2E50-D9CD-4E89-9BD7-3C119E119E28",
  "name": "\"지구\""
}
*/

EventLoopFuture<Galaxy>를 출력하면

현재 모델에는

id와 name이 있으니 생성된 그값들이 나오네요

 

 

Read

All

전체배열

// query all
app.get("galaxies", "all") { req in
    Galaxy.query(on: req.db).all()
}

/*
[
  {
    "id": "64AA8096-41B8-4D55-8F66-DA6653AFC31D",
    "name": "\"태양\""
  },
  {
    "name": "\"지구\"",
    "id": "B6CA2E50-D9CD-4E89-9BD7-3C119E119E28"
  }
]
*/

디비에 있는 모든데이터가 배열로 나오는 코드에요 간단하죠

 

Single Field

원하는 필드만 뽑아내고싶다면

    // query single field all
app.get("galaxies", "name", "all") { req in
    Galaxy.query(on: req.db).all(\.$name)
}

/*
[
  "\"태양\"",
  "\"지구\""
]
*/

키패스를 이용해서 원하는 프로퍼티에만 접근할 수 있어요

 

Field

원하는 필드만 가져오고 싶을 때 사용하구요

선택하지 않은 필드는 nil로 나와요

// field
    app.get("galaxies", "field") { req in
        return Galaxy.query(on: req.db)
            .field(\.$name)
            .all()
    }

/*
[
  {
    "name": "지구지구",
    "id": null
  },
  {
    "name": "\"지구나라\"",
    "id": null
  },
  {
    "id": null,
    "name": "\"태양\""
  },
  {
    "name": "\"지구\"",
    "id": null
  },
  {
    "id": null,
    "name": "지구나라"
  }
]
*/

 

Filter

필터를 설정할 수도있어요

// query first
    app.get("galaxies", "first") { req -> EventLoopFuture<Galaxy> in
        let earth = Galaxy.query(on: req.db)
            .filter(\.$name == req.query["name"]!)
            .first()
            .unwrap(or: Abort(.noContent))
        
        return earth
    }

 

요청에서 원하는 이름을 받아오고 해당된 이름을 모두 불러온뒤

처음에 나온 데이터만 반환해주는 코드에요

 

Group

filter를 중복해서사용하면 and연산을하지만

group을 사용하면 or연산이 가능해요

// query group
    app.get("galaxies", "group") { req -> EventLoopFuture<[Galaxy]> in
        let a: String = req.query["a"]!
        let b: String = req.query["b"]!
        
        let galaxy = Galaxy.query(on: req.db).group(.or) { group in
            group.filter(\.$name == b).filter(\.$name == a)
        }.all()
        
        return galaxy
    }

/*
[
  {
    "name": "지구지구",
    "id": "32968A73-90A5-490C-B218-A4A150338550"
  },
  {
    "name": "지구나라",
    "id": "EACA8500-CAD1-4ECC-B42D-2730EBFD5595"
  }
]
*/



////////////////////
// query filter
    app.get("galaxies", "filter") { req -> EventLoopFuture<[Galaxy]> in
        let a: String = req.query["a"]!
        let b: String = req.query["b"]!
        
        let earth = Galaxy.query(on: req.db)
            .filter(\.$name == a)
            .filter(\.$name == b)
            .all()
        
        return earth
    }
/*
[]
*/

 

 

Unique

해당값에대한 유니크한 값만 나와요

같은 값이 있다면 무시되고 하나만 나오는거에요

// unique
    app.get("galaxies", "unique") { req in
        return Galaxy.query(on: req.db)
            .field(\.$name)
            .unique()
            .all()
    }
/*
[
  {
    "id": null,
    "name": "지구지구"
  },
  {
    "id": null,
    "name": "\"지구나라\""
  },
  {
    "name": "\"태양\"",
    "id": null
  },
  {
    "name": "\"지구\"",
    "id": null
  },
  {
    "name": "지구나라",
    "id": null
  }
]
*/

 

Range

반환 갯수 제한가능

// range
    app.get("galaxies", "range") { req in
        Galaxy.query(on: req.db)
            .range(..<3)
            .all()
    }

 

Update

// update
    app.get("galaxies", "update") { req in
        Galaxy.query(on: req.db)
            .set(\.$name, to: req.query["newName"]!)
            .filter(\.$name == "지구지구")
            .update().map { "update" }
    }

위의 코드는

데이터베이스에서 지구지구 이름인 데이터를 새로 받은 newName으로 수정하는 코드에요

update함수는 set, filter, range를 지원해줘서 같이 사용할 수 있어요

 

set이 변경할 패스와 값을 지정해주고

마지막에 update()를 실행해야 적용이되요

 

수정후 성공포맷을 반환하고싶다면

// update
app.get("galaxies", "update") { req in
    Galaxy.query(on: req.db)
        .filter(\.$name == req.query["name"]!)
        .set(\.$name, to: req.query["newName"]!)
        .update().map { SimpleResponse(code: 200, msg: "수정성공") }
}

struct SimpleResponse: Content {
    var code: Int
    var msg: String
}
/*
{
  "msg": "수정성공",
  "code": 200
}
*/

 

Delete

delete함수엔 filter가 지원되구요

// delete
    app.get("galaxies", "delete") { req in
        Galaxy.query(on: req.db)
            .filter(\.$name == req.query["name"]!)
            .delete()
            .map { SimpleResponse(code: 200, msg: "삭제성공")}
    }

 

 

Paginate

페이징 기능도 중요하죠

vapor은 기본적으로 페이징을 지원해줘서 간편하게 사용할 수 있구요

함수구현시 paginate만 사용하고

요청url에 

per

page

url query를 붙여서 원하는 위치를 접근 할 수 있어요

 

응답으로나오는 메타데이터의

per: 페이지에 보여줄수 있는 최대 갯수

page: 현재페이지

total: 테이블에 있는 데이터 전체갯수

// paginate
    app.get("galaxies") { req in
        Galaxy.query(on: req.db)
            .paginate(for: req)
        
    }
// http://127.0.0.1:8080/galaxies?page=1
{
  "items": [
    {
      "id": "59A0DCE5-B994-4FBD-A793-4A78DD9410D1",
      "name": "\"지구나라\""
    },
    {
      "name": "\"태양\"",
      "id": "64AA8096-41B8-4D55-8F66-DA6653AFC31D"
    },
    {
      "name": "\"지구\"",
      "id": "B6CA2E50-D9CD-4E89-9BD7-3C119E119E28"
    },
    {
      "id": "EACA8500-CAD1-4ECC-B42D-2730EBFD5595",
      "name": "지구나라"
    }
  ],
  "metadata": {
    "per": 10,
    "total": 4,
    "page": 1
  }
}
// http://127.0.0.1:8080/galaxies?page=1&per=3
{
  "items": [
    {
      "id": "59A0DCE5-B994-4FBD-A793-4A78DD9410D1",
      "name": "\"지구나라\""
    },
    {
      "name": "\"태양\"",
      "id": "64AA8096-41B8-4D55-8F66-DA6653AFC31D"
    },
    {
      "name": "\"지구\"",
      "id": "B6CA2E50-D9CD-4E89-9BD7-3C119E119E28"
    }
  ],
  "metadata": {
    "per": 3,
    "total": 4,
    "page": 1
  }
}

 

반응형