В следующем примере описывается та же самая ситуация, но класс Main, использованный в главе 4, заменен сессионным компонентом, не сохраняющим состояние (BookEJB). EJB является транзакционным по своей природе (что вы увидите в следующей главе), так что наш сессионный компонент будет обрабатывать CRUD-операции для сущности Book с помощью транзакций, управляемых контейнером (CMT). Я также добавлю синглтон (DatabasePopulator), который будет заполнять при запуске базу данных, так что в ней всегда будут данные, а также CDI-производитель, способный внедрить устойчивый объект (с помощью аннотации @Inject). BookEJB, сущность Book, синглтон DatabasePopulator, CDI-производитель и все необходимые файлы конфигурации XML затем будут упакованы и развернуты в GlassFish. BookEJB будет вызываться удаленно классом Main (рис. 8.4), а также тестироваться встроенным контейнером (BookEJBIT).
Рис. 8.4. Объединяем все вместе
Для того чтобы использовать транзакции, сессионный компонент должен получить доступ к базе данных через источник данных (jdbc/chapter08DS), который должен быть развернут в GlassFish и связан с базой данных chapter08DB (это делается с помощью аннотации @DataSourceDefinition, что вы увидите позже).
Структура каталога для проекта следует соглашениям Maven, так что вы должны поместить классы и файлы в следующие каталоги:
• src/main/java — для сущности Book, BookEJB, интерфейса BookEJBRemote, а также классов DatabasePopulator и DatabaseProducer;
• src/main/resources — для файла persistence.xml, содержащего устойчивый объект, используемый для базы данных Derby, а также для файла beans.xml, который заставляет срабатывать CDI;
• src/test/java — для класса BookEJBIT, предназначенного для тестирования;
• pom.xml — объектная модель Maven Project Object Model (РОМ), описывающая проект, ее зависимости от других внешних модулей и компоненты.
Написание сущности Entity
Листинг 8.10 ссылается на сущность Book, описанную в главе 4 (см. листинг 4.3), поэтому я не буду объяснять ее подробно (она использует аннотации JPA и Bean Validation). Одно отличие будет заключаться в том, что сущность Book в листинге 8.10 реализует интерфейс Serializable, поскольку она должна быть доступна удаленно из класса Main.
@Entity
@NamedQuery(name = FIND_ALL, query = "SELECT b FROM Book b")
public class Book implements Serializable {
··public static final String FIND_ALL = "Book.findAllBooks";
··@Id @GeneratedValue
··private Long id;
··@NotNull
··@Column(nullable = false)
··private String title;
··private Float price;
··@Size(max = 2000)
··@Column(length = 2000)
··private String description;
··private String isbn;
··private Integer nbOfPage;
··privateBooleanillustrations;
··// Конструкторы и методы доступа
}
Написание сеансового компонента BookEJB, не сохраняющего состояние
BookEJB является сессионным компонентом, не сохраняющим состояние, который действует как фасад и обрабатывает операции CRUD в сущности Book. В листинге 8.11 показан класс Java, который должен быть аннотирован @javax.ejb.Stateless. Он не предоставляет интерфейса для просмотра благодаря аннотации @LocalBean и реализует интерфейс BookEJBRemote (см. листинг 8.12). EJB получает ссылку на менеджер сущностей с использованием аннотации @Inject, поскольку объект класса EntityManager создается объектом класса DatabaseProducer (листинг 8.13). Приведу небольшие разъяснения по каждому бизнес-методу:
• findBooks — использует именованный запрос findAllBooks, определенный в сущности Book, чтобы получить все экземпляры книг из базы данных;
• createBook — принимает объект типа Book (которая не может иметь значение null) в качестве параметра, сохраняет его в базе данных и затем возвращает его;
• updateBook — принимает отдельные объекты типа Book (которые не могут иметь значение null) в качестве параметра и объединяет их. С помощью метода merge() объект прикрепляется к менеджеру сущностей и синхронизируется с базой данных;
• deleteBook — перед удалением объекта типа Book из базы данных этот метод должен повторно прикрепить объект к менеджеру сущностей (с помощью метода merge()), а затем удалить его.