Simple BLoC Tutorial - Counter

|

Tutorial: Counter

+버튼을 누르면 1이 증가하고 -버튼을 누르면 1이 감소하는 간단한 어플이다. 코드에 대한 설명은 주석 참고.

Main.dart

import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

enum CounterEvent { increment, decrement }

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: BlocProvider<CounterBloc>(
        create: (context) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

class CounterBloc extends Bloc<CounterEvent, int> {
  // initialState 설정
  @override
  int get initialState => 0;

  // Evnet가 생겼을 때, state를 변화시켜서 다시 stream에다가 집어넣음.
  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.decrement:
        yield state - 1;
        break;
      case CounterEvent.increment:
        yield state + 1;
        break;
    }
  }
}


class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // BlocProvider로 Bloc생성
    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      // CounterBloc의 state인 count에 따라 표시될 text가 달라질 것.
      body: BlocBuilder<CounterBloc, int>(
        builder: (context, count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                // Event 발생. 처리는 CounterBloc의 mapEventToState에서!
                counterBloc.add(CounterEvent.increment);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () {
                // Event 발생. 처리는 CounterBloc의 mapEventToState에서!
                counterBloc.add(CounterEvent.decrement);
              },
            ),
          ),
        ],
      ),
    );
  }
}

이 글은 아래의 글을 참고하여 작성되었습니다.

Counter Tutorial Using Bloc

Architecture of BLoC

|

Architecture of BLoC

Bloc Architecture

Bloc을 쓰면 어플을 다음과 같은 3개의 layer로 나눌 수 있다.

  • Presentation
  • Business Logic(Bloc)
  • Data

Data Layer

데이터를 검색, 관리하는 부분

2가지 part로 나눌 수 있다.

1. Data Provider

raw data를 제공하며 일반적이고 다용도적이다.

보통 CRUD를 수행한다.

2. Repository

1개 이상의 data provider를 둘러싼 layer다. 다수의 data provider들을 서로 상호작용 시키고 Bloc Layer로 가기 전에 데이터를 수정할 수 있다.

Bloc Layer

Presentation Layer(UI)에 event로 인한 변화를 연결시켜주는 layer다. 일종의 다리역할

Bloc-to-Bloc Comuunication

Bloc은 다른 Bloc의 state 변화에 대응하기 위해 다른 bloc들의 dependency들을 가지고 있다.

Presentation Layer

Bloc state에 따라 screen을 render함.

이 글은 아래의 글을 참고하여 작성되었습니다.

Architecture of bloc

Core Concepts of Bloc

|

Core Concepts of Bloc

Bloc의 사용을 위해 꼭 알아야 할 것들은 다음과 같다.

Events

Bloc의 input이다. 버튼을 눌렀다거나 페이지가 로드된다거나 등의 사건이 일어나는 것을 말한다.

States

Bloc의 output이다. 어플의 현재 상태를 나타낸다.

Transitions

현재 state가 A라는 event를 만났을 때, 어떤 state로 변하게 되는데 이 변화를 transition이라고 한다.

Streams

Stream은 asynchronous data들을 모아둔 pipe라고 생각하면 된다. Asynchronous data들은 물이라고 생각하고 pipe에 물이 흐르고 있는 것을 생각하면 접근이 쉬워진다.

Stream을 생성할 때는 async*로 생성할 수 있다.

Stream<int> countStream(int max) async* {
    for (int i = 0; i < max; i++) {
        yield i;
    }
}

yield는 stream으로 data를 넣는 keyword이다. 위 함수는 0~max-1까지 stream에 data를 넣는 작업을 하고 있다.

Stream을 다룰 때에는 Future, async, await으로 다룰 수 있다. 지금 당장이 아닌 미래에 값이 들어올 것이기 때문이다.

Future<int> sumStream(Stream<int> stream) async {
    int sum = 0;
    await for (int value in stream) {
        sum += value;
    }
    return sum;
}

