Интеграция Groovy в JEE приложение

в 10:05, , рубрики: groovy, hibernate, java, java ee, maven, spring, метки: , , , , ,
В одну телегу впрячь не можно
Коня и трепетную лань
                        А.С. Пушкин

Всем привет!

В данной заметке я хочу рассказать, как интегрировать язык Groovy в существующее JEE приложение на основе Maven. Выражаю благодарность Антону Щастному schaan за разрешение на использование исходного кода его проекта в качестве отправной точки. Поэтому данный топик можно считать продолжением его статьи Учимся готовить: Spring 3 MVC + Spring Security + Hibernate.

Начнем.

Подготовка проекта.

Я использую Eclipse в сборке SpringSource Tool Suite. Выкачиваем проект ContactManager. Но не торопитесь его открывать в IDE. Будет даже надежнее, если вы вообще удалите все файлы проекта: .classpath .project и каталог .settings. Ибо с момента публикации статьи Антона технологии шагнули вперед, вышли новые версии STS (с другой структурой проекта и новой версией плагина m2e), поэтому мы сначала исправим pom-файл, затем на его основе STS нам создаст новый проект.

Для простоты я удалил из pom.xml зависимости от аспектов и Spring Roo. Также заменил MySQL на более привычный мне PostgreSQL (см. файл jdbc.properties). Но это все присказка, а вот и сказка: добавляем зависимость

<dependency>
      <groupId>org.codehaus.groovy</groupId>
      <artifactId>groovy-all</artifactId>
      <version>1.8.6</version>
</dependency>

Собственно это почти все, Groovy уже интегрирован в наш проект. Осталось только разобраться с совместной компиляцией Java и Groovy.

groovy-eclipse-compiler

Около года мы пользовались плагином gmaven. В работе с ним были свои «подводные камни», нет смысла уже о них вспоминать, потому что мы перешли на groovy-eclipse-compiler. Редактируем pom.xml

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>2.3.2</version>
	<configuration>
		<source>1.6</source>
		<target>1.6</target>
		<encoding>UTF-8</encoding>
		<compilerId>groovy-eclipse-compiler</compilerId>
	</configuration>
	<dependencies>
		<dependency>
			<groupId>org.codehaus.groovy</groupId>
			<artifactId>groovy-eclipse-compiler</artifactId>
			<version>2.6.0-01</version>
		</dependency>
	</dependencies>
</plugin>

Все, с pom.xml закончили, запускаем STS и импортируем в него проект. File -> Import -> Maven -> Existing Maven Projects. «Зараженный Groovy» проект выглядит совершенно обычно.

Интеграция Groovy в JEE приложение

STS не ругается, это хорошо, но для чистоты эксперимента нужно собрать все мавеном. Выполняем

mvn clean package

и видим в логе искомое:

[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ contactmanager ---
[INFO] Using Groovy-Eclipse compiler to compile both Java and Groovy files
Переходим на Groovy

Итак с java мы практически попрощались, дальше будем писать на Groovy. Контактов набралось уже изрядно и мы хотим эти самые контакты группировать по типам: «семья», «работа» и т.д.

Начнем с POJO, то бишь с POGO.

Создадим каталог src/main/groovy, добавим его в BuildPath, создадим пакет (в нашем примере com.acme.contactmanager.domain)

На пакете кликаем правой кнопкой -> New -> Groovy class

Назовем его… скажем… Полуэкт ContactType и напишем его исходный код:

@Entity
@Table(name = "CONTACT_TYPES", uniqueConstraints = [
	@UniqueConstraint(columnNames = ["code"]),
	@UniqueConstraint(columnNames = ["name"])
])
class ContactType {

	@Id
	@GeneratedValue
	Integer id

	@Basic
	@Column(unique = true, nullable = true)
	String code

	@Basic
	@Column(unique = true, nullable = true)
	String name

	@Basic
	@Column(nullable = true)
	Boolean defaulttype = false

	@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.REFRESH, CascadeType.MERGE], mappedBy = "contacttype")
	List<Contact> contacts = null
}

Ничего сверхестественного, обычные аннотации, разве что в массивах вместо фигурных скобок квадратные. Нет модификаторов, геттеров-сеттеров, точек с запятой, все чисто-аккуратно.

Сообщаем хибернейту, что у нас появилась новая сущность

<hibernate-configuration>
	<session-factory>
		<mapping class="net.schastny.contactmanager.domain.Contact" />
		<mapping class="com.acme.contactmanager.domain.ContactType" />
	</session-factory>
</hibernate-configuration>

Добавляем новое поле в Contact.java, тут уже без геттеров-сеттеров не обойтись

	@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH, optional = false)
	private ContactType contacttype;

	public ContactType getContacttype() {
		return contacttype;
	}

	public void setContacttype(ContactType contacttype) {
		this.contacttype = contacttype;
	}

