본문 바로가기
대한상공회의소 스마트팩토리 교육/데이터베이스

[데이터베이스 운영] DBMS 기초 «수업-9» : BLOB을 활용한 이미지 파일(Color) 데이터베이스(DB)에 저장 방법 Using MySQL

by 나는영하 2022. 5. 4.

※ 주의사항 

본 블로그는 수업 내용을 바탕으로 제가 이해한 부분을 정리한 블로그입니다.
본 내용을 참고로만 보시고, 틀린 부분이 있다면 지적 부탁드립니다!

감사합니다😁

 


이미지파일(COLOR) 데이터베이스에 저장

BLOB


안녕하세요!!

오늘은 칼라 이미지 파일을 데이터베이스에 저장하고 데이터베이스에 저장된 데이터를 다시 칼라 이미지로 변환하는 과정을 알아보겠습니다.

 

어제와 다른점은 어제는(그레이 스케일 이미지) 화소점 값 하나 하나를 별도의 테이블에 저장을 하였습니다. 따라서 64 x 64 해상도의 이미지 파일이라면 4,096개의 테이블 행이 필요로 했습니다. 하지만 오늘은 파일의 화소점 값을 한번에(BLOB) 그룹지어서 한개의 테이블 행에 저장하도록 하겠습니다. 이러한 과정은 어제보다 훨씬 빠른 처리속도를 보여줄 것입니다. 

 

✔ BLOB(Binary Large Object) : 이진 데이터를 저장하기 위한 DB의 필드 유형, 이미지 파일의 경우 Binary(이진) 데이터로 이루어져 있기 때문에 BLOB유형의 데이터로 만들어서 DB에 한번에 저장 할 수 있다.

 


1 칼라 이미지 파일을 저장하기 위한 테이블 생성(MySQL)

지난번엔 image 테이블과 pixel 테이블 두개를 만들어서 image 테이블의 PRIMARY KEY 열인 f_id를 기준으로 pixel 테이블에 화소점 값을 저장하였습니다.

 

오늘은 테이블은 한가지만 사용할 것이며(pixel 테이블 미사용) image 테이블에 이미지 화소점 값인 binary 데이터의 그룹을 저장하기 위한 열(타입 : LONGBLOB)을 추가해서 사용할 예정입니다.

 

따라서 어제처럼 기본키와 외래키의 관계를 고민할 필요도 없고, 추가 테이블을 생성해서 메모리를 차지할 필요가 없기 때문에 DB에 업로드 및 다운로드시 개선된 속도를 확인하실 수 있습니다. 

CREATE DATABASE color_db;
USE color_db;

 CREATE TABLE blob_image(
                f_id  INT NOT NULL PRIMARY KEY,
                fname  VARCHAR(256) NOT NULL,
                extname VARCHAR(10) NOT NULL DEFAULT 'PNG',
                fsize BIGINT NOT NULL,
                f_width SMALLINT NOT NULL,
                f_height SMALLINT NOT NULL,
                f_data LONGBLOB -- 덩어리당 2GB까지 저장 가능 / MS SQL에는 없음
);

이미지의 이진(Binary) 데이터 파일 한번에 저장하기 위한 열(f_datas)을 추가했습니다. 해당 열의 데이터 타입은 BLOB 타입이며 MySQL에서는 총 4가지의 BLOB타입을 가지고 있습니다.

  • BLOB
  • TINYBLOB
  • MEDIUMBLOB
  • LONGBLOB

각각의 차이점은 저장할 수 있는 용량의 차이이며 제가 사용한 LONGBLOB은 최대 4기가 까지 저장이 가능합니다. 


2. 칼라 이미지 파일을 데이터베이스(DB)에 업로드 하기

칼라 이미지 파일 데이터베이스에 업로드 Form

DB와 연결하고 OpenFileDialog를 통해 이미지 경로를 구하는 과정은 지난 내용과 동일하기 때문에 설명은 생략하겠습니다. 지난 내용을 참고해주세요 ^^:

 

2022.05.03 - [데이터베이스] - [데이터베이스 운영] DBMS 기초 «수업-8» : 이미지 파일(RAW) 데이터베이스(DB)에 저장 방법

 

[데이터베이스 운영] DBMS 기초 «수업-8» : 이미지 파일(RAW) 데이터베이스(DB)에 저장 방법