Stream에 있는 data들을 모두 받아서 sum에다가 더하고 이를 반환하는 함수이다.

Blocs

Business Logic Components의 약자로 events를 states로 바꾸는 역할을 한다.

모든 bloc은 아무런 event가 일어나지 않은 inital state를 만들어 줘야 하고, event가 들어오면 state를 반환하는 mapEventToState도 만들어 줘야 한다.

import 'package:bloc/bloc.dart';

enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
  @override
  int get initialState => 0;

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.decrement:
        yield state - 1;
        break;
      case CounterEvent.increment:
        yield state + 1;
        break;
    }
  }
}

add method를 통해 event가 생기고 mapEventToState가 실행됨.

void main() {
    CounterBloc bloc = CounterBloc();

    for (int i = 0; i < 3; i++) {
        bloc.add(CounterEvent.increment);
    }
}

state가 변하는 transition을 관리하려면 먼저 onTransition method를 override시켜야 한다. onTransition은 모든 local Bloc을 관리할 수 있고, Bloc의 state가 update되기 직전에 call된다.

그리고 onError를 사용하면 exception도 관리가 가능하다.

@override
void onTransition(Transition<CounterEvent, int> transition) {
    print(transition);
    super.onTransition(transition);
}

@override
void onError(Object error, StackTrace stackTrace) {
  print('$error, $stackTrace');
}

BlocDelegate

Bloc을 쓰면 한 곳에서 모든 transition에 접근이 가능하다.

event나 error나 transition이 일어났을 때 여러 Bloc들이 모두 같은 행동을 한다면 그걸 하나로 모아서 관리하는 것이다.

Core concept of flutter_bloc

Bloc Widgets

BlocBuilder

StreamBuilder widget과 굉장히 비슷하다. widget의 building을 new state에 대응해서 관리한다.

bloc parameter가 빠져있으면 BlocBuilder는 자동으로 BlocProvider와 현재 BuildContext를 확인해서 일을 한다.

BlocBuilder<BlocA, BlocAState>(
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

BlocProvider와 현재 BuildContext에서 갈 수 없으면서 범위가 하나의 widget인 bloc인 경우에는 bloc을 지정해준다.

BlocBuilder<BlocA, BlocAState>(
  bloc: blocA, // provide the local bloc instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

Parameter중 하나인 condition은 이전 state, 현재 state 중 어느 state로 building을 할지 정하는 parameter이다. true면 rebuild하고 false면 그대로 내비둔다.

BlocBuilder<BlocA, BlocAState>(
  condition: (previousState, state) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)
BlocProvider

bloc을 children widget에게 전해주는 widget.

대부분 BlocProvider를 써서 subtree의 나머지 부분에 쓸 수 있는 새로운 bloc을 만들어야 한다.

BlocProvider(
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

몇몇 case에서는 이미 존재하는 bloc을 쓸 때도 있다. 이미 있는 bloc에 새로운 route를 만들어 줄 때 쓴다.

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
  child: ScreenA(),
);

ChildAScreenA에서 BlocA를 쓰려면 다음처럼 쓰면 된다

// with extensions
context.bloc<BlocA>();

// without extensions
BlocProvider.of<BlocA>(context)

MultiBlocProvider

다수의 BlocProvider를 하나의 widget으로 묶는 역할을 하는 widget이다. 가독성 향상과 BlocProvider의 중첩을 없애주는 역할을 한다.

//Same Code
BlocProvider<BlocA>(
  create: (BuildContext context) => BlocA(),
  child: BlocProvider<BlocB>(
    create: (BuildContext context) => BlocB(),
    child: BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
      child: ChildA(),
    )
  )
)

//Same Code
MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)
BlocListener

state의 변화를 확인하는 widget. state 변할 때 마다 listener를 call함.

BlocBuilder처럼 bloc parameter가 빠져있으면 자동으로 BlocProvider와 현재 BuildContext를 확인해서 일을 한다.

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