Собираемся, деплоимся и, если в хибернейте стоит опция hibernate.hbm2ddl.auto=update, то видим в БД новую таблицу CONTACT_TYPES и новое поле CONTACTS.contacttype_id.

Примечание: Если в БД уже есть контакты, то not null ограничение хибернейт поставить не сможет, несмотря на присутствие аннотации optinal=false. Заполнение ссылок дефолтными значениями и выставление not null ограничения оставлю читателям в качестве домашнего задания.

Groovy DAO

Но рано останавливаться на достигнутом. Следующий шаг — groovy-dao. Он, как и в случае с java, должен состоять из пары «интерфейс-реализация», иначе Spring будет недоволен. Повторяем шаги с созданием пакета (на этот раз — com.acme.contactmanager.dao) и созданием класса ContactTypeDAO.groovy

interface ContactTypeDAO {

	void addContactType(ContactType contactType)

	List<ContactType> listContactTypes()

	void removeContactType(Integer id)

	ContactType getDefault()
}

Все на 99% как в java, поэтому переходим к реализации. Не вершина программерского мастерства, но для примера достаточно. Метод listContactTypes() уже содержит основные прелести, ради которых мы все и затеяли (см. комментарии в коде):

@Repository
class ContactTypeDAOImpl implements ContactTypeDAO {

    @Autowired
    private SessionFactory sessionFactory;

    private Session getCurrentSession() {
        sessionFactory.getCurrentSession()
    }

    @Override
    @Transactional
    void addContactType(ContactType contactType) {
        currentSession.save(contactType)
    }

    @Override
    @Transactional
    List<ContactType> listContactTypes() {
        
        // в вызовах любых get-методов можно опускать префикс get и пустые скобки
        def result = currentSession.createQuery("from ContactType").list()

        // проверка, что список пустой, выглядит так
        if(!result){

            // Нужен List<Map<String, Object>>? Что может быть проще!
            def types = [
                // кавычки для ключей не обязательны,
                // значения могут быть любого типа
                [name:'Семья', code:'family', defaulttype: false],
                [name:'Работа', code:'job', defaulttype: false],
                [name:'Знакомые', code:'stuff', defaulttype: true]
            ]

            // вместо цикла можно использовать замыкание 
            types.each { type ->
                ContactType contactType = new ContactType(
                        // в любой Groovy-класс по умолчанию добавляется конструктор,
                        // принимающий параметром Map
                        code: type.code,
                        name : type.name,
                        defaulttype : type.defaulttype
                        )
                currentSession.save(contactType)
                // перегруженный оператор << добавляет элемент в список
                // переменная result доступна в контексте замыкания
                result << contactType
            }
        }
        // ключевое слово return не обязательно
        result
    }

    @Override
    @Transactional
    void removeContactType(Integer id) {
        ContactType contactType = currentSession.get(ContactType.class, id)
        if (contactType) {
            currentSession.delete(contactType)
        }
    }

    @Override
    @Transactional
    ContactType getDefault() {
        currentSession.createCriteria(ContactType.class)
                .add(Restrictions.eq('defaulttype', true))
                .uniqueResult()
    }
}

Осталось интегрировать спежеиспеченный DAO в Java-service:

public interface ContactService {

    // старые методы где-то тут ...

    public List<ContactType> listContactType();
}
@Service
public class ContactServiceImpl implements ContactService {
 
    // старые методы где-то тут ...

    @Autowired
    private ContactTypeDAO contactTypeDAO;
    
    @Override
    @Transactional
    public List<ContactType> listContactType() {
        return contactTypeDAO.listContactTypes();
    }
}

Добавляем вызов в контроллер:

	@RequestMapping("/index")
	public String listContacts(Map<String, Object> map) {

		map.put("contact", new Contact());
		map.put("contactList", contactService.listContact());
		// список типов контактов для JSP
		map.put("contactTypeList", contactService.listContactType());

		return "contact";
	}

Добавляем локализованные сообщения в файлы messages*.properties и выпадающий список типов на JSP (см. в проекте), собираемся, деплоимся. Проверяем:

Интеграция Groovy в JEE приложение

Дальше можно использовать Groovy для тестов, парсить XML и т.д. и т.п.

Исходный код проекта.

Спасибо за внимание, пишите на Groovy!

Ссылки

Учимся готовить: Spring 3 MVC + Spring Security + Hibernate
Groovy Home
Groovy Eclipse compiler
SpringSource Tool Suite
Programming Groovy: Dynamic Productivity for the Java Developer
Проект Lombok, или Объявляем войну бойлерплейту

Автор: monzdrpower

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js