JPA는 여러 ORM 전문가가 참여한 EJB 3.0 스펙 작업에서 기존 EJB ORM이던 Entity Bean을 JPA라고 바꾸고 JavaSE, JavaEE를 위한 영속성(persistence) 관리와 ORM을 위한 표준 기술이다. JPA는 ORM 표준 기술로 Hibernate, OpenJPA, EclipseLink, TopLink Essentials과 같은 구현체가 있고 이에 표준 인터페이스가 바로 JPA이다.
ORM(Object Relational Mapping)이란 RDB 테이블을 객체지향적으로 사용하기 위한 기술이다. RDB 테이블은 객체지향적 특징(상속, 다형성, 레퍼런스, 오브젝트 등)이 없고 자바와 같은 언어로 접근하기 쉽지 않다. 때문에 ORM을 사용해 오브젝트와 RDB 사이에 존재하는 개념과 접근을 객체지향적으로 다루기 위한 기술이다.
DAO 는 Database 에 Access 할 때 사용하는 Object다. Spring 기준으로 보면 보통 @Repository 가 붙은 놈들이다.
DTO 는 말 그대로 Data를 주고 받을 때 사용하는 Object이며
Entity Class 는 DB 와 Data를 직접 주고 받을 때 사용하는 놈이다.
왜 DTO와 Entity Class를 나누는가?
-View Layer와 DB Layer의 역할을 철저하게 분리하기 위해서
-테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request / Response 클래스)는 자주 변경되므로 분리해야 한다.
-Domain Model을 아무리 잘 설계했다고 해도 각 View 내에서 Domain Model의 getter만을 이용해서 원하는 정보를 표시하기가 어려운 경우가 종종 있다. 이런 경우 Domain Model 내에 Presentation을 위한 필드나 로직을 추가하게 되는데, 이러한 방식이 모델링의 순수성을 깨고 Domain Model 객체를 망가뜨리게 된다.
또한 Domain Model을 복잡하게 조합한 형태의 Presentation 요구사항들이 있기 때문에 Domain Model을 직접 사용하는 것은 어렵다. 즉 DTO는 Domain Model을 복사한 형태로, 다양한 Presentation Logic을 추가한 정도로 사용하며 Domain Model 객체는 Persistent만을 위해서 사용한다.
즉, DB 와 직접 통신하는 놈은 최대한 군더더기 없이 유지하는 것이 좋다.
데이터 전달 상, DB 통신과는 전혀 상관없는 필드와 메서드들이 덕지덕지 붙을 수 있기 때문에 Entity Class와 DTO 클래스를 분리한다.
DTO 클래스는 상~당히 Entity Class 와 유사하다.
다시 DataSource 로 돌아가보자.
위에 DAO 보이는가?
DAO 는 Data Access Object가 Database를 만나기 위해 거쳐야 하는 두 계단이 있다.
JDBC Template과 JDBC Driver다.
아래의 설명은 Datasource 링크의 내용이다.
# DataSource 란? DataSource는 JDBC 명세의 일부분이면서 일반화된 연결 팩토리이다. 즉 DB와 관계된 connection 정보를 담고 있으며, bean으로 등록하여 인자로 넘겨준다. 이 과정을 통해 Spring은 DataSource로 DB와의 연결을 획득한다.
DataSource는 JDBC Driver vendor(Mysql, Oracle 등) 별로 여러가지가 존재한다.
DataSource가 하는 일 - DB Server와의 기본적인 연결 - DB Connection Pooling 기능 (* 아래 참고) - 트랜젝션 처리
H2는 인메모리 DB로 애플리케이션이 동작하는 경우에만 DB가 함께 동작한다. Spring이 시작하는 시점에 scheme이 생성되도록/src/main/resource/scheme.sql을 생성하여 주고 아래에 다음과 같이 table 생성 SQL과 예제데이터를 추가하여 주자.
mybatis 설정 관련 첫 번째 링크에서 Spring이 시작되는 시점에 DB의 scheme 을 생성하기 위해 /resource 아래에 scheme.sql 을 생성해 준다고 돼있다. 링크를 타고 가서 스프링 공식 문서를 봐보자.
Spring Boot can automatically create the schema (DDL scripts) of yourDataSourceand initialize it (DML scripts). It loads SQL from the standard root classpath locations:schema.sqlanddata.sql, respectively. In addition, Spring Boot processes theschema-${platform}.sqlanddata-${platform}.sqlfiles (if present), whereplatformis the value ofspring.datasource.platform. This allows you to switch to database-specific scripts if necessary. For example, you might choose to set it to the vendor name of the database (hsqldb,h2,oracle,mysql,postgresql, and so on).
공식 문서에 의하면, Spring Boot 는 자동으로 스키마를 생성하고, 데이터를 생성할 수 있다.
기본 루트 클래스 패스 (/src/main/resources/schema.sql 이겠지?) 위치에 있는 scheme.sql 에 기입된 DDL scripts 를 실행해 schema 를 생성하고, data.sql 에 기입된 DML scripts 를 실행해 "데이터" 를 생성한다.
DDL 과 DML 의 차이는 여기서는 설명하지 않겠다. (다들 알겠지.)
일단 DDL 을 이용해 schema.sql 을 작성 해야 하는데 ,먼저 schema.sql 파일을 생성하자.
링크의 내용과 여러 Goolgle 검색을 토대로 DDL 을 작성 해준다. 덤으로 DML 도 살짝 얹었다.
CREATE TABLE TEXT_DIARY( ID INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20) NOT NULL, CONTENT TEXT NOT NULL, DATE DATE DEFAULT CURRENT_DATE, CREATE_DATE DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO TEXT_DIARY (NAME, CONTENT) VALUES ('오늘의 일기', '오늘은 공부를 했다.')
일단 간단한 테스트 TABLE 을 만들어 보도록 했다.
TEXT만 입력 가능한 간단한 도메인을 설정 해보자.
TEXT_DIARY 의 PRIMARY_KEY 는 ID 이며 INT 타입으로, 자동으로 증가하는 값을 가진다,
이름을 갖고 있으며 20자 이내여야 한다, 내용은 길게 들어갈 수 있으므로 적당히 TEXT 로 해놓는다,
CREATE_DATE 와 DATE 를 갖는다. 이 둘을 구분한 이유는 상식적으로 일기는 항상 당일에 작성하지는 않기 때문이다.
아직은 테스트 단계이기 때문에 전체적인 틀을 잡는다기보다는 간단한 테스트용 테이블을 위와 같이 만들어 보았다.
링크에서 시키는대로 엔트리 포인트가 되는 클래스에다가 @MapperScan 을 붙여준다.
mapper를 스캔할 수 있도록 설정하자. SpringBoot Configuration 파일상단에 아래와 같이 프로젝트 패키지의 경로를 넣자. 이렇게 하여 프로젝트 하위에 존재하는Mapper들을 빈으로 등록할수 있게 된다.
링크를 보면 모델의 type-alias 를 사용하기 위한 application.properties 파일 수정이 있지만, 그 부분은 제외하도록 하자.
DML 을 이용해 나름 데이터를 입력까지 했으니, 일단 꺼내서 확인해보자.
Entity class를 만들자. 이를 위해 package를 새로 따서, 그 안에 TextDiary 라는 Entity Class를 만들 것이다.
그리고 데이터를 입력 해준다.
package com.wpapp.voice_diary_server.model;
import lombok.Data;
import java.util.Date;
@Data
public class TextDiary {
private Integer id;
private String name;
private String content;
private Date date;
private Date createDate;
}
대충 요런 형태로 만들었다.
그리고 DAO 역할을 해줄 mapper 인터페이스와(repository), 이 DAO 를 다루는 서비스, 그리고 그 서비스를 다루는 Rest Controller 를 만들 것이다.
구조는 이렇게 된다.
Rest Controller - 서비스 - DAO(Repository and mapper)
Kotlin provides many types for representing arrays: IntArray, LongArray, DoubleArray, FloatArray, CharArray, ShortArray, ByteArray, BooleanArray. Each array store elements of the corresponding type (Int, Long, Double and so on). Note, there is no StringArray.
Function 또한 일정한 타입으로 볼 수 있는데... ex) 3 -> Int , 3L -> Long
fun sum(a: Int, b: Int): Int = a + b
sum has a type of (Int, Int) -> Int.
fun sayHello() {
println("Hello")
}
printHello has a type of () -> Unit
이런식이다.
코틀린은 함수 또한 오브젝트처럼 다룰 수 있다.
Var mul = {a: Int, b:Int -> a + b}
이러면 mul 변수가함수를가리키는 “변수” 다.
즉, 함수를 가리키는 "변수" 를 지정할 수 있다.
mul 변수의 타입을 "명시적" 으로 표현해 볼까 그럼?
val mul: (Int, Int) -> Int = {a: Int, b: Int -> a + b}
요런식으로 표현이 된다.
그럼 함수를 mul 이라는 변수에 담았을 때는 저런 식으로, "참조" 할 수 있다 치고...
fun sum(a: Int, b:Int): Int = a + b
이렇게이름이부여된함수를넘기고싶다면?
fun sum(a: Int, b: Int): Int = a + b
val mul2 = { a: Int, b: Int -> a * b }
val increment = placeArgument(1, ::sum)
val triple = placeArgument(3, mul2)
:: 를 함수 이름 앞에 붙여서 넘기면 된다.
함수를 리턴하는 함수를 보면서, 함수의 타입에 대한 이해를 마치도록 하자.
fun getRealGrade(x: Double) = x
fun getGradeWithPenalty(x: Double) = x - 1
fun getScoringFunction(isCheater: Boolean): (Double) -> Double {
if (isCheater) {
return ::getGradeWithPenalty
}
return ::getRealGrade
}
이름이 없는 함수
이름이 없는 함수는 크게 두 가지로 나뉜다.
하나는익명, 하나는람다.
fun(arguments): ReturnType { body } – this one is commonly called an "anonymous function".
{ arguments -> body } – this one is commonly called a "lambda expression".
익명 함수가 이미 이름이 없는 함수인데 이게 뭔 개 소리인가 싶을 것이다. 그런데 어쩔 수없다. 영어 표현과 한국어 표현의 차이다.
영어로 표현하면 function which has no name. 요딴식이겠지.
하여간, 이름이 없는 함수는 => 어노니머스 함수 와 람다 함수. 이렇게 둘로 나뉜다.
(헷갈리니까 어노니머스 함수 라고 하자.)
fun(a: Int, b: Int): Int {
return a * b
}
{ a: Int, b: Int -> a * b }
위의 두 표현은 "같은 표현" 이다.
즉
val mul1 = fun(a: Int, b: Int): Int {
return a * b
}
val mul2 = { a: Int, b: Int -> a * b }
이렇게 하면, 사실상 mul1 과 mul2 는 완전 똑같은 행위를 한다.
람다 함수의 경우, 매겨변수가 없을 때는
{ body }
요런 식으로 몸통만 써줘야 한다. -> 화살표는 반드시 생략한다.
람다를 매개변수로 받는 쪽은 여러 함축 단계를 거칠 수 있다. 매우 귀찮으니..
Lambdas and syntactic sugar
람다와, 시태틱 슈가
There are ways to make the code more readable for human without changing the code logic. If such a way is provided by the programming language and relates to syntax, the way is called syntactic sugar.
코드를 짜는 논리를 바꾸지 않으면서도, 코드를 좀 더 읽기 쉽게 하는 여러 방법이 있다. 만약 그런 것이 프로그래밍 언어 차원에서 지원될 때
우리는 이런걸 "syntactic sugar" 라고 부른다.
Kotlin promotes Functional Programming so there is syntactic sugar for it.
코틀린은 함수형 프로그래밍을 지향(?) 혹은 지원하며 이를 위한 문법적인 지원이 있다.
Let's recall the following example of passing the function as an argument:
fun isNotDot(c: Char): Boolean = c != '.'
val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter(::isNotDot)
Rewrite it to pass a lambda:
val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter({ c: Char -> c != '.' })
이런식으로 람다 함수를 쓰면, isNotDot 함수를 미리 선언할 필요가 없어진다.
Now we act! First of all, Kotlin infers types of many objects, and here specifying the c type isn't necessary:
originalText.filter({ c -> c != '.' })
그런데 솔직히 타입은 필요 없잖아? Char 를 굳이 써 줘야해? 생략하자.
Secondly, there are situations when the lambda is passed as the last argument. This is the case. Kotlin provides a way to eliminate these bracket sequences ({ }), allowing to write the lambda outside the parentheses:
originalText.filter() { c -> c != '.' }
그런데 함수의 매개변수로 넘어가는 람다가, "마지막 변수" 면, 사실 () 뒤쪽으로 빼내는게 좀더 보기 좋지 않냐? 그러니까 뺄 수 있어.
If the parentheses are left empty after this operation, they can be removed:
originalText.filter { c -> c != '.' }
아니 빼고 나니까 () 가 조금 거슬리네? 아무 변수도 없는데 저게 남아있잖아. 아 그럼, 아무것도 없는 괄호는 그냥 없앨 수 있게 하자.
() 안이 비어있으면 생략 가능함.
Finally, when there is a single parameter in a lambda, there is an opportunity to skip it. The parameter is available under the it name. The final version of the code removing dots is the following:
val originalText = "I don't know... what to say..."
val textWithoutDots = originalText.filter { it != '.' }
음... 다 좋은데. 저 c 가 마음에 안들어. 아니, 어차피 매개변수가 딱 하나인데... 굳이 c 요런 식으로 하나를 써 줘야해? 매개변수가 하나밖에 없을 때는 그냥 c 같은 건 생략할 수 있게 하는 건 어떨까?
람다는, 매개변수 없으면 -> 없어지잖아. 그 하나뿐인 매개변수는 it 으로 표현할 수 있게 하자..
여기서 getClousre 함수가아니라, 이안에서뱉어지는 function() {return text; }
가클로저야. 이 클로저는 말 그대로 "문을 닫는 느낌" 이지. text 배열을 자기 방 안에 넣고, 문을 닫아버리는 거야.
--
if 와 when
코틀린에서는
if 문이문, 이아니라표현이다.
표현은반드시반환값을갖는것이며, 문은반환값을갖는다기보다, 값들에변화를일으키는
것들을일컫는다.
만약 if가표현으로서즉값을반환하는용도로쓰인다면반드시 else 가있어야한다.
또 switch 대신 when 이 있다.
When 또한 Expression 으로쓸수있는데
이때는 if 처럼반드시 else 가있어야함.
val result = when (op) {
"+" -> a + b
"-" -> a - b
"*" -> a * b
else -> "Unknown operator"
}
println(result)
보면, when(op)이런식으로 ( ) 안에변수가하나들어가고
이 op 에해당하는값이있으면 -> 분기를타는방식이다.
when (n) {
0 -> println("n is zero")
in 1..10 -> println("n is between 1 and 10 (inclusive)")
in 25..30 -> println("n is between 25 and 30 (inclusive)")
else -> println("n is outside a range")
}
만약 when 뒤에아무런변수도없다? 그냥모두 boolean 조건이필요함.
when {
n == 0 -> println("n is zero")
n in 100..200 -> println("n is between 100 and 200")
n > 300 -> println("n is greater than 300")
n < 0 -> println("n is negative")
// else-branch is optional here
}
이런 식으로...
Switch 랑다르게, 한번에한분기만탈수있다. (여러 expression 실행불가)
when(str) {
“Hello”,”world” -> {
}
}
이런 식으로는 쓸 수 있다. 이렇게 한 분기.
반복문
repeat(n) 이라는함수가있다. Argument 로 int 로값을받아 n 회실행한다.
While 문은여타 while 문과같음.
scanner.hasNext() for strings and scanner.hasNextInt()
scanner의 hasNext() 를 활용할 수 있다.
라벨 개념
loop@for (i in 1..3) {
for (j in 1..3) {
println("i = $i, j = $j")
if (j == 3) break@loop
}
}
귀찮으니 간단히 정리하고 넘어가겠다.
val fileName = "src/reading.txt"
val lines = File(fileName).readText()
print(lines)
이렇게가져오면, 파일의모든값을한번에가져옴.
메모리 제한은 2GB 정도다.
val fileName = "src/reading.txt"
val lines = File(fileName).readLines()
for (line in lines){
println(line)
}
readLine 도 2GB. 사실상 전부 불러오고, List<String> 안에 한 줄씩 넣어놨을 뿐임.
readBytes() – may be helpful if you need to store file contents as an array of bytes:
이 것도, 결국 readText() 를 구현하기 위해 사용하는 세부 함수라서 2GB 메모리 제한임.
그 이상의 파일은 다음과 같은 방법을 사용해보라.
File(fileName).forEachLine { print(it) }
일단 넘어가자. 파일은 나중에 쓸 일이 있을 때 한번 더 훑어봐도 늦지 않는다.
클래스
Properties 는반드시초기화돼야함.
Also, a property has a strict type. The type can be any. It can be a standard type like a number or a string or a custom type. So a property type can be your own class and even the same class where the property is declared.
Constructors are class members that initialize a new object of the class.
class Size {
var width: Int = 0
var height: Int = 0
constructor(_width: Int, _height: Int) {
width = _width
height = _height
}
}
이런식의생성자를 “Secondary constructor” 라고한다. 차순위생성자?
대체생성자?
Omitting default values
Classes in Koltin have useful feature: you don't have to declare a default property value if the value is assigned in the constructor of the class:
Primary Constructor
주생성자
존나코틀린의새로운문법중하나인데
Class Size constructor(width: Int, height: Int) {}
요런식으로 constructor를선언하는거임.
class Size constructor(width: Int, height: Int) {
val width: Int = width
val height: Int = height
val area: Int = width * height
}
Primary constructor 의 constructor 키워드는생략이되며,
심지어 primary constructor 에서는변수선언도가능하다.
class Size(val width: Int, height: Int) {
val height: Int = height
val area: Int = width * height
}
위와같이. Width 는없던변수를선언해놓는것이다.
위의클래스선언은사실상첫번째것과다를게없다.
class Size(val width: Int, val height: Int)
이런식으로,{} 를생략하고아주간단한 Single line class를만들수도있다.
Any Kotlin class has a constructor to initialize its objects.
A constructor is available for invocation under the class name.
There are different ways to declare a constructor but a primary constructor is the most concise.
If a class has no custom constructors, then there is an implicit default constructor with no arguments and it does nothing.
모든코틀린클래스는오브젝트를초기화하기위한생성자를갖고있다.
컨스트럭트는클래스이름아래에있을수있다.
커스텀컨스트럭터가없다면, 기본생성자가암시적으로존재한다.
enumclass Rainbow(val color: String) {
RED("Red"),
ORANGE("Orange"),
YELLOW("Yellow"),
GREEN("Green"),
BLUE("Blue"),
INDIGO("Indigo"),
VIOLET("Violet")
}
Enum
enumclass Rainbow(val color: String, val rgb: String) {
RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00"),
GREEN("Green", "#00FF00"),
BLUE("Blue", "#0000FF"),
INDIGO("Indigo", "#4B0082"),
VIOLET("Violet", "#8B00FF");
fun printFullInfo() {
println("Color - $color, rgb - $rgb")
}
}
Enum 의기본속성들
Name -> 그냥이름 Rainbow.RED -> RED
Ordinal -> enum의순서?Rainbow.GREEN.ordinal -> 3
Values ()-> enum instances 들을반환.
fun isRainbow(color: String) : Boolean {
for (enumin Rainbow.values()) {
if (color.toUpperCase() == enum.name) returntrue
}
returnfalse
}
반복문은위와같이 enum 을씀.
valueOf() - returns an instance of Enum by its name with String type and case sensitivity
If you want to extend your Enum but with static context, then you need to wrap your method with companionobject keywords, let's modify our Rainbow in order to find an instance by RGB parameter:
Companion object 가여기서처음등장한다.
Static 으로 class 에 member를추가할때사용한다.
enumclass Rainbow(val color: String, val rgb: String) {
RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00"),
GREEN("Green", "#00FF00"),
BLUE("Blue", "#0000FF"),
INDIGO("Indigo", "#4B0082"),
VIOLET("Violet", "#8B00FF"),
NULL("", "");
companionobject {
fun findByRgb(rgb: String): Rainbow {
for (enumin Rainbow.values()) {
if (rgb == enum.rgb) returnenum
}
return NULL
}
}
fun printFullInfo() {
println("Color - $color, rgb - $rgb")
}
}
이렇게쓴다.
Member function
자바에서는클래스안에있는함수를 “메서드” 라고표현하곤하는데,
여기서는 Member function 이라고쓴다.
코틀린은클래스에 function 을 “확장선언” 하는기능을제공한다.
이를우린 “확장함수” Extension Function 이라고부른다.
fun String.repeated(): String = this + this
Fun 을붙이고, “클래스”.”함수이름” 을하고 “내용을선언” 하면끝.
"ha".repeated() => “haha”
The syntax to define an extension function is like defining a top-level function. Just write the name of a class which you would like to extend and a dot before the function name. The class to be extended is called a receiver type.
이때, “클래스” 를 “리시버타입 “ 이라고칭한다.
This 키워드를사용하면, 확장함수를사용하는 “인스턴스” 를참조할수있다.
So the issue is solved: Kotlin developers are able to add any functions to any classes they want.