BlocProvider와 현재 BuildContext에서 갈 수 없는 bloc인 경우에는 bloc을 지정해준다.

BlocListener<BlocA, BlocAState>(
  bloc: blocA,
  listener: (context, state) {
    // do stuff here based on BlocA's state
  }
)

Parameter중 하나인 condition은 이전 state, 현재 state 중 어느 state로 building을 할지 정하는 parameter이다. true면 listener가 state를 부르고, false면 state를 부르지 않는다.

BlocListener<BlocA, BlocAState>(
  condition: (previousState, state) {
    // return true/false to determine whether or not
    // to call listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)
MultiBlocListener

BlocListener를 하나로 묶음.

// Same code
BlocListener<BlocA, BlocAState>(
  listener: (context, state) {},
  child: BlocListener<BlocB, BlocBState>(
    listener: (context, state) {},
    child: BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
      child: ChildA(),
    ),
  ),
)

// Same code
MultiBlocListener(
  listeners: [
    BlocListener<BlocA, BlocAState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocB, BlocBState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
    ),
  ],
  child: ChildA(),
)
BlocConsumer

New state에 반응하기 위해 builderlistener를 사용한다. BlocWidgetBuilderBlocWidgetListener가 필수다.

BlocListenerBlocBuilder와 비슷하지만 boilerplate code가 적다.

bloc에서 UI의 rebuild와 state change로 인해서 reaction이 실행될 때 사용된다.

BlocBuilderBlocListener와 사용법은 비슷하다.

BlocConsumer<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
    // stuff는 state가 변했다는 것을 navigation처럼 알려주는 역할을 하면 된다.
    // 보통은 snackbar나 dialog등을 사용
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

listenWhenbuildWhenlistenerbuilder를 더 세밀하게 control하기 위해 사용된다.

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
    // return true/false to determine whether or not
    // to invoke listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  buildWhen: (previous, current) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)
RepositoryProvider

BlocProvider처럼 repository를 children에게 전해주는 widget.

BlocProvider는 Bloc제공에 쓰이는 반면 RepositoryProvider는 repository를 쓸 때에만 사용되어야 한다.

RepositoryProvider(
  create: (context) => RepositoryA(),
  child: ChildA(),
);

이렇게 하면 ChildARepository의 instance로 다음과 같이 사용이 가능하다.

// with extensions
context.repository<RepositoryA>();

// without extensions
RepositoryProvider.of<RepositoryA>(context)
MultiRepositoryProvider

이제는 이름만 보고도 감이 올텐데 RepositoryProvider 를 하나로 묶는데에 사용된다.

// Same Code
RepositoryProvider<RepositoryA>(
  create: (context) => RepositoryA(),
  child: RepositoryProvider<RepositoryB>(
    create: (context) => RepositoryB(),
    child: RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
      child: ChildA(),
    )
  )
)

// Same Code
MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)

Usage

배운 이것들을 어떻게 쓸 것인가? 아래의 예제를 통해 BlocBuilder를 써서 CounterPage widget을 CounterBloc에 연결하는 방법에 대해 살펴보자.

counter_bloc.dart
enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
  @override
  int get initialState => 0;

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.decrement:
        yield state - 1;
        break;
      case CounterEvent.increment:
        yield state + 1;
        break;
    }
  }
}

counter_page.dart
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: BlocBuilder<CounterBloc, int>(
        builder: (context, count) {
          return Center(
            child: Text(
              '$count',
              style: TextStyle(fontSize: 24.0),
            ),
          );
        },
      ),
      floatingActionButton: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.add),
              onPressed: () {
                counterBloc.add(CounterEvent.increment);
              },
            ),
          ),
          Padding(
            padding: EdgeInsets.symmetric(vertical: 5.0),
            child: FloatingActionButton(
              child: Icon(Icons.remove),
              onPressed: () {
                counterBloc.add(CounterEvent.decrement);
              },
            ),
          ),
        ],
      ),
    );
  }
}

