DA04 Object to Relational Behavioural Design
Tags: learning, design, architecture
Published: 2020-08-24 12:32:04

## 本篇

这些pattern主要是处理数据从**对象**以及**关系型数据库**之间的传递问题。

# 只有Data-source Layer的问题

- 如果要处理大量的数据，我们需要一口气把这些数据读出来，再一口气写回去；
- concurrency

# Unit of Work

The unit of work pattern describes a way to keep track of which domain objects have changed (or new objects created), so that only those objects that have changed need to be updated in the database.

记录那些对象改变了，然后更新数据库中对应的记录。

有一个Unit of Work对象，包含了**4**个`list`一个`commit()`方法。

- `new list`：新增的对象，需要插入到数据库中
- `dirty list`：修改过的对象
- `clean list`：未修改过的对象（实际情况下，`clean list`无需创建）
- `delete list`：删除的对象，需要在数据库中删除相应记录

有两种方法更新这些list：

- **Caller registration**：在代码附近增加把对象放进list里的操作，比例new出一个对象后立即把它放进`new list`中；
- **Object registration**：让对象自己把自己放进相应的list中，比如在setter方法里和构造方法里添加相应的代码。

以**Object registration**为例（线程安全相关的代码省略）：
```java
public class Song extends DomainObject {
    private String name;
    private String artist;
    ...

    public Song(String name, String artist, ...) {
        this.name = name;
        ...
        UnitOfWork.registerNew(this);
    }

    public void setName(String name) {
        this.name = name;
        ...
        UnitOfWork.registerDirty(this);
    }
}
```

```java
public class UnitOfWork {
    private static ThreadLocal current = new ThreadLocal();

    private List<DomainObject> newObjects = new ArrayList<DomainObject>();
    private List<DomainObject> dirtyObjects = new ArrayList<DomainObject>();
    private List<DomainObject> deletedObjects = new ArrayList<DomainObject>();

    // put obj into the New list
    public void registerNew(DomainObject obj) {
        Assert.notNull(obj.getId(), "id is null", );
        Assert.isTrue(!dirtyObjects.contains(obj), "object is dirty");
        Assert.isTrue(!deletedObjects.contains(obj), "object is deleted");
        Assert.isTrue(!newObjects.contains(obj), "object is new");
        newObjects.add(obj);
    }

    public void registerDirty(DomainObject obj) {...}
    public void registerDelete(DomainObject obj) {...}

    // Let the data-source layer to do the db-things
    public void commit() {
        for (DomainObject obj : newObjects) {
            DataMapper.getMapper(obj.getClass()).insert(obj);
        }
        for (DomainObject obj : dirtyObjects) {
            DataMapper.getMapper(obj.getClass()).update(obj);
        }
        for (DomainObject obj : deletedObjects) {
            DataMapper.getMapper(obj.getClass()).delete(obj);
        }
    }
}
```

## Unit of Work的优缺点

### Pros

- High cohesion: 高内聚，关于已更改、添加或删除内容的所有信息都包含在每个线程的单个对象中
- 简单
- 高效

### Cons

- 容易忘记将对象放入相应的列表中

# Identity Map

Ensures that each object gets loaded only once by keeping every loaded object in a map. Looks up objects using the map when referring to them.

只要用来防止同一个数据被多次从数据库中读出来。

实现的方法是对数据库的每一个表构造一个map（即代码中的`Map<Class, IdentityMap> singleton`），利用数据库中的key来判断那些记录已经被读出来了。

```java
public class IdentityMap<E> {
    private Map<Long, E> map = new HashMap<Long, E>();
    private static Map<Class, IdentityMap> singleton = 
        new HashMap<Class, IdentityMap>();

    public static <E> IdentityMap<E> getInstance(E e) {
        IdentityMap<E> result = singletons.get(e.getClass());
        if (result == null) {
            result = new IdentityMap<E>();
            singletons.put(e.getClass(), result);
        }
        return result;
    }

    public void put(long id, E o) {
        map.put(id, o);
    }

    public E get(long id) {
        return map.get(id);
    }
}
```

## Identity Map的优缺点

### Pros

- 可以直接使用`==`比较对象，而不是使用`equals()`
- 简单
- 高效

### Cons

- 暂时没有

# Lazy Load

An object that does not contain all of the data that you need, but knows how to get it.

Lazy Load旨在降低对数据库的读操作，只读需要的数据。

## 实现Lazy Load的四种方法：

- **Lazy Initialisation**: 每一个对象在创建的时候，它的所有属性都设置为null，需要某个属性的时候再从数据库里读取这个属性；
- **Virtual Proxy**
- **Value Holder**
- **Ghost**：与**Lazy Initialisation**类似，只是需要某个属性时，从数据库里读取这个对象的全部属性。

### Lazy Initialisation
```java
public class Student {
    private int id;
    private String firstName;
    private String lastName;
    ...

    public String getLastName() {
        if(this.lastName == null) {
            // load lastName from db
            Record record = gateway.find(this.id);
            this.lastName = record.get("lastName");
        }
        return this.lastName;
    }
    
    ...
}
```

### Ghost
```java
public class Student {
    private int id;
    private String firstName;
    private String lastName;
    ...

    public String getLastName() {
        if(this.lastName == null) {
            load();
        }
        return this.lastName;
    }
    
    ...

    // load all the attributes from db
    public void load() {
        Record record = gateway.find(this.id);
        if(this.lastName == null) {
            this.lastName = record.get("lastName");
        }
        // load other attributes
        ...
        
    }
}
```

### Virtual Proxy

与代理模式相同。

A proxy controls access to the original object, allowing you to perform something either before or after the request gets through to the original object.

首先创建一个接口，里面的方法和实体类里的一样，

```java
public interface StudentI {
    public int getId();
    public void setId(int id);
    public String getFitstName();
    public void setFitstName(String firstName);
    ...
}
```

```java
public class StudentProxy implements StudentI {
    // the real object
    private StudentI source;
    
    private StudentI getSource() {
        if (source == null) {
            load();
        }
    }
    
    public void setLastName(String lastName) {
        getSource().setLastName(lastName);
    }

    public String getLastName() {
        return getSource().getLastName();
    }

    ...
}
```

### Value holder

与Virtual Proxy类似，只是Value Holder类不像Virtual Proxy一样拥有一样的方法接口。所有的实体类都共享一个Value Holder。

```java
// Java function to illustrate
// Lazy Initialization in
// Lazy Loading Design Pattern
public class ValueHolder<T> {
    private T value;
    private readonlyFunc<object, T> valueRetrieval;
    // Constructor
    public ValueHolder(Func<object, T> valueRetrieval) {
        valueRetrieval= this.valueRetrieval;
    }

    // We'll use the signature "GetValue" for convention
    public T GetValue(object parameter) {
        if (value == null)
            value = valueRetrieval(parameter);
        return value;
    }
}
```

## Lazy Load的优缺点

### Pros

- 高效

### Cons

- 复杂
- 继承问题：如果实体类间有继承关系，那么会使得不知道该创建哪个类
- 有时候一次性读取大量的记录效率更高