Статьи

Пользовательские типы пользователей в GORM

Недавно я хотел смоделировать мерчанта, который, как и многие вещи в модели предметной области, имел адрес. Я думал, что имеет смысл, что адрес был встроен в продавца. Причины:

  • У Продавца не было жизненного цикла. Торговец умирает, так должен адрес.
  • Он принадлежал только одному и только одному Торговцу

Совершенно очевидно, что это были композиционные отношения.

Теперь возможно моделировать композиционные отношения в GORM. Смотрите здесь Однако этот подход сопровождается оговоркой, что адрес должен быть объектом GORM. Я не хотел, чтобы Адрес был объектом GORM, потому что объекты GORM мощны в Grails. Со всеми своими динамическими поисковиками и API-интерфейсами GORM они по сути как DAO на стероидах. Если разработчик получает в свои руки, он может сделать много вещей (не всегда хорошие вещи). Я не хотел или не нуждался в этом. Кроме того, хорошая архитектура мешает разработчикам делать ошибки, когда они работают под давлением на высоких скоростях. Это означает, что когда вы принимаете проектные решения, вам нужно думать о силе, которую вы должны дать, дать и дать.

Имея это в виду, я изучил возможность создания пользовательского типа для Address. Это была бы просто структура данных, которая смоделировала бы адрес, могла бы быть повторно использована за пределами Продавца (таким образом, продвигая согласованность и снова продвигая, таким образом, продвигая хороший дизайн) и не придавая силы GORM. В документации GORM есть некоторая документация для пользовательских типов, но не полный рабочий пример. Я взглянул на некоторые примеры Hibernate, а затем сумел собрать их воедино и приступить к работе.

Вот мой адрес объекта.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Immutable
class Address {
 
    private final String city;
    private final String country;
    private final String state;
    private final String street1;
    private final String street2;
    private final String street3;
    private final String zip;
 
    public String getCity() {
        return city;
    }
 
    public String getCountry() {
        return country;
    }
 
    public String getZip() {
        return zip;
    }
 
    public String getState() {
        return state;
    }
 
    public String getStreet1() {
        return street1;
    }
 
    public String getStreet2() {
        return street2;
    }
 
    public String getStreet3() {
        return street3;
    }
}

Вот мой объект AddressUserType:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class AddressUserType implements UserType {
 
    public int[] sqlTypes() {
        return [
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType()
        ] as int[]
    }
 
    public Class getReturnedClass() {
        return Address.class;
    }
 
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
        assert names.length == 7;
        log.debug(">>mullSafeGet(name=${names}")
        String city = StringType.INSTANCE.get(rs, names[0], session); // already handles null check
        String country = StringType.INSTANCE.get(rs, names[1], session ); // already handles null check
        String state = StringType.INSTANCE.get(rs, names[2], session ); // already handles null check
        String street1 = StringType.INSTANCE.get(rs, names[3], session ); // already handles null check
        String street2 = StringType.INSTANCE.get(rs, names[4], session ); // already handles null check
        String street3 = StringType.INSTANCE.get(rs, names[5], session ); // already handles null check
        String zip = StringType.INSTANCE.get(rs, names[6], session ); // already handles null check
 
        return city == null && v == null ? null : new GAddress(city: city, country: country, state: state, street1: street1, street2: street2,  street3: street3, zip: zip);
    }
 
    void nullSafeSet(java.sql.PreparedStatement st, java.lang.Object value, int index, org.hibernate.engine.spi.SessionImplementor session) throws org.hibernate.HibernateException, java.sql.SQLException {
        if ( value == null ) {
            StringType.INSTANCE.set( st, null, index );
            StringType.INSTANCE.set( st, null, index+1 );
            StringType.INSTANCE.set( st, null, index+2 );
            StringType.INSTANCE.set( st, null, index+3 );
            StringType.INSTANCE.set( st, null, index+4 );
            StringType.INSTANCE.set( st, null, index+5 );
            StringType.INSTANCE.set( st, null, index+6 );
        }
        else {
            final Address address = (Address) value;
            StringType.INSTANCE.set( st, address.getCity(), index,session );
            StringType.INSTANCE.set( st, address.getCountry(), index+1,session);
            StringType.INSTANCE.set( st, address.getState(), index+2,session);
            StringType.INSTANCE.set( st, address.getStreet1(), index+3,session);
            StringType.INSTANCE.set( st, address.getStreet2(), index+4,session);
            StringType.INSTANCE.set( st, address.getStreet3(), index+5,session);
            StringType.INSTANCE.set( st, address.getZip(), index+6,session);
        }
    }
 
 
    @Override
    public boolean isMutable() {
        return false;
    }
 
    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        // for now
        return x.equals(y);
    }
 
    @Override
    public int hashCode(Object x) throws HibernateException {
        assert (x != null);
        return x.hashCode();
    }
 
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }
 
    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return original;
    }
 
    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }
 
    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return cached;
    }
 
    public Class returnedClass() {
        return Address.class;
    }
}

А вот мой торговец, у которого есть адрес.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Merchant {
    UUID id;
 
    String color;
    String displayName;
    //...
    //...
 
    Address address
     
    static mapping = {
        address type: AddressUserType, {
            column name: "city"
            column name: "country"
            column name: "zip"
            column name: "state"
            column name: "street1"
            column name: "street2"
            column name: "street3"
        }
    }
 
}

Как указано, при таком подходе структура данных Address может использоваться в других объектах GORM. До следующего раза береги себя.

Ссылка: Пользовательский пользователь вводит GORM от нашего партнера JCG Алекса Стейвли в блоге Techlin в Дублине .