Статьи

Начало работы с Dropwizard: подключение к базе данных с помощью Hibernate

В предыдущих статьях этой серии мы обсуждали, как создать приложение Dropwizard с использованием архетипа Maven и как использовать файл YAML для установки параметров конфигурации для приложения. В этом руководстве мы создадим простое приложение, которое предоставляет каталог сотрудников через REST API. По причинам простоты наши представления не будут содержать гиперссылок, то есть мы поговорим о 2-м уровне модели зрелости Ричардсона . Источники для учебника можно найти здесь .

Начнем с добавления параметров подключения к базе данных, включая учетные данные, URI подключения и имя драйвера базы данных, в файл конфигурации приложения. Фрагмент ниже показывает, как настроить соединение с базой данных.

# Database settings.
database:
    # the name of the JDBC driver, mysql in our case
    driverClass: com.mysql.jdbc.Driver
    # the username
    user: javaeeeee
    # the password
    password: 1
    # the JDBC URL; the database is called DWGettingStarted
    url: jdbc:mysql://localhost:3306/DWGettingStarted

В нашем примере использовалась СУБД MySQL, но можно использовать и некоторые другие СУБД. Поскольку мы будем использовать Hibernate для общения с нашей базой данных, переключение баз данных совсем несложно, нужно только изменить имя драйвера в приведенном выше фрагменте. Преимущество использования Hibernate заключается в том, что он знает, как работать с различными системами управления базами данных, и нет необходимости изменять код приложения после переключения на другую RDBMS.

Нашим следующим шагом будет изменение файла приложения pom.xml . Dropwizard имеет модули JDBI и Hibernate, которые можно использовать для подключения к базе данных, и оба включают модуль базы данных, который также является частью Dropwizard, поэтому, добавив модуль Hibernate, мы осуществляем большую часть изменений, необходимых для использования базы данных в нашем приложении. Кроме того, мы должны добавить зависимость от драйвера базы данных, MySQL в нашем случае. Поэтому, если вы предпочитаете какую-либо другую СУБД, не забудьте заменить зависимость от драйвера. Фрагмент кода со всеми необходимыми зависимостями показан ниже.

        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-hibernate</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
        </dependency>

Затем, после добавления необходимых зависимостей, мы можем изменить класс Configuration, названный DWGettingStartedConfiguration, в примере приложения. Наборы связанных параметров группируются в так называемые фабрики. Мы должны создать новое поле типа DataSourceFactory и добавить для него метод получения, и Dropwizard десериализует все параметры подключения к базе данных из файла config.yml до фабрики. Вот код, который нужно добавить в класс Configuration .

public class DWGettingStartedConfiguration extends Configuration {

  ...

    /**
     * A factory used to connect to a relational database management system.
     * Factories are used by Dropwizard to group together related configuration
     * parameters such as database connection driver, URI, password etc.
     */
    @NotNull
    @Valid
    private DataSourceFactory dataSourceFactory
            = new DataSourceFactory();

    ...

    /**
     * A getter for the database factory.
     *
     * @return An instance of database factory deserialized from the
     * configuration file passed as a command-line argument to the application.
     */
    @JsonProperty("database")
    public DataSourceFactory getDataSourceFactory() {
        return dataSourceFactory;
    }

}

Если вы новичок в Hibernate , на данный момент достаточно знать, что Hibernate может отображать классы Java в таблицы базы данных, то есть для нашего приложения нам понадобится класс Employee со всеми полями, описывающий таблицу employee и employee с столбцы для хранения тех же данных, и Hibernate знает, как передавать данные из полей в столбцы и наоборот. Хватит говорить, давайте создадим таблицу и класс. Сценарий SQL для создания таблицы показан ниже.

-- A script to create employee table
create table employees(
    -- auto-generated primary key
    id bigint primary key not null auto_increment,
    first_name varchar(255) not null,
    last_name varchar(255) not null,
    -- employee position
    e_position  varchar(255) not null,
    phone  varchar(255) not null,
    e_mail varchar(255) not null
);

Теперь давайте обратимся к классу.

@Entity
@Table(name = "employees")
@NamedQueries({
    @NamedQuery(name = "com.javaeeeee.dwstart.core.Employee.findAll",
            query = "select e from Employee e"),
    @NamedQuery(name = "com.javaeeeee.dwstart.core.Employee.findByName",
            query = "select e from Employee e "
            + "where e.firstName like :name "
            + "or e.lastName like :name")
})
public class Employee {