※ 주의사항 ※ 본 블로그는 수업 내용을 바탕으로 제가 이해한 부분을 정리한 블로그입니다. 본 내용을 참고로만 보시고, 틀린 부분이 있다면 지적 부탁드립니다! 감사합니다😁 이미지 파일(R

920416.tistory.com

 

 private void btn_upload_Click(object sender, EventArgs e)
        {
            /*  CREATE TABLE blob_image(
                f_id  INT NOT NULL PRIMARY KEY,
                fname  VARCHAR(256) NOT NULL,
                extname VARCHAR(10) NOT NULL DEFAULT 'RAW',
                fsize BIGINT NOT NULL,
                f_width SMALLINT NOT NULL,
                f_height SMALLINT NOT NULL
                f_data LONGBLOB -- 덩어리당 2GB까지 저장 가능 / MS SQL에는 없음
            );  */            
            String fullname = tb_fullname.Text;
            // '\'를 기준으로 문자열을 그룹으로 나눈다. 
            //   C:\images\Color\LENNA512.png
            // 즉 위와 같은 경로일경우 tmp[0] : c: / tmp[1] : iamges / tmp[2] : Color
            String[] tmp = fullname.Split('\\');
            String filename = tmp[tmp.Length - 1]; // tmp[] 배열의 마지막 즉, 
            tmp = filename.Split('.'); //
            String fname = tmp[0];
            String extname = tmp[1];
            long fsize = new FileInfo(fullname).Length;
            int f_height; int f_width;
            
            // 비트맵 활용
            Bitmap paper = new Bitmap(fullname);
            f_width = paper.Height;
            f_height = paper.Width;

            Random rnd = new Random();
            int f_id = rnd.Next(int.MinValue, int.MaxValue);

            // 쿼리문 입력
            sql = "INSERT INTO blob_image(f_id,fname, extname, fsize, f_width, f_height, f_data)";
            sql += "VALUES(" + f_id + ",'" + fname + "', '" + extname + "', " + fsize;
            sql += ", " + f_width + ", " + f_height + ", @BLOB_DATA)";

            // 파일 덩어리들 준비
            FileStream fs = new FileStream(fullname, FileMode.Open, FileAccess.Read);
            byte[] blob_data = new byte[fsize];
            fs.Read(blob_data, 0,(int)fsize);
            fs.Close();

            cmd.Parameters.Clear();
            cmd.Parameters.AddWithValue("@BLOB_DATA", blob_data);

            cmd.CommandText = sql;
            cmd.ExecuteNonQuery();

            tb_fullname.Text = "";
            listView();
        }

중간의 비트맵 활용부 부터 어제와 오늘 코드가 달라집니다.

RAW확장자가 아닌 칼라 이미지 파일들은 이미지의 폭과 높이의 길이가 서로 다르기 때문에 어제처럼 Math.Sqrt를 통해 구할수는 없습니다. 따라서 Bitmap 생성자를 통해 이미지의 폭과 높이를 구하였습니다. (이러한 내용도 지난 미니프로젝트 Ver2에서 다루었던 코드이니 지난글을 참고해주세요.!!)

 

오늘 확인해야하는 부분은 Binary Data를 한번에 저장하기 위한 과정입니다.

① INSERT Quarry문에 @BLOB_DATA 라는 변수를 사용 (MySQL에서 골뱅이(@)는 변수를 의미합니다.)

FileStream 생성자를 통해 이미지 파일의 이진 데이터 파일을 읽어와서 byte 행렬에 저장합니다.

MySqlCommand.Parameters.AddWithValue 메서드를 통해 변수(@BLOB_DATA)에 값을 저장합니다. 

④ ①에서 생성한 쿼리문을 전송합니다.

⑤ TextBox를 빈칸으로 만들고, 하단의 ListView를 리셋해줍니다. (ListView에 대한 설명은 아래에서 설명하겠습니다.)

 

FAKER.jpg 이미지 파일 데이터베이스에 업로드 

 


3. 데이터베이스(DB)에 있는 데이터를 칼라 이미지 파일로 다운로드 하기

데이터베이스의 데이터를 칼라 이미지 파일로 다운로드

ListView에서는 DB에 있는 현재 테이블의 데이터를 보여주며,

좌측의 체크박스를 체크하면 TextBox에 파일 번호를 자동으로 입력하게 합니다. 

그리고 다운로드를 누르게 되면 해당 칼라 이미지 DB를 파일 형태로 다운로드 할 수 있습니다. 

 


3-1. listView 함수부

 void listView()
        {
            listView1.Clear(); // listView 호출시 기존 항목 리셋
            listView1.GridLines = true; // 데이터 사이사이 점선으로 구분
            listView1.CheckBoxes = true; // 좌측에 체크박스 사용 (파일 번호 불러오는용)
            listView1.View = View.Details; // ListView를 엑셀모양으로 표시

            listView1.Columns.Add("파일 번호");
            listView1.Columns.Add("파일명");
            listView1.Columns.Add("확장자명");
            listView1.Columns.Add("파일 크기");
            listView1.Columns.Add("이미지 폭");
            listView1.Columns.Add("이미지 높이");

            ListViewItem Item;
            String f_id, fname, extname, fsize, f_width, f_height;

            sql = "SELECT f_id, fname, extname, fsize, f_width, f_height FROM blob_image";

            cmd.CommandText = sql;
            MySqlDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                f_id = reader["f_id"].ToString();
                fname = reader["fname"].ToString();
                extname = reader["extname"].ToString();
                fsize = reader["fsize"].ToString();
                f_width = reader["f_width"].ToString();
                f_height = reader["f_height"].ToString();

                Item = new ListViewItem(f_id);
                Item.SubItems.Add(fname);
                Item.SubItems.Add(extname);
                Item.SubItems.Add(fsize);
                Item.SubItems.Add(f_width);
                Item.SubItems.Add(f_height);

                listView1.Items.Add(Item);
            }
            reader.Close();
        }

