Spring и Hibernate ma sh -up, объект, который является прокси @Entity с добавленной дополнительной @Service - PullRequest
0 голосов
/ 25 марта 2020

Я думаю, что это может быть тот случай, когда я знаю ответ, но мне просто не нравится.

Моя отправная точка была более ранним вопросом, Hibernate @ Entity конфликт со Spring @Autowired для не -column object .

У меня есть @Entity, который «естественно» связан в отношении «один ко многим» с другим набором сущностей. В моем примере я называю это ItemEntity, и у него (очень) большая история цен. Настолько большой, что ленивая нагрузка Hibernate снижает производительность, потому что реальные случаи использования никогда не требуют всей истории (сотни тысяч цен против нескольких сотен, которые обычно необходимы). Таким образом, у меня есть служба PriceCache, которая получает то, что мне нужно по требованию.

«Естественная» вещь из обычного варианта использования - это получение интересующего элемента ItemEntity, а затем запросить связанную историю цен в течение некоторого времени. временной диапазон. Как указывалось в вышеприведенном посте, использование этого сервиса внутри ItemEntity не является нормальным, хотя это можно сделать и заставить работать.

В приведенном ниже примере кода я написал это по-другому, имеющий интерфейс Item с реализацией, которая фактически является прокси для ItemEntity плюс служба PriceCache. Это пример кода, и биты отсутствуют; Я думаю (надеюсь), что достаточно подарка, чтобы быть ясным.

Мой набор сущностей и их свойства не настолько велики, что я не мог бы сделать это вручную для всех них; пара дюжин сущностей, каждая с 5-20 свойствами. Это было бы умеренно больно и скучно, но это должно сработать.

Но ... есть ли более простой способ создания прокси-объекта с добавленной дополнительной службой? Или, может быть, вопрос в том, есть ли более ленивый способ сделать это?

@Entity @Table(name="item")
public class ItemEntity {
    @Id @Column(name="id")
    private long id;
    @Column(name="name")
    private String name;
    /* ... setters, getters ... */
}

@Service
public class ItemCache {
    @Autowired
    private ItemDAO itemDAO;
    @Autowired
    private PriceCache priceCache;
    private Map<Long,Item> itemCache;
    public ItemCache() {
        itemCache = new HashMap<>();
    }
    public Item get(long id) {
        if (itemCache.containsKey(id))
            return itemCache.get(id);
        ItemEntity itemEntity = itemDAO.find(id);
        Item item = (itemEntity == null) ? null : new ItemImpl(itemEntity, priceCache);
        itemCache.put(id, item); // caches nulls to avoid retry
        return item;
    }
}

@Service
public class PriceCache {
    @Autowired
    private PriceDAO priceDAO;
    /* ... various cache/map structures to hold previous query results ... */
    public PriceCache() {
        /* ... initialize all those cache/map structures ... */
    }
    public Collection<Price> getPrices(long id, LocalDateTime begTime, LocalDateTime endTime) {
        Collection<Price> results;
        /* ... check the caches to see if we already have the data ... */
        /* ... otherwise, use priceDAO to find it and save the results in the cache ... */
        return results;
    }
}

public interface Item {
    public long getId();
    public String getName();
    public Collection<Price> getPrices(LocalDateTime begTime, LocalDateTime endTime);
}

public class ItemImpl implements Item {
    public ItemImpl(ItemEntity underlying, PriceCache priceCache) { ... }
    public long getId() {
        return underlying.getId();
    }
    public String getName() {
        return underlying.getName();
    }
    public Collection<Price> getPrices(LocalDateTime begTime, LocalDateTime endTime) {
        priceCache.get(getId(), begTime, endTime);
    }
}

1 Ответ

0 голосов
/ 27 марта 2020

Итак ... я предполагаю, что все вежливы и не хотят соглашаться с тем, что я ищу ленивый выход: -)

Я не сделал этого для Приведенный выше пример, но у меня есть другой похожий случай, когда я хочу, по сути, bean-компонент с некоторыми добавленными сервисами. Вместо того, чтобы писать прокси и тому подобное, я сделал объект, который предоставляет сервисы, производным объектом. Это не @Entity, а @Component, который создается исключительно из applicationContext. xml description.

Итак, есть четыре части; bean-компонент, который описывает объект, «настоящий» бизнес-объект, который использует / расширяет описания и предоставляет сервис, который вводится, и сервис кэширования, который находит и создает эти бизнес-объекты из описаний.

Еще или меньше как ниже. Это урезано и написано на лету, поэтому может иметь опечатки; реальный код и идея работают.

@Component
public class ThingDescr {
    /* ... various attributes, setters, getters, just a bean ... */
}

public class Thing extends ThingDescr implements HelperService {
    public Thing(ThingDescr td, HelperService svc) {
        /* ... basically a copy constructor ... */
    }
    @Override
    public void doSomething() {
        /* ... whatever HelperService is supposed to do ... */
    }
}

public interface HelperService {
    public void doSomething();
}

@Service
public class ThingCache {
    @Autowired
    private HelperService svc;
    @Autowired
    private List<? extends ThingDescr> thingList;
    private Map<String,Thing> thingMap;
    private void load() {
        thingMap = new HashMap<>();
        for (ThingDescr td : thingList) {
            Thing thing = new Thing(td, svc);
            thingMap.put(thing.getName(), thing);
        }
    public getThing(String name) {
        if (thingMap == null || thingMap.isEmpty())
            load();
        return thingMap.get(name);
    }
}

Основным преимуществом этого шаблона является то, что бизнес-объект "Вещь" наследует все свойства базового объекта без необходимости их записи. Я этого не делал, но я думаю, что если свойства должны быть доступны только для чтения, сеттеры в «ThingDescr» могут быть защищены, чтобы «Вещь» могла получить к ним доступ, но пользователи класса не могут.

...