    /**
     * Entity's unique identifier.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    /**
     * employee first name.
     */
    @Column(name = "first_name")
    private String firstName;
    /**
     * employee last name.
     */
    @Column(name = "last_name")
    private String lastName;
    /**
     * employee position.
     */
    @Column(name = "e_position")
    private String position;
    /**
     * employee phone.
     */
    private String phone;
    /**
     * employee e-mail.
     */
    private String e_mail;

    /**
     * A no-argument constructor.
     */
    public Employee() {
    }

    /**
     * A constructor to create employees. Id is not passed, cause it's
     * auto-generated by RDBMS.
     *
     */
    public Employee(String firstName, String lastName, String position, String phone, String e_mail) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.position = position;
        this.phone = phone;
        this.e_mail = e_mail;
    }

    // Auto-generated equald, hashCode, getters and setters.
    ...
  }

Это простой старый Java-объект в дополнение к полям, содержащим пару конструкторов, требуются аргументы без аргументов, методы equals () и hashCode (), унаследованные от Object, а также методы получения и установки для всех полей, которые могут быть автоматически сгенерированы IDE и здесь не показаны Также есть несколько аннотаций. Во-первых, @Entity показывает, что этот POJO сопоставлен с таблицей. Второй, @Table , используется для предоставления имени таблицы, в которую отображается POJO, и требуется, только если имена класса и таблицы отличаются, как в нашем случае: Employee / employee . Третий, @Id, используется для пометки поля, которое однозначно идентифицирует экземпляр класса и в нашем случае будет хранить автоматически сгенерированный первичный ключ СУБД. И, наконец, аннотация @GeneratedValue показывает, что мы используем автоинкремент в качестве стратегии генерации, и инструктирует Hibernate считать значение сгенерированного ключа из базы данных в поле id после сохранения новой сущности. Кроме того, есть @Column, который используется, только если имя поля и имя связанного столбца отличаются.

Кроме того, нам нужно украсить нашу сущность еще одной аннотацией — @NamedQuery , которая содержит разделенный запятыми список именованных запросов, каждый из которых используется для извлечения данных из базы данных. Для создания запросов используется специальный язык запросов персистентности Java ( JPQL ), который напоминает SQL, но работает не с таблицами и строками, а с классами и сущностями. Пара запросов для извлечения полного списка сотрудников и поиска сотрудников по имени показана выше.

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

Чтобы скрыть от разработчиков большинство тонкостей использования Hibernate, Dropwizard предлагает нам класс AbsractDAO . Мы должны создать классы DAO, которые реализуют всю логику CRUD, расширяя этот класс. Пример показан здесь.

public class EmployeeDAO extends AbstractDAO<Employee> {

    /**
     * Constructor.
     *
     * @param sessionFactory Hibernate session factory.
     */
    public EmployeeDAO(SessionFactory sessionFactory) {
        super(sessionFactory);
    }

    /**
     * Method returns all employees stored in the database.
     *
     * @return list of all employees stored in the database
     */
    public List<Employee> findAll() {
        return list(namedQuery("com.javaeeeee.dwstart.core.Employee.findAll"));
    }

    /**
     * Looks for employees whose first or last name contains the passed
     * parameter as a substring.
     *
     * @param name query parameter
     * @return list of employees whose first or last name contains the passed
     * parameter as a substring.
     */
    public List<Employee> findByName(String name) {
        StringBuilder builder = new StringBuilder("%");
        builder.append(name).append("%");
        return list(
                namedQuery("com.javaeeeee.dwstart.core.Employee.findByName")
                .setParameter("name", builder.toString())
        );
    }

    /**
     * Method looks for an employee by her id.
     *
     * @param id the id of an employee we are looking for.
     * @return Optional containing the found employee or an empty Optional
     * otherwise.
     */
    public Optional<Employee> findById(long id) {
        return Optional.fromNullable(get(id));
    }
}

Мы полагались на унаследованный метод namedQuery () , который возвращает объекты класса, реализующего интерфейс Query, который, в свою очередь, может использоваться для передачи параметров в запросы. Также мы использовали унаследованный метод list () , который знает, как извлекать списки из базы данных, используя JPQL-запросы, и унаследованный метод get (), который используется для извлечения сущностей по id .

Следующим этапом нашего пути будет создание класса ресурсов, который опирается на недавно созданный DAO и возвращает JSON-представления отдельных сотрудников и списки сотрудников. Код показан ниже. Большинство частей должны быть вам знакомы, если вы читали две предыдущие статьи этой серии. Разница в том, что наш класс ресурсов содержит DAO в качестве поля и конструктор для его инициализации. Поразительным отличием является аннотация @UnitOfWork, которая украшает методы класса. Он является частью Dropwizard и используется, чтобы избавить разработчиков от написания большого количества стандартного кода, связанного с Hibernate.

