Сохранение объектов со ссылками на другие объекты - PullRequest
0 голосов
/ 25 ноября 2010


Я работаю над довольно простым приложением CRUD с веб-интерфейсом. Я использую общий шаблон DAO в слое постоянства. Интерфейс моего универсального DAO выглядит так:

public interface GenericDAO<T> {

    void create( T entity ) throws CannotPersistException;
    T findById( Long id ) throws NotFoundException;
    Collection<T> findAll();
    void update( T entity ) throws CannotPersistException, NotFoundException;
    void delete( Long id ) throws CannotPersistException, NotFoundException;
}

Этот интерфейс реализован с использованием JPA, но у меня также есть реализация в памяти, главным образом для целей тестирования. У меня нет никаких других классов в слое постоянства , я просто создаю один экземпляр этого DAO для каждого класса домена.

Проблема, с которой я сталкиваюсь, заключается в том, как правильно сохранить объект, имеющий ссылки на другие объекты, которые также необходимо сохранить. Я знаю, что каскадирование в JPA решило бы эту проблему, не требуя от меня добавления новых классов в свой уровень персистентности. Однако это сделало бы меня зависимым от конкретной реализации (и также нарушило бы мою реализацию DAO в памяти) ,

Правильно, я использую сервисный уровень для управления классами DAO и решаю проблему, работая с несколькими DAO . 1014 * Я покажу пример:

public class PlayerServiceImpl implements PlayerService {

    private GenericDAO<Player> playerDAO;
    private GenericDAO<Club> clubDAO;

    //constructor ommited

    public Player addNewPlayer( Player player, Long clubId ) throws NotFoundException,
            CannotPersistException {
        Club club = clubDAO.findById( clubId );
        player.setClub( club );
        playerDAO.create( player );
        club.addPlayer( player );
        clubDAO.update( club );
        return player;
    }

    //... other methods

}

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

Я обнаружил другой недостаток при тестировании приложения - Я не могу по-настоящему протестировать класс GenericDao в отдельности , если я хочу использовать объекты Club, в которых поля их коллекции заполнены объектами Player.
Сначала мне нужно сохранить объект Player, чтобы добавить его к объекту Club, чтобы передать его тестируемому GenericDao.

Я искал книгу Фаулера по PoEAA, чтобы найти решение, но я немного запутался, когда O / R-фреймворк, такой как Hibernate, вписывается в шаблоны, которые он описывает.

Ответы [ 4 ]

2 голосов
/ 25 ноября 2010

Использовать источник данных в памяти

Похоже, что вам лучше использовать источник данных в памяти, чем изобретать обходной путь для каскада JPA. Я бы посоветовал вам взглянуть на настройку тестовой среды, чтобы позволить вам выбрать другой источник данных, предназначенный для экземпляра HSQLDB в памяти. Затем вы можете аннотировать ваши доменные объекты для каскадирования и проверки на содержание вашего сердца. Вот контекст Spring, который демонстрирует простую конфигурацию:

  <bean id="testDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="org.hsqldb.jdbcDriver"/>
    <property name="jdbcUrl" value="jdbc:hsqldb:mem:test"/>
    <property name="user" value="sa"/>
    <property name="password" value=""/>
  </bean>

Ваш тестовый код может иметь Фабрику, которая создает определенные сценарии, которые вы хотите протестировать (используя коллекции временных объектов домена, пока они не достигнут DAO, который заполнит их поля первичного ключа). Поскольку вы можете добавить много потенциальных дубликатов в свои тестовые наборы данных, вам следует рассмотреть возможность добавления поля UUID в ваши доменные объекты, чтобы их можно было дифференцировать, когда первичный ключ равен нулю. Обычно это просто загруженный метод getUUID (), который сохраняется вместе с другими полями. Вы могли бы даже рассмотреть возможность использования его в качестве первичного ключа, так как шансы на столкновение по определению бесконечно малы.

Кроме того, я бы подошел к тестированию, имея как соединение JDBC, так и соединение DAO JPA. Вы просматриваете состояние базы данных для подтверждения через соединение JDBC и обновляете через соединение DAO.

Объекты анемичного домена не являются антишаблоном

В зависимости от вашей стратегии моделирования предметной области, в анемичных доменных объектах нет ничего особенно плохого. Например, у вас может быть куча детализированных бизнес-объектов, которые выполняют операции изменения состояния в доменных объектах в соответствии с бизнес-правилами. По сути, у вас был бы подход в стиле Unit of Work / Command / DTO с вашим сервисным уровнем, обеспечивающим транзакцию приложения.

1 голос
/ 25 ноября 2010

Проблема, с которой я сталкиваюсь, заключается в том, как правильно сохранить объект, имеющий ссылки на другие объекты, которые также необходимо сохранить.Я знаю, что каскадирование в JPA решило бы эту проблему, не требуя от меня добавления новых классов в мой слой постоянства. Однако это сделало бы меня зависимой от конкретной реализации (и также нарушило бы мою реализацию DAO в памяти).

Вы понимаете, что JPA - это стандарт Java для персистентностии это не фактическая реализация.Чтобы использовать JPA, вам все еще нужен PersistenceProvider (реализация), но вы можете выбирать между несколькими реализациями (Toplink, Hibernate и т. Д.).

Я думаю, вам следует использовать JPA Cascade здесь.

1 голос
/ 25 ноября 2010

в C # Я, вероятно, рассмотрю возможность создания атрибута, который сообщает дао, следует ли рассматривать свойство как часть родителя («потомок»).Я знаю, что в Java есть нечто подобное, но я не могу вспомнить, как это называется.

Извините, это C #:

class Club
{
  [ChildEntity]
  List<Player> Players { get; private set; }
}

Нужно подумать, чтобы найти всех детей и их детей.Если вам это не нравится, вы можете написать интерфейс, который возвращает дочерние элементы:

class Club : IComplexEntity
{
  List<Player> Players { get; private set; }

  public IEnumerable<object> EntityChildren { get { return Players; } }
}
0 голосов
/ 29 ноября 2010

У вас есть универсальный тип (DOA), но поведение, которое вам нужно реализовать, не является универсальным.Это все равно что пытаться поставить квадратный колышек в круглое отверстие: это никогда не сработает.

Некоторые возможные подходы:

Будьте конкретны : если это был я, яиспользовал бы типы, которые определяют конкретные вещи: Club будет иметь (просто думая о возможных примерах), address, DJ будет иметь Genre / Style.

Да, я бы закончилс множеством мягких классов, но у каждого класса будет одна работа, и она будет делать это хорошо.Поскольку они были отделены друг от друга, они были бы изолированы от изменений - что является принципиальной проблемой, с которой вы сейчас столкнулись.

Используйте наследование : если у всех ваших классов есть эти общие черты, то вы можете использоватьчто в качестве основы, а остальное обрабатывать отдельно?

Использовать несколько интерфейсов : немного похоже на наследование, но другое.Вы сохраняете существующий интерфейс, который обрабатывает основы;Затем вы определяете дополнительные интерфейсы для дополнительного поведения.Когда приходит объект, которому нужно выполнить какое-то действие, он оценивает, какие интерфейсы он реализует, и обрабатывает их по очереди.Вы также сохраняете возможность изменить существующий интерфейс для лучшей поддержки этого подхода - если это имеет смысл.

Возможно, вы можете смешивать и сочетать некоторые из этих параметров или их части.

...