Presentation layer와 Business Logic layer를 위와 같이 분리해서 다룰 수 있음을 알 수 있다.

CounterPage widget은 버튼을 누르면 어떤 일이 일어나는지에 대해서는 아무것도 모른다. 그냥 CounterBloc widget에 user가 어떤 버튼을 눌렀더라~ 하고 알려주기만 할 뿐이다. 그러면 일은 아래것 아니 CounterBloc widget에서 해줄 것이다.

이 글은 아래의 글을 참고하여 작성되었습니다.

Core Concepts of bloc

Core Concepts of flutter_bloc

DB Chapter 8

|

Ch.8: Views and Indexes


1. Virtual Views

Virtual view: query를 통해 가상으로 만들어진 relation. 즉, 논리적으로만 존재하는 table. 보통 view라고 하면 이걸 말함

Materialized view: 실제로 DB에 저장되어 있는 view. 특정 튜플에 빠르게 접근하기 위해 사용.

Table 은 결과를 저장, View는 과정을 저장.

Likes(StudentID, book)
Sells(bookstore, book, price)
Frequents(StudentID, bookstore)

#Synergy라는 view를 만드는 방법. SID와 book, bookstore로 이루어져 있고, where 아래 3개를 만족.
CREATE VIEW Synergy AS
    SELECT Likes.StudentID, Likes.book, Sells.bookstore
    FROM Likes, Sells, Frequents
    WHERE Likes.StudentID = Frequents.StudentID AND
         Likes.book = Sells.book AND
         Sells.bookstore = Frequents.bookstore;
	
/*
Synergy에 insert할 때의 trigger. 
만약 Synergy에 값이 새로 들어오면 Likes에 새로 들어온 StudentID,book의 값이 저장되고, 
Sells의 bookstore, book에는 새로 들어온 bookstore, book의 값이 저장되고, 
Frequents에는 새로들어온 SID, bookstore의 값이 저장이 된다.
*/
CREATE TRIGGER ViewTrig
INSTEAD OF INSERT ON Synergy
FOR EACH ROW
	BEGIN
		INSERT INTO Likes VALUES(:new.StudentID, :new.book);
		INSERT INTO Sells(bookstore, book) VALUES(:new.bookstore, :new.book);
		INSERT INTO Frequents VALUES(:new.StudentID, :new.bookstore);
	END;
.
run

#책에 나온 예제. view에는 t, y만 저장해서 studioname에 해당하는 정보가 없어서 Movie에 저장될 때 t,y만 값이 저장이 되고 studioname에는 paramount가 아닌 NULL이 저장
CREATE VIEW ParamountMovies AS
	SELECT title, year
	FROM Movies
	WHERE studioName = 'Paramount'

#해결은 trigger로! ParamountMovies에 새로운 값이 들어오면 Movie에 title, year에는 새로운 값, studioName에는 paramount가 저장
CREATE TRIGGER ParamountInsert
INSTEAD OF INSERT ON ParamountMovies
REFFERENCING NEW ROW AS NewRow
FOR EACH ROW
INSERT INTO Movies(title, year, studioName) VALUES(NewRow.title, NewRow.year, 'Paramount');

###

3. Indexes in SQL

index: table에 저장된 tuple에 더 빨리 접근하기 위한 data structure.

B-Tree(Balance search Tree) 형식 SQL에서 data들은 heap에 저장이 되는데 select할 때 heap을 처음부터 다 확인하는데 index를 이용하면 더 빨리 확인 가능.

CREATE INDEX BookIndex ON Bookstores(book);
CREATE INDEX SellIndex ON Sells(bookstore, book);

근데 index를 쓴다고 해서 무조건 빨라지는게 아님. 적절한 tuning이 필요. 이 tuning을 돕는게 tuning advisor. 아래의 일을 함. 근데 뭔말인지 잘 모르겠다. 시험 안나오겟지?ㅎㅎ

  1. 해당 db에서 run한 적이 있는 랜덤 query를 선택하는 방법
  2. designer에게 sample workload를 제공(workload = 작업량)