@Path("/employees")
@Produces(MediaType.APPLICATION_JSON)
public class EmployeesResource {

    /**
     * The DAO object to manipulate employees.
     */
    private EmployeeDAO employeeDAO;

    /**
     * Constructor.
     *
     * @param employeeDAO DAO object to manipulate employees.
     */
    public EmployeesResource(EmployeeDAO employeeDAO) {
        this.employeeDAO = employeeDAO;
    }

    /**
     * Looks for employees whose first or last name contains the passed
     * parameter as a substring. If name argument was not passed, returns all
     * employees stored in the database.
     *
     * @param name query parameter
     * @return list of employees whose first or last name contains the passed
     * parameter as a substring or list of all employees stored in the database.
     */
    @GET
    @UnitOfWork
    public List<Employee> findByName(
            @QueryParam("name") Optional<String> name
    ) {
        if (name.isPresent()) {
            return employeeDAO.findByName(name.get());
        } else {
            return employeeDAO.findAll();
        }
    }

    /**
     * Method looks for an employee by her id.
     *
     * @param id the id of an employee we are looking for.
     * @return Optional containing the found employee or an empty Optional
     * otherwise.
     */
    @GET
    @Path("/{id}")
    @UnitOfWork
    public Optional<Employee> findById(@PathParam("id") LongParam id) {
        return employeeDAO.findById(id.get());
    }
}

Также следует упомянуть класс Dropwizard LongParam, который полезен для проверки того, что аргумент правильного типа был передан от клиента. Если клиент попытается предоставить строку вместо long, ответ 400 Bad Request будет возвращен. Обратите внимание, что Dropwizard знает, как обращаться с дополнительными компонентами, и код ответа HTTP 404 Not Found будет возвращен клиенту, если метод в классе ресурсов возвратит пустой Optional.

И теперь мы должны создать так называемый Hibernate-комплект — новое поле в классе Application , которое называется DWGettingStartedApplication в примере приложения . Обратите внимание, что разделенный запятыми список всех классов сущностей должен быть передан в комплект Hibernate.

public class DWGettingStartedApplication
        extends Application<DWGettingStartedConfiguration> {

    /**
     * Hibernate bundle.
     */
    private final HibernateBundle<DWGettingStartedConfiguration> hibernateBundle
            = new HibernateBundle<DWGettingStartedConfiguration>(
                    Employee.class
            ) {

                @Override
                public DataSourceFactory getDataSourceFactory(
                        DWGettingStartedConfiguration configuration
                ) {
                    return configuration.getDataSourceFactory();
                }

            };

    ...

    @Override
    public void initialize(
            final Bootstrap<DWGettingStartedConfiguration> bootstrap) {
        bootstrap.addBundle(hibernateBundle);
    }

    ...

}

Мы должны добавить пакет в тело метода initialize () приложения, и после этого он может быть использован позже для создания экземпляров объектов Hibernate ConnectionFactory . Мы не будем вдаваться в подробности того, как работает вышеупомянутая фабрика, из-за того, что Dropwizard использует ее за кулисами для выполнения операции CRUD, и нет необходимости копать глубже, чтобы начать использовать Hibernate с Dropwizard.

Наконец, мы должны зарегистрировать наш класс ресурсов в методе run () класса Application, но перед этим мы должны создать экземпляр DAO и передать ему экземпляр фабрики сеансов Hibernate, полученный из пакета Hibernate. Фрагмент кода ниже показывает, как это можно сделать.

public class DWGettingStartedApplication
        extends Application<DWGettingStartedConfiguration> {

    ...

    @Override
    public void run(final DWGettingStartedConfiguration configuration,
            final Environment environment) {
        final EmployeeDAO employeeDAO
                = new EmployeeDAO(hibernateBundle.getSessionFactory());

        ...

        environment.jersey().register(new EmployeesResource(employeeDAO));
    }

}

Мы создали простой класс ресурсов, который может извлекать данные из РСУБД, используя Hibernate и полезные классы, предлагаемые Dropwizard, и возвращать представления JSON клиенту. Если вы следили за серией, вы сможете протестировать код, используя cURL или дополнение для браузера. Можно заметить, что модуль Dropwizard Hibernate делает чрезвычайно простым создание REST-подобного API, который использует базу данных для хранения своих данных.

Ресурсы

  1. Dropwizard Hibernate

  2. Hibernate Документация

  3. Идеи для приложения Справочник сотрудников