DA05 관계형 구조 설계


本篇主要讲如何实现程序里的对象与关系数据库之间的记录进行对应,这个问题可以分为以下几个小问题:
  • 程序中对象通过引用或指针来进行标注,而关系数据库则是通过键;
  • 对象有설정和리스트等容器,而关系数据库则没有;
  • 关系数据库没有继承机制

  • 정합성 보장 필드
    객체에 데이터베이스 ID 필드를 저장하여 메모리에 있는 객체와 데이터베이스 행 간의 객체 ID를 유지합니다.
    通过保存数据库中的主键来保证对象的唯一
    class KeyTable {
    
        public int getKey(String tableName) {
            //get the next key from the table
            String query = "SELECT nextID FROM keys WHERE name = {0} FOR UPDATE";
            String queryPrepared = DB.prepare(query, tableName);
            IDbCommand comm = new OleDbCommand(queryPrepared, DB.Connection);
            ResultSet rs = comm.executeReader();
            Record record = rs.get(0);
            int result = record.getLong(1);
    
            //update the table with the next key
            int nextKey = result + 1;
            String update = "UPDATE keys SET nextID = {0} WHERE name = {1}";
            String updatePrepared = DB.prepare(update, nextKey, tableName);
            comm = new OleDbCommand(queryPrepared, DB.Connection);
            comm.executeNonQuery();
    
            return result;
        }
    }
    
    实际上以上的例子已经很少使用,因为数据库现在支持主键自动递增了.
    另外,对于之前的一些패턴无需使用정합성 보장 필드例如테이블 모듈, 트랜잭션 스크립트, 테이블 데이터 게이트웨이它们的영역对象中已经存贮了记录在数据库中的主键.

    외부 키 매핑
    객체 간의 연관을 테이블 간의 외부 키 참조에 매핑합니다.
    如果영역对象中包含了组合关系,例如개체 A的一个熟悉是객체 B那么数据库中,A对应的表和B对应的表就会有一个外键关系.
    下面的例子中,아티스트会有多个앨범一个앨범含有多个앨범表中有一个아티스트外键,추적하다.表中有一个앨범外键.
    class Artist {
        private int id;
        private String name;
        ...
    }
    
    class Track {
        private int id;
        private String title;
        private String style;
        ...
    }
    
    class Album {
        private int id;
        private String title;
        private Artist artist;
        private ArrayList<Track> tracks;
        ...
    }
    
    아티스트, 트랙 및 앨범对于的数据库表如下
    Artist:
        id: int
        name: varchar
    
    Track:
        id: int
        album_id: int (foreign key)
        title: varchar
    
    Album:
        id: int
        artist_id: int (foreign key)
        title: varchar
    
    对于的데이터 매퍼:
    class AlbumMapper {
    
        public Album find(long id) {
            String sql = "SELECT ID, artistID, title " +
                " from albums " +
                " WHERE ID = {0}";
            String sqlPrepared = DB.prepare(sql, id);
            IDbCommand comm = new OleDbCommand(sqlPrepared, DB.Connection);
            ResultSet rs = comm.executeQuery();
            rs.next();
    
            //get the artist information
            long artistID = rs.getLong(1);
            ArtistMapper artistMapper = new ArtistMapper();
            Artist artist = artistMapper.find(artistID);
    
            //get the track information
            TrackMapper trackMapper = new TrackMapper();
            Track [] tracks = trackMapper.findForAlbum(id);
            Album result = new Album(id, title, artist, tracks);
            return result;
        }
    }
    
    class TrackMapper {
        public Track [] findForAlbum(long albumId) {
            String sql = "SELECT ID, title " +
                " from tracks " +
                " WHERE albumID = {0}";
            String sqlPrepared = DB.prepare(sql, albumID);
            IDbCommand comm = new OleDbCommand(sqlPrepared, DB.Connection);
            ResultSet rs = comm.executeQuery();
    
            Track [] result = new Track[rs.size()];
            for (int i = 0; i < rs.size(); i++) {
                rs.next();
                result[i] = new Track(rs.getLong(0), rs.getString(1));
            }
            return result;
        }
    }
    
    但是执行앨범的찾아내다方法会总共执行三次JDBC的질의하다这样效率比较低,我们可以直接使用数据库的连表查询来对찾아내다方法进行修改:
    class AlbumMapper {
        public Album find(long id) {
            String sql = "SELECT a.ID, a.artistID, a.title, r.name " +
                " from albums a, artists r " +
                " WHERE ID = {0} and a.artistID = r.ID";
            String sqlPrepared = DB.prepare(sql, id);
            IDbCommand comm = new OleDbCommand(sqlPrepared, DB.Connection);
            ResultSet rs = comm.executeQuery();
            rs.next();
    
            //get the artist information
            long artistID = rs.getLong(1);
            String artistName = rs.getString(3);
            Artist artist = new Artist(artistID, artistName);
    
            //get the track information
            ...
        }
    }
    

    외부 키 매핑的优缺点

    찬성 의견
  • 일대다的情况会比较简单

  • 기만하다
  • 不支持다대다的情况

  • 연관 테이블 매핑
    연관된 링크를 포함하는 테이블의 외부 키를 포함하는 테이블로 저장합니다.
    对于多对多的情况,我们无法用一个外键来表示这样的关系,연관 테이블 매핑就仿照了关系数据库来创建一个新的表来记录这些关系.
    还是아티스트的例子,아티스트和계기有多对多的关系:
    class Artist {
        private int id;
        private String name;
        ...
    }
    
    class Instrument {
        private int id;
        private String name;
    }
    
    Artist:
        id: int
        name: varchar
    
    Instrument:
        id: int
        name: varchar
    
    artist-instruments:
        artist_id: int
        instrument_id: int
    
    对应的,예술의 대가会是这样:
    class ArtistMapper {
        public Artist find(long id) {
            String sql = "SELECT ID, name " +
                " from artists " +
                " WHERE ID = {0}";
            String sqlPrepared = DB.prepare(sql, id);
            IDbCommand comm = new OleDbCommand(sqlPrepared, DB.Connection);
            ResultSet rs = comm.executeQuery();
            rs.next();
    
            //get the name
            String name = rs.getString(1);
    
            //get the instruments information
            Instrument [] instruments = loadInstruments(id);
    
            Artist result = new Artist(id, name, instruments);
            return result;
        }
    
        // find instruments that the artist plays in artist-instruments table
        public Instrument [] loadInstruments(long artistID) {
            String sql = "SELECT artistID, instrumentID " +
                " from artist-instruments " +
                " WHERE artistID = {0}";
            String sqlPrepared = DB.prepare(sql, id);
            IDbCommand comm = new OleDbCommand(sqlPrepared, DB.Connection);
            ResultSet rs = comm.executeQuery();
    
            //load the instrument details using an InstrumentMapper
            Instrument [] result = new Instrument[rs.size()];
            InstrumentMapper instrumentMapper = new InstrumentMapper();
            for (int i = 0; i < rs.size(); i++) {
                rs.next();
                result[i] = instrumentMapper.find(rs.getLong(2));
            }
            return result;
        }
    }
    

    연관 테이블 매핑的优缺点

    찬성 의견
  • 巧妙

  • 기만하다
  • 复杂

  • 삽입값(매핑에 의존)
    한 객체를 다른 객체 테이블의 여러 필드에 매핑합니다.
    포함된 값은 객체의 값을 소유자의 필드에 매핑합니다.또한 포함된 값은 소속 객체를 로드/저장할 때 로드/저장됩니다.
    有时我们不需要为每一个类都创建一个对应的数据库表,因为有一些表会显得没有意义.
    总的来说,내포된 가치只存在于一对一的关系.
    class Employee {
        int id;
        String name;
        Period period;  // working period
        Money salary;
    }
    
    class Period {
        Date startDate;
        Date endDate;
    }
    
    class Money {
        float amount;
        String currency;
    }
    
    我们并不为上面的三个类创建三个表,而是之创建一个表:
    Employee:
        id: int
        name: varchar
        startDate: Date
        endDate: Date
        salary: float
        currency: varchar
    
    对应的제도원如下:
    class EmploymentMapping {
    
        public Employment find(long id) {
            String sql = "SELECT * from Employments WHERE id = {0}";
            String sqlPrepared = DB.prepare(sql, id);
            IDbCommand comm = new OleDbCommand(sqlPrepared, DB.Connection);
            ResultSet rs = comm.executeQuery();
            Record record = rs.get(0);
    
            //lookup the information from the Person table
            long personID = rs.getFloat(2);
            Person person = personMapper.find(personID);
    
            //create the data range and money objects
            Date startDate = rs.getDate(3);
            Date endDate = rs.getDate(4);
            DateRange dateRange = new DateRange(startDate, endState);
    
            float amount = rs.getFloat(5);
            String currency = rs.getString(6);
            Money money = new Money(amount, currency);
    
            //create the Employment instance
            Employment result = new Employment(id, person, dateRange, money);
            return result;
        }
    
        ...
    }
    

    표 상속
    클래스의 계승 차원 구조를 하나의 표로 표시하고 이 표는 각 클래스의 모든 필드의 열을 포함한다.
    用一张表来存储所有种类的학급这张表的列包含了所有的属性,对于不同학급的반대, 반대하다它们所在的行只使用它的属性对应的字段,其余字段则设为空.同时,还需要一个记录其类型的字段타이핑
    我们有三个类:선수并有以下的继承关系:
    class Player {
        protected String name;
        protected int age;
    }
    
    class Footballer extends PLayer {
        protected String club;
    }
    
    class Cricketer extends PLayer {
        protected int battingAverage;
    }
    
    对应的数据库表,我们仅创建一个,包含以上所有属性以及一个유형:
    Player:
        name: varchar
        age: int
        club: varchar
        batting_average: int
        type: int
    

    표 상속的优缺点

    찬성 의견
  • 简单
  • 높은 확장성: 차원 구조에서 필드를 이동할 때 표를 변경할 필요가 없고 해당하는 코드만 있으면 재구성 디자인이 가능하다.
  • 연결 없음: 시계가 하나밖에 없어서

  • 기만하다
  • 性能较差,因为只有一张表,会限制并发,잦은 잠금
  • 空间利用率低

  • 클래스 테이블 상속
    클래스의 계승 차원 구조를 나타내고 클래스마다 표가 있다.
    与표 상속相对,为每一个类创建一个탁자.
    每一次对子类表的数据库操作,都会与其父类表进行连表操作.
    还是用上面플레이어的例子,这次的表是这样的:
    Player:
        name: varchar
        age: int
    
    Footballer:
        club: varchar
    
    Cricketer:
        batting_average: int
    

    클래스 테이블 상속的优缺点

    찬성 의견
  • 直观

  • 기만하다
  • 性能瓶颈,因为每一次操作都会涉及到最终父类
  • 재구성: 계승 차원 구조에서 필드를 한 클래스에서 다른 클래스로 이동하려면 데이터베이스와 코드를 변경해야 한다.
  • 연결:连表操作

  • 콘크리트 시계 계승
    클래스의 계승 차원 구조를 나타내고 차원 구조에서 각 구체적인 클래스는 표가 있다.
    与클래스 테이블 상속类似,有多个表,子类对应的表包含了所有的属性:
    Footballer:
        name: varchar
        age: int
        club: varchar
    
    Cricketer:
        name: varchar
        age: int
        batting_average: int
    

    세부 상속的优缺点

    찬성 의견
  • 简单
  • 直观
  • 연결 없음
  • 병목 지점 없음, 로드 분산

  • 기만하다
  • 需要生成唯一的主键,即使在不同的表之间,主键也要保证唯一
  • 재구성: 계승 차원 구조에서 필드를 한 클래스에서 다른 클래스로 이동하려면 데이터베이스와 코드를 변경해야 한다.
  • 维护性差,如果父类的属性修改了,那么会导致大量的表也要跟着修改
  • 全局查询慢,如果不知道要查询内容的类型,就需要在所有的表内进行查询
  • 좋은 웹페이지 즐겨찾기