статический доступ к диспетчеру сущностей весной и необычная архитектура - PullRequest
3 голосов
/ 10 августа 2011

быстрый вопрос:

У меня есть веб-приложение (wicket + spring + jpa), и я думал о довольно необычном архитектурном дизайне.Пожалуйста, проверьте его и оставьте свои комментарии.

Рассмотрим класс Wrapper:

@Service
public class Wrapper {
    protected static EntityManager entityManager;

    @PersistenceContext
    private void injectEntityManager(EntityManager entityManager) {
        Wrapper.entityManager = entityManager;
    }

, как вы видите, теперь EntityManager вводится статически.

Теперь рассмотрим простую сущность DogEntity

@Entity
public class DogEntity {
   String name;
}

И для этой сущности мы создаем оболочку Dog

public class Dog extends Wrapper {
  private DogEntity entity;

  private Dog(DogEntity entity) {
     this.entity = entity;
  }

  public static Dog create(String name) {
    entity = new DogEntity();
    entity.name = name;
    entityManager.persist(entity); // for a moment forget that this code is not in transaction
    return new Dog(entity);
  }
}

Теперь в моем веб-приложении (в моем контроллере) я могу сделать что-то вроде этого:

saveButton = new Button("save") {

public void onSubmit() {
   Dog dog = Dog.create(name);
   // other code 
}

С точки зрения кода эта архитектура выглядит идеально.У нас есть обертки, представляющие бизнес-объекты.Все они имеют постоянное состояние, в приложении нет глупых сервисов, называемых DogSaver с методом save (DogEntity), которые вызывают только персистентность в диспетчере сущностей.Код действительно получает большую читабельность и некоторые другие преимущества, но я не буду вдаваться в подробности.

Что меня действительно беспокоит, так это статический EntityManager.У меня недостаточно знаний о внутренностях Spring, чтобы знать, является ли этот подход правильным и безопасным.Существуют ли ситуации, когда вещи становятся ужасными?Я знаю, что EntityManare не имеет состояния (в соответствии со спецификацией JPA), он всегда берет постоянный контекст из транзакции, поэтому его статичность не кажется плохой идеей.Но я боюсь, что могу что-то испортить.

Есть мысли?

Ответы [ 2 ]

5 голосов
/ 10 августа 2011

Тебе стоит взглянуть на Spring Roo.У них есть что-то симулированное (без DAO или сервисов), но там EntityManager не статичен.

Они добиваются цели с помощью аннотации @Configurable в сущностях:

@Entiy
@Configurable
class MyEntity() {

  @PersistenceContext
  transient EntityManager Car.entityManager;

  ...

  public static MyEntity findMyEntityById(Long id) {
    if (id == null) return null;
    return entityManager().find(MyEntity.class, id);
  }

  public static EntityManager entityManager() {
    EntityManager em = new MyEntity().entityManager;
    if (em == null) throw new IllegalStateException("Entity manager has not been injected (is the Spring Aspects JAR configured as an AJC/AJDT aspects library?)");
    return em;
  }    
}

В любом случае у него есть дваили три недостатка:

  • вам нужен AspectJ
  • эта строка EntityManager em = new MyEntity().entityManager; очень уродлива
  • тестирование становится немного сложным, если вы хотите издеваться над постоянным "слоем».Но, к счастью, Spring предоставляет специальный перехватчик AOP (@see JavaDoc org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl )

Но он также имеет несколько приятных эффектов: напримерМетоды persist и delete становятся очень естественными, они просто являются членами сущности:

@Transactional
public void persist() {
    if (this.entityManager == null) this.entityManager = entityManager();
    this.entityManager.persist(this);
}

Чтобы сделать проект none Roo доступным для @Configurable, вам нужно по крайней мере сделать следующее:

расширение файла pom.xml:

<properties>
    <spring.version>3.0.5.RELEASE</spring.version>
    <aspectj.version>1.6.11</aspectj.version>
    <aspectj-maven-plugin.version>1.2</aspectj-maven-plugin.version>
    <maven-compiler-plugin.version>2.3.2</maven-compiler-plugin.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

    ...

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>${spring.version}</version>
    </dependency>

...

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>${aspectj-maven-plugin.version}</version>
            <!-- NB: do use 1.3 or 1.3.x due to MASPECTJ-90 - wait for 1.4 -->
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjrt</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjtools</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
    <!--
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-aspects</artifactId>
        <version>3.0.5.RELEASE</version>
    </dependency>
    -->
            </dependencies>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <outxml>true</outxml>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>org.springframework</groupId>
                        <artifactId>spring-aspects</artifactId>
                    </aspectLibrary>
                    <!--
                    <aspectLibrary>
                        <groupId>org.springframework.security</groupId>
                        <artifactId>spring-security-aspects</artifactId>
                    </aspectLibrary>
                    -->
                </aspectLibraries>
                <source>1.6</source>
                <target>1.6</target>
                <encoding>utf-8</encoding>
            </configuration>
        </plugin>

Настройка пружины:

<!-- Turn on AspectJ @Configurable support. As a result, any time you instantiate an object,
Spring will attempt to perform dependency injection on that object.
This occurs for instantiation via the "new" keyword, as well as via reflection.
This is possible because AspectJ is used to "weave" Roo-based applications at compile time.
 In effect this feature allows dependency injection of any object at all in your system,
 which is a very useful feature (without @Configurable you'd only be able to
 dependency inject objects acquired from Spring or subsequently presented to
 a specific Spring dependency injection method). -->
 <context:spring-configured />

 <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />

  <!--
  Spring Security:
  requires version 3.0.4 of Spring Security XSD: spring-security-3.0.4.xsd
  <global-method-security ... mode="aspectj"> 
  -->
1 голос
/ 10 августа 2011

EntityManager в состоянии (в некотором смысле).Фабрика не имеет гражданства.И наличие статического диспетчера сущностей является анти-шаблоном, называемым «сеанс на приложение» (или диспетчер сущностей на приложение).

Обратите внимание, что это детали реализации - как обрабатывается @PersistenceContext EntityManager em.Hibernate предоставляет EntityManagerImpl, который создает новый сеанс гибернации, если он еще не создан.Но если он уже создан, он содержит ссылку на него (контекст сеанса = постоянство).

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

То, что вы пытаетесь сделать, - это управляемый доменом дизайн. Вот моя статья о DDD и JPA.

Мое личное предпочтение - не идти по этому пути - объект не должен быть в состоянии сохраняться в базе данных - это логика инфраструктуры,Чтобы избежать дублирования кода, вы можете просто использовать BaseDao, который заключает в себе метод persist.Или даже напрямую используйте EntityManager на вашем сервисном уровне.(Это предполагает, что у вас есть четкие границы слоев)

Если вы действительно уверены, что хотите пойти по пути DDD, взгляните на примеры в статье.Spring позволяет вам внедрять менеджер сущностей в любой объект через аспектJ.Таким образом, ваш менеджер сущностей будет правильно обрабатываться, как и ваши транзакции.

...