4. Selection of Indexes

Calculation the Best Indexes to Create

이 문제에 들어가기 전에, 사실 이렇게 자세히 알 필요 없이 밑에 표가 주어지면 average를 구하고, p1과 p2의 값이 주어졌을 경우 어떤 것을 선택하는게 좋을지 정도만 구하면 되지만 처음부터 확인하는게 가장 best니까 봅시다.

StarsIn(title, year, starName)이 있고 Q1은 starName이 s(“배수지”)인 (title, year)를 선택하는 query고, Q2는 title = t(“건축학개론”), year = y(“2012”)인 (starName)을 선택하는 query라고 하자. (여기서 s, t, y는 상수, 정해진 어떤 값들)

그리고 I를 INSERT INTO StarsIn VALUES(t,y,s)라고 하자. (StarsIn에 t,y,s를 새로 넣는 작업)

그리고 data에 대해 5가지 assumption이 있다고 가정.

  1. StarsIn은 10page를 점유한다. 그래서 전체 relation을 검사하는데 10의 cost가 든다.
  2. 평균적으로 한 명의 star는 3개의 영화에, 한 영화에는 3명의 star가 나타난다.
  3. star와 movie는 10page에 골고루 있어서, starName에 대한 index와 title, year에 대한 index가 있어도 평균적으로 3개의 tuple을 찾는데 3번의 disk access가 필요함. index가 없으면 10번
  4. 하나의 disk가 접근하면 하나의 page를 읽어서 사용. 만약 수정이 필요하면 또 다른 disk가 와서 그 page를 수정
  5. 마찬가지로 insertion할 때, 하나의 disk가 새로운 tuple이 들어갈 page를 읽음 그리고 또 다른 disk가 가서 page에 새로운 data를 씀.

그럼 Q1에 드는 시간이 p1, Q2에 드는 시간이 p2라고 했을 때, Insertion의 경우 1-p1-p2만큼 사용.

DB8_1

  1. Index가 없으면 10page를 다 확인해야 하므로 Q1, Q2는 10. insertion을 할 때는 2만큼 들음. 새로 들어갈 data가 위치할 장소를 잡는데 1번의 disk access 그 장소에 data를 쓰는데도 1번의 disk access. 그래서 총 2번의 disk access.

    그래서 average는 10p1 + 10p2 + 2(1-p1-p2) = 2 + 8p1 + 8p2

  2. Q1이 star를 다루는 거였으니까 star에 index가 있는 경우. 맨처음 index page에 access하는데 1번, 그리고 index를 통해 각각의 tuple에 aceess하는데 3개의 tuple을 확인해야 하므로 3번. 합쳐서 총 4번. insertion의 경우 각 tuple에 read write하는 page, data를 위한 page가 필요. 그래서 총 4번의 disk access.

    그래서 average는 4p1 + 10p2 + 4(1-p1-p2) = 4 + 6p2

  3. Q2는 2번과 비슷하니까 skip

  4. index가 둘다 있을 경우 각각 4번의 disk access를 하고 insertion의 경우 2개의 index page를 읽고 쓰니까 4번, data page는 각각 1개씩 있으니까 2번. 합쳐서 총 6번의 disk access가 발생.(사실 잘 모르겟다 여긴)

    average는 4p1 + 4p2 + 6(1-p1-p2) = 6 - 2p1 - 2p2

이제 여기서 p1=p2=0.1일 경우 no index가 가장 빠르지만 둘 다 0.4일 경우는 둘다 index하는게 제일 빠르다. p1=0.5, p2=0.1일 경우는 Star index가 가장 빠르다. 매번 상황에 따라 달라지므로 index를 만들지 말지는 상황에 따라 결정하는게 바람직하다.

DB Chapter 7

|

Ch.7: Constraints and Triggers


1. Foreign Key