blob_image 테이블에 있는 데이터들을 ListView에 열거하는 함수부입니다. 

별도로 사용자 정의 함수 형태로 만들어서 업로드시에도 해당 ListView가 리셋될 수 있도록 하였습니다.

 

ListView의 CheckBox를 사용해서 파일 번호를 TextBox에 불러오는 부분 말고는 기존에 했던 코드부입니다.

 


3-2. listView_ItemChecked 이벤트 함수부

private void listView1_ItemChecked(object sender, ItemCheckedEventArgs e)
        {
            if(listView1.CheckedItems.Count > 0 & listView1.CheckedItems.Count < 2) { 
            String f_id = listView1.CheckedItems[0].ToString();
            f_id = Regex.Replace(f_id, @"[^0-9-]", ""); // -와 숫자를 제외하고 모두 공백
            tb_f_id.Text = f_id;
            } 
            else if(listView1.CheckedItems.Count >= 2)
            {
                MessageBox.Show("한개만 체크해주세요.");

            }
            else { }
        }

제가 위에서 listView.CheckBoxes를 사용(Ture)한다고 했기 때문에 해당 이벤트를 사용할 수 있게되었습니다. 좌측에 생성된 체크박스를 체크할때마다 발생하는 이벤트입니다. 

 

아직은 다중 DB를 일괄적으로 다운로드하는 기능은 없기 때문에 체크박스 한개만 선택할 수 있도록 if문을 통해 체크박스를 한개만 선택할 수 있도록 하였습니다. 

 

체크박스를 선택하면 선택한 ListView 행의 0번째 Item 즉, 파일번호(f_id)를 String 형태로 TextBox에 보여지게 됩니다. 파일번호는 음수기호(-)와 숫자로 이루어져있기 때문에 Regex.Replace를 사용해서 다른 문자열들은 공백으로 변환하였습니다. (해당 코드를 사용하지 않으면 파일 번호 말고도 다른 문자열을 출력합니다..ㅠ)

 


3-3. 다운로드 버튼 클릭 함수부

private void button1_Click(object sender, EventArgs e)
        {
            
            String f_id = tb_f_id.Text.ToString();
            sql = "SELECT f_id,fname, extname, fsize, f_data FROM blob_image WHERE f_id = " + f_id ;
            cmd.CommandText = sql;
            MySqlDataReader reader = cmd.ExecuteReader();
            reader.Read();

            String fname = reader["fname"].ToString();
            String extname = reader["extname"].ToString();
            int fsize = int.Parse(reader["fsize"].ToString());
            byte[] f_data = new byte[fsize];
            reader.GetBytes(reader.GetOrdinal("f_data"), 0,f_data,0, fsize);

            String fullname = " C:\\images\\" + fname + "." + extname;
            FileStream fs = new FileStream(fullname, FileMode.OpenOrCreate, FileAccess.Write);
            fs.Write(f_data, 0, (int)fsize);
            fs.Close();

            MessageBox.Show("다운로드 완료");
        }

BLOB형태의 데이터는 reader.GetBytes 메서드를 사용해서 Byte 배열(f_data)에 바이트 스트림을 저장합니다.자세한 설명은 아래의 사이트를 참고 바랍니다. (저도 완전히 이해를 하지 못한채 넘어갔습니다..😥)https://dev.mysql.com/doc/dev/connector-net/6.10/html/M_MySql_Data_MySqlClient_MySqlDataReader_GetBytes.htm

 

MySqlDataReader.GetBytes Method

MySqlDataReaderGetBytes Method Reads a stream of bytes from the specified column offset into the buffer an array starting at the given buffer offset. Namespace:  MySql.Data.MySqlClient Assembly:  MySql.Data (in MySql.Data.dll) Version: 6.10.9 public over

dev.mysql.com

 

업로드와는 반대로 FileStream 생성자를 통해 이미지 파일의 이진 데이터 파일을 저장하였습니다.

 

위와 같은 함수 코드부는 이미 정형화되어있는 코드부라 본 블로그에 정리를 해두고 추후에 필요할때 확인해서 적용할 수 있을 정도로만 이해하고 넘어가겠습니다. 😁

댓글