다른 table에서 가져온 녀석인데 가져오려면 그 table의 primary key 또는 unique만을 가져올 수 있음. 예시를 갖고 끝까지 봅시다.

CREATE TABLE Books (
   name CHAR(20) PRIMARY KEY,
   publisher CHAR(20)
);

#REAL = float(24)
CREATE TABLE Sells (
   bookstore CHAR(20),
   book CHAR(20),
   price REAL,
   FOREIGN KEY book REFERENCES Books(name)
	  ON DELETE SET NULL
	  ON UPDATE CASCADE
);

여기서 sells의 book은 books의 name에 있는 data를 가져오기 때문에 books의 name에 없는 data는 등록할 수 없음. 만약 이를 어길 시 어떻게 하느냐? 바로 reject.

그러면 원래 있던 data의 값을 바꾸거나(update) 지우면(delete) foreign key로 가져온 data는 어떻게 해야할까? 3가지 policy가 존재

  1. Default Policy: 아예 바꾸는 것이 허용이 안댐
  2. Cascade Policy: 삭제하거나 바꾸면 foreign key에 해당하는 값도 삭제하거나 바꿔야 함
  3. Set-null Policy: 삭제하면 foreign key의 값이 null이 됨.

delete, update의 경우를 따로 설정 가능.

Deferred checking of constraint

Circular Constraints: A가 되기 위해서는 B가 필요, B가 되기 위해서는 A가 필요 마치 deadlock

신규채용 요건: 5년 이상 직장 경험자! 아니 경험을 어디서 쌓냐구요~~

Solution!

동시에 2개를 같이 넣는데 transaction 끝나기 전까지는 다른 애들이 못건드리게 해서 동시에 넣으면 됨. lock을 걸고 풀고~ 이게 DEFERRABLE

default로는 initally immediate인데 initally deferrable로 바꾸면 해결~

2. Constraints on Attribute and Tuple

Check Constraints

CHECK(condition) 의 형식으로 사용. check는 attribute나 tuple이 바뀔때(insert, update) condition을 check함. 새로 바뀌는 값이 condition을 만족하지 못할경우 reject.

Tuple based constraints는 data가 자주 바뀌지만 Attribute based constraints는 attribute가 자주 바뀌지는 않음.

4. Assertion

relation에서 변화가 일어날 때 마다 check하는 것.

CREATE ASSERTION name

CHECK(condition)

Bookstore(name, addr, license)
Students(studentID, name, phone)

CREATE ASSERTION FewBookstore
CHECK(
   (SELECT COUNT(*) FROM Bookstore) <= (SELECT COUNT(*) FROM Students)
);

Bookstore나 Students에 변화가 일어날 때 마다 check

Assertion은 insert, delete, update를 다 check하지만 tuple based check는 insert, update만

DB7-1

5. Trigger

Trigger는 특정한 case만 check함. Assertion은 다하는데 oracle의 경우 assertion대신 triger만 사용

Event(insert, update 등), Condition(where부분), Action(SQL statement) 로 나뉨

Sells(bookstore, book, price)
CheatingBookstores(bookstore)

#책들의 가격이 이전보다 1달러 더 오른 서점들만 cheatingbookstore에 들어감.
CREATE TRIGGER PriceTrig
AFTER UPDATE OF price ON Sells
FOR EACH ROW
WHEN(new.price > old.price + 1.00)
  BEGIN
  INSERT INTO CheatingBookstore VALUES(:new.bookstore);
  END;
.
run

after는 before로도 사용 가능하고, view라면 instead of 로 바뀜. 위의 경우 sells의 price가 update될 때만 check. when은 조건문

after의 경우 update나 insert를 일단 실행을 하고 when에 있는 조건문을 보고 조건문이 맞으면 begin ~ end를 실행.

before의 경우 조건문을 보고 begin ~ end를 실행하고 update나 insert를 실행(after는 확실한데 before는 잘 모르것네요)

. run은 trigger를 DB에 저